1

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.

mechnicov
  • 12,025
  • 4
  • 33
  • 56
Dev V
  • 85
  • 1
  • 10

2 Answers2

6

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.

Alex
  • 16,409
  • 6
  • 40
  • 56
  • 2
    Excellent answer. Let me add, to be clear, that there is no need to touch the configuration (please do remove that config.autoload_once_paths setting), and that this is covered in the migration HOWTO: https://guides.rubyonrails.org/classic_to_zeitwerk_howto.html#acronyms – Xavier Noria Sep 16 '22 at 07:22
  • Alex and @XavierNoria Thankyou for you answer, it was really helpful. Although I had another question related to zeitwerk. Please answer if you know something about that. I have been stuck at this rails 7 upgrade for quite a while now. https://stackoverflow.com/questions/73754260/zeitwerk-not-autoloading-models-in-rails-7 – Dev V Sep 17 '22 at 11:04
  • 1
    I answered that other question . – Xavier Noria Sep 17 '22 at 12:44
2

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
mechnicov
  • 12,025
  • 4
  • 33
  • 56