Intro:

I am migrating my application from Rails 3.2.x to Rails 7.0.4.2. I am much more far already, than I expected.

Although I am a zeitwerk noob, I like that approach - especially the "no require" part.

To be sure, that I do not have any requires I renamed all to require3, that only does something in rails 3 (I also have a require7 for those cases where it is really needed). In parallel I have a red line for those in console.

The module:

The module is BotCheck (yes, a capture) and is located as bot_check.rb in lib. Needed classes are in lib/bot_check/..

The source of BotCheck is short. One extend and two methods (these are somehow 'global'). All the rest is behind in classes in lib/bot_check/.

# lib/bot_check.rb

module BotCheck
    extend Configurable

    configurable do
        global_attr etw_divider: 5
        global_attr etw_min_level: 0
    end
    
    # this for debugging …
    puts "loading ====================== module BotCheck".blue.on_yellow

    def self.bot_check_from_bot_id(id)
        …
    end

    def self.bot_check_level
        …
    end
    
    def bot_check_from_bot_id(id)
        BotCheck.bot_check_from_bot_id(id)
    end

    def bot_check_level
        BotCheck.bot_check_level
    end
end      

All the needed modules and classes are like

#lib/bot_check/what_ever.rb

module BotCheck
    class WhatEver
        include BotCheck #only some classes 
        …
        …
    end
end 

I ran this in the old world Rails 3 an all works as expected (even without a require!)

Running it with Rails 7 (zeitwerk), I got "undefined method BotCheck.bot_check_level" called from BotCheck::TagHelper (not a helper just a module that creates the tags), as if the module BotCheck it self was not loaded.

This is not possible, because BotCheck.config is here and also the logger line from Configurable. that configurable was called.

    Configurable: ++++++++++++++++++ extended(BotCheck)     
    

The next try (after 2 hours not understanding anything) was to add the line above (puts "module loaded"

with rails3 I got:

Configurable: ++++++++++++++++++ extended(BotCheck)
loading ==================module BotCheck

with 7

Configurable: ++++++++++++++++++ extended(BotCheck)
"nope" something else

So I put a require7 'bot_check' (remember only for rails 7 not for 3 needed) in application.rb

Now I have my two lines, and everything works.

Conclusion:

Zeitwerk finds the Module, lets me include the module, executes extend within the module, but does not load it.

Now I have questions, because I do not expect me to find a zeitwerk bug after a few days working with it.

  • What is the idea behind this?

  • How is the state of such a module called? "somehow"?

  • What about the no require idea? Or:

  • how to completely load a module without a require, if not even an include does the job?

  • Where to put the require if no other solution?

  • What about side effects? Methods of a module or class that includes such "somehow" loaded, get the included Methods after the module is fully loaded. I do not want to search errors because of that. I made this small check:

    module BotCheck class BotCheckController<SiteController include BotCheck

    def check
        puts "botcheck config: #{BotCheck.config}
        puts "11111111111111 #{self.respond_to?(:bot_check_from_bot_id)}"
        puts "22222222222222 #{BotCheck.respond_to?(:bot_check_from_bot_id)}"
        require7 "bot_check"
        puts "33333333333333 #{self.respond_to?(:bot_check_from_bot_id)}"
        puts "44444444444444 #{BotCheck.respond_to?(:bot_check_from_bot_id)}"
        …
    

with this output:

botcheck config: #<Configurable::BotCheckConfig:0x00007f5080c7c450>
11111111111111 false
22222222222222 false
Warning: Require for 7:'bot_check' in: /home/user/appl/lib/bot_check/bot_check_controller.rb:11:in `check'
Configurable: ++++++++++++++++++ extended(BotCheck) ((<- this is the 2nd time of extend logged))
loading ==========================================================================module BotCheck
33333333333333 true
44444444444444 true
   

Edit based on comment:

The module Configurable is loaded during initialization. So it is available in initializers. Like (for BotCheck): (please note the String!)

Configurable.setup("BotCheck") do |config|
    config.etw_divider=20
    config.etw_min_level=0
end

The module sits and waits for extend Configurable and only after this call it injects the config into the other module. (as far as I know, it's the same algorithm, that rails has know built in. Just without parameter checking)

The module it self does not know anything about the modules that are using it until extend

1

There are 1 best solutions below

2
halfbit On

1st thanks to Xavier Noria, his comment helped me to find the problem.

2nd, as I expected, it was my fault. Zeitwerk is clean!

I had a stone old zombie "bot_check.rb" in controllers, that only "extend Configurable" had.

After removing this, all works as designed / expected.

The question was exactly was going on, would need further investigation, but - I be true - two files same name, same module, but different content in different autoload-pathes …