Search code examples
ruby-on-railszeitwerk

Rails 7.x and Zeitwerk seems to load / find nothing? What did I forget?


I have an old Rails 3.x application that I now migrate to 7.x (It is/was already running under 5.0, so it is not so hard). However, I planed to do this step by step, but it is more or less impossible to install older versions 2023.

The application runs nice and (almost) without any requires with 3.x. Therefore I assume, that I kept the naming conventions.

Edit from: But zeitwerk ignores all my constants, to: But zeitwerk ignores all my Classes and Modules,

not complicated things I read in other Q&A, that are tricky. No just easy things.

I bring two examples, that I think zeitwerk should find, but doesn't

I have a config\initializers\site_init.rb that I use to configure some of my modules.

Site.setup do |config|
    config.default_do_say=false
    config.default_show_all_exceptions=false
end


Site::Cache.setup do |config|
    …
end

So I would expect zeitwerk to find it in lib/site/cache.rb

where it is defined like that:

module Site
    class Cache
        include Singleton
        extend Configurable

        configurable_local_name 'config', 'setup'
        configurable_all_to_module

        configurable_global cache_path: "./tmp/cache"
        configurable_global auto_file_size: 1000
        configurable_global auto_persist: 100
        ...
                                                                            

The second is also easy (config/initializers/bot_check.rb):

BotCheck.setup do |config|
    config.etw_divider=20
    config.etw_min_level=0
end

Located in controllers directly

module BotCheck
    extend Configurable

    configurable_local_name 'config', 'setup'
    configurable_all_to_module
    # configurable_global start_time: Time.now
    configurable_global etw_divider: 5
    configurable_global etw_min_level: 0


    def self.check_level
        count=BotResult.where("created_at > ?", Time.now()-1.hour).count()
        Rails.logger.warn "bot check level: #{count},  #{config.etw_min_level}".white.on_red
        [count/config.etw_divider, config.etw_min_level].max
    end
end

rails zeitwerk:check tells me:

/config/environment.rb:5:in -> NameError: uninitialized constant BotCheck

You see, I struggle early. That's why I am sure, that I have overseen or not understood something very basic of zeitwerk.

The relevant part of application.rb looks like

if Rails.version < "7"
    config.autoload_paths += %W(#{config.root}/app/helpers)
    config.autoload_paths += %W(#{config.root}/app/helpers/fields)
    config.autoload_paths += %W(#{config.root}/app/helpers/tags)
    config.autoload_paths += %W(#{config.root}/lib)
    config.autoload_paths += %W(#{config.root}/lib/geo_lib)
    config.autoload_paths += %W(#{config.root}/lib/tech_draw)
    config.autoload_paths += %W(#{config.root}/lib/site)
    config.autoload_paths += %W(#{config.root}/lib/inplace_trans)
    config.autoload_paths += %W(#{config.root}/lib/flat_form)
    config.autoload_paths += %W(#{config.root}/lib/site_tag_helper)
else
    config.load_defaults 7.0
    config.add_autoload_paths_to_load_path=false
    puts "---------------- 7777777 -----------------"
    config.eager_load = false

    config.autoload_paths << "#{config.root}/lib"
    config.autoload_paths << "#{config.root}/lib/site"
    puts config.eager_load_namespaces
end
#check for typos
config.autoload_paths.each { |p| puts "autoload_paths: #{p}"; puts "#{p} not found or no directory".yellow if !File.directory?(p) }
config.eager_load_paths.each { |p| puts "eager_load_paths: #{p}"; puts "#{p} not found or no directory".yellow if !File.directory?(p) }

Solution

  • Wow, that is a big jump!

    The ability to autoload reloadable constants while the application boots was deprecated in Rails 6, and removed in Rais 7.

    This was a technical change, unrelated to Zeitwerk. Basically, autoloading reloadable code during initialization is a bad idea because initializers do not run on reload, so whatever edits you do to that code won't be reflected.

    The guide Autoloading and Reloading Constants has a section about referring to reloadable constants in the initializers that could help.

    It would also be a good idea to read Classic to Zeitwerk HOWTO.

    One last remark, the application should have zero require calls for autoloadable application files, I recommend that you delete them all, they could be hiding something that is not right, and could in some edge cases make things go wrong.

    The point of autoloading is to not use require at all. This advice is also unrelated to Zeitwerk, and was present in the guide for the classic autoloader too. Of course, you still use require for 3rd party code in gems as usual, this remark only applies to your own autoloadable code.

    After deleting them, please run bin/rails zeitwerk:check, if there's any mismatch between file names and expected constants that will show it.