Search code examples
ruby-on-railsrubyruby-on-rails-7zeitwerk

Zeitwerk::NameError due to Module name in rails 7


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.


Solution

  • 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.