Zeitwerk::NameError due to Module name in rails 7

Viewed 61

I recently upgraded my application from rails 6 to 7 and I am facing one issue due to zeitwerk naming convention. I have a below file which I want to autoload:

app/models/dw/hospital.rb

module DW
  class Hospital < DataWarehouse
    def self.columns
      super.reject{|column| column.name == 'tableau_user' }
    end
  end
end

I tried autoloading this file by adding the following line in my application.rb file:

config.autoload_once_paths << 'app/models/dw'

But when I am starting the server I am getting the following error:

expected file app/models/dw/hospital.rb to define constant Hospital, but didn't (Zeitwerk::NameError)

I'm not sure why this is throwing such error since the constant is already defined. I suspect it is because the module I have defined before the class. Please let me know if anybody how to fix this. I have been stuck at this far too long.

2 Answers

Because you've added app/models/dw to autoload paths, you have to define Hospital but your definition is namespaced DW::Hospital. You don't need to touch autoload config, app/models is already in autoload_paths:

>> ActiveSupport::Dependencies.autoload_paths
=> 
...
 "/home/alex/code/stackoverflow/app/jobs",
 "/home/alex/code/stackoverflow/app/mailers",
 "/home/alex/code/stackoverflow/app/models",   # <======
...

These are so called root directories. It means file structure relative to app/models have to correspond to module/class names.

So if you have dw/hospital.rb in any of the root directories you have to define Dw::Hospital, which you've defined already. You have to watch for inflections as well, it should be Dw, unless you have an acronym inflection rule or zeitwerk inflection:

>> "dw".camelize
=> "Dw"

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym "DW" 
end  

>> "dw".camelize
=> "DW"

If you must nest root directories, you should have a really good reason:

# if you want it to be reloadable,
# use `autoload_paths` instead of `autoload_once_paths`
config.autoload_paths << Rails.root.join("app/models/dw")

# app/models/dw/hospital.rb
class Hospital
end

But as Xavier mentioned in the comment, there is no need for this configuration. Use the default config and don't complicate your set up unnecessarily.

Looks like the problem with uppercase

Normally PascalCase (aka CamelCase) class name must correspond to the snake_case file name. The same rule applies to namespaces and folders

But you have DW in dw folder (not Dw)

You can create some initializer like this

# config/initializers/zeitwerk.rb 

Rails.autoloaders.each do |autoloader|
  autoloader.inflector = Zeitwerk::Inflector.new
  autoloader.inflector.inflect(
    'dw' => 'DW',
    # other special cases
  )
end
Related