Rails 6: Zeitwerk::Nameerror Doesn't Load Class from Module

Zeitwerk doesn't requires lib classes properly in Rails 6.1.6

An autoload path is a root directory, not its contents.

You need to remove the wildcards as documented here.

Rails 6 not auto-loading class properly in development (zeitwerk mode)

https://github.com/rails/rails/issues/36054

Ok so turns out that the concerns folders are actually in the rails load paths, so it means they shouldnt be namespaced, the same way a class is app/models isn't in the Models namespace.

The fact that they were working in Rails 5 was some sort of side-effect that I wont get into here, but you can read about in the link above.

There is a programmatical way to detect Zeitwerk::NameError when upgrading to Rails 6?

You can check the autoload compatibility of a project with the zeitwerk::check task:

$ bin/rails zeitwerk:check
Hold on, I am eager loading the application.
All is good!

It exercises all the autoload paths and will flag issues such as those caused by the change in constant name inference (such as those with acronyms). The task doesn't appear to be widely advertised or documented except in the upgrade guide. But, I actually use it sometimes to just flush out constant naming issues in the project generally (i.e., where the class doesn't match the file).

rails 7 zietwerk constants not found

Thanks for the updates. The problem is this glob:

config.eager_load_paths += Dir["#{config.root}/lib/**/"]

Adding to the eager load paths, adds also to the autoload paths (I find this a bit confusing, but it is how it works). Therefore, that glob is adding, for example, lib/project_name to the autoload paths. The key observation to understand what is happening is that when there are nested root paths, the most nested one wins as far as the files and directories below that one is concerned.

So, since lib is in the autoload paths, anything below lib is assumed to be defined in Object. But, since lib/project_name is in the autoload paths too, that subtree also represents Object. It is the same situation like the one you have in app/models and app/models/concerns.

These docs explain it, but they were easy to overlook in this case because it was not obvious your configuration is affected by that point.

The solution is to add only lib:

config.autoload_paths << "#{config.root}/lib"
config.eager_load_paths << "#{config.root}/lib"

Just like that, without wildcards. Technically, the first line is redundant given the second one, but in my view this is subtle and not obvious, I personally like to make both explicit, but as you like here. (There won't be duplicated entries, the final collection passes through uniq.)

Since we are on it, let me also comment something extra. Maybe your application already does this, but just in case, let me say that lib has often files and directories that are not meant to be autoloaded. For example, lib/tasks. It is clean to be deliberate about them and tell the autoloader:

Rails.autoloaders.main.ignore("#{config.root}/lib/tasks")

Otherwise, the autoloader will believe lib/tasks is a namespace, will define an autoload for Tasks in Object, will eager load (== actually define) the module if eager loading is enabled, etc. Conceptually, lib/tasks does not have code to be autoloaded/eager loaded, and the configuration has to reflect that. Same with other similar directories.



Related Topics



Leave a reply



Submit