fxn/zeitwerk

Namespaced concerns don't load

stanley90 opened this issue ยท 33 comments

Hi, I'm migrating an older Rails app to Zeitwerk and I'm blocked on the following problem (not even sure if to post this here or to Rails):

# app/models/document.rb
class Document
  include Mongoid::Document
  include ::Document::Confirmable
  # and a few more
end

# app/models/concerns/document/confirmable.rb
module Document::Confirmable
  extend ActiveSupport::Concern
end

When running zeitwerk:check, none of the Document:: namespaced concerns get loaded:

# (grepped log)
Zeitwerk@rails.main: autoload set for Document, to be loaded from /Users/stanley/workspace/fundbase/app/models/document.rb
Zeitwerk@rails.main: autoload set for Document::Confirmable, to be loaded from /Users/stanley/workspace/fundbase/app/models/concerns/document/confirmable.rb
Zeitwerk@rails.main: autoload set for Document::Schedulable, to be loaded from /Users/stanley/workspace/fundbase/app/models/concerns/document/schedulable.rb
Zeitwerk@rails.main: autoload set for Document::SensitiveContent, to be loaded from /Users/stanley/workspace/fundbase/app/models/concerns/document/sensitive_content.rb
Zeitwerk@rails.main: expected file /Users/stanley/workspace/fundbase/app/models/concerns/document/confirmable.rb to define constant Document::Confirmable, but didn't

I wrapped the concern definition in begin/rescue/end which gives

#<NameError: uninitialized constant Document::Confirmable
  include ::Document::Confirmable
                    ^^^^^^^^^^^^^>

I also tried to play a bit with prefixing the Document with :: to prevent a conflict with Mongoid::Document, but that didn't help.

fxn commented

Yeah, the leading :: should not be necessary because the module Mongoid::Document does not have a constant called Document itself. Indeed, Confirmable would be enough.

The trace says that Zeitwerk is managing the file, but for some reason the expected constant Confirmable is not found un Document after loading the file.

Since this is very strange, let's try some guerrilla things first. I'd suggest adding 4 traces in the following places: first line of the file, first line in the body of Document::Confirmable, last line of the body, last line of the file. Can you add them and check if all of them are printed?

Actually I already did that before, and that's why I put the rescue to see why the module definition doesn't finish. Only the output before the module definition is printed, and then a few of outputs from before/in the Document definition:

--- BEFORE CONFIRMABLE
--- BEFORE DOCUMENT
--- IN DOCUMENT
--- BEFORE DOCUMENT
--- IN DOCUMENT
...

What seems to be happening is that the Document in the module Document::Confirmable is trying to load the Document class (which fails to load because it tries to include the module?), but that's a circular reference?

fxn commented

Document in the include call is redundant, but should work, because the lookup path would find it in Object, since it was just defined.

The interesting bit is that BEFORE CONFIRMABLE is printed before BEFORE DOCUMENT. Who is triggering the autoload of that file? Can you pp caller_locations first thing in app/models/concerns/document/confirmable.rb?

["/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.12/lib/zeitwerk/kernel.rb:38:in `require'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.12/lib/zeitwerk/kernel.rb:38:in `require'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/dependencies/zeitwerk_integration.rb:49:in `require_dependency'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/mongoid-7.5.4/lib/rails/mongoid.rb:49:in `load_model'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/mongoid-7.5.4/lib/rails/mongoid.rb:25:in `block (2 levels) in load_models'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/mongoid-7.5.4/lib/rails/mongoid.rb:24:in `each'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/mongoid-7.5.4/lib/rails/mongoid.rb:24:in `block in load_models'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/mongoid-7.5.4/lib/rails/mongoid.rb:16:in `each'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/mongoid-7.5.4/lib/rails/mongoid.rb:16:in `load_models'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/mongoid-7.5.4/lib/rails/mongoid.rb:35:in `preload_models'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/mongoid-7.5.4/lib/mongoid/railtie.rb:80:in `block (2 levels) in <class:Railtie>'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:427:in `instance_exec'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:427:in `block in make_lambda'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:198:in `block (2 levels) in halting'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:604:in `block (2 levels) in default_terminator'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:603:in `catch'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:603:in `block in default_terminator'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:199:in `block in halting'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:512:in `block in invoke_before'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:512:in `each'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:512:in `invoke_before'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/callbacks.rb:105:in `run_callbacks'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/reloader.rb:88:in `prepare!'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/application/finisher.rb:124:in `block in <module:Finisher>'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/initializable.rb:32:in `instance_exec'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/initializable.rb:32:in `run'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/initializable.rb:61:in `block in run_initializers'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/3.1.0/tsort.rb:228:in `block in tsort_each'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/3.1.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/3.1.0/tsort.rb:431:in `each_strongly_connected_component_from'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/3.1.0/tsort.rb:349:in `block in each_strongly_connected_component'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/3.1.0/tsort.rb:347:in `each'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/3.1.0/tsort.rb:347:in `call'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/3.1.0/tsort.rb:347:in `each_strongly_connected_component'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/3.1.0/tsort.rb:226:in `tsort_each'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/3.1.0/tsort.rb:205:in `tsort_each'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/initializable.rb:60:in `run_initializers'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/application.rb:391:in `initialize!'",
 "/Users/stanley/workspace/fundbase/config/environment.rb:5:in `<top (required)>'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.12/lib/zeitwerk/kernel.rb:38:in `require'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.12/lib/zeitwerk/kernel.rb:38:in `require'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/dependencies.rb:332:in `block in require'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/dependencies.rb:299:in `load_dependency'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/activesupport-6.1.7.7/lib/active_support/dependencies.rb:332:in `require'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/application.rb:367:in `require_environment!'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/application.rb:533:in `block in run_tasks_blocks'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:281:in `block in execute'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:281:in `each'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:281:in `execute'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:219:in `block in invoke_with_call_chain'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:199:in `synchronize'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:199:in `invoke_with_call_chain'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:243:in `block in invoke_prerequisites'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:241:in `each'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:241:in `invoke_prerequisites'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:218:in `block in invoke_with_call_chain'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:199:in `synchronize'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:199:in `invoke_with_call_chain'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:188:in `invoke'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:160:in `invoke_task'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:116:in `block (2 levels) in top_level'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:116:in `each'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:116:in `block in top_level'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:125:in `run_with_threads'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:110:in `top_level'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/commands/rake/rake_command.rb:24:in `block (2 levels) in perform'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:186:in `standard_exception_handling'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/commands/rake/rake_command.rb:24:in `block in perform'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/rake_module.rb:59:in `with_application'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/commands/rake/rake_command.rb:18:in `perform'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/command.rb:50:in `invoke'",
 "/Users/stanley/.asdf/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/railties-6.1.7.7/lib/rails/commands.rb:18:in `<top (required)>'",
 "bin/rails:5:in `require'",
 "bin/rails:5:in `<main>'"]
fxn commented

require_dependency and load_model are suspicious there. Is as if Mongoid was trying to load the concern directly using require_dependency, without going through the model first. That does not seem right, why would Mongoid be loading a file that is related only indirectly?

I cannot follow that hint right now, not familiar with Mongoid either. Just pointing it out in case it helps you investigate further.

The calls to require_dependency are invoked because of preload_models: true (in Mongoid config), which is needed for STI.
But maybe that can be replaced with https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#option-2-preload-a-collapsed-directory. I'll play with it tomorrow.

fxn commented

@stanley90 yes, we have it.

As you guessed, there is a circularity. This is a limitation of the current require_dependency in Rails, I should revise it.

Indeed, if preloading models is done just to load that STI, the technique you linked to is going to work and, assuming there are more models, it is going to do less things.

Okay, so I disabled Mongoid's preload_models and cleaned up the collapsed and eager-loaded directories. This part seems to work, but still a lot of mess ahead.
Do you think I should post about this situation to Mongoid's JIRA too?

fxn commented

Which is the mess?

Occasional wrong namespacing, some require statements, etc. (10-year old app...)
Btw I realized you're on this year's Euruko, I'm buying you a beer in Sarajevo for the quick help ๐Ÿ˜€ ๐Ÿป

Btw, if I have some core extensions, should I eager_load or require them in the initializer?

fxn commented

@stanley90 up for that beer! :)

If core extensions is code that reopens already existing classes/modules, that is not managed by the autoloaders, therefore it should be required normally, the autolaoder API cannot be used. If the code was mixed with autoloaded code in the project tree, the autoloader should be configured to ignore it.

So (almost) everything works now, but found one more issue. According to the documentation on STI I did

unless Rails.application.config.eager_load
  Rails.application.config.to_prepare do
    Rails.autoloaders.main.eager_load_dir(Rails.root.join('app/models'))
  end
end

but if I try the Rails console and Rspec with config.eager_load = true, the model subclasses are not loaded (zeitwerk:check doesn't complain, though). I could remove the unless condition, but it smells a bit.

fxn commented

Let me just say that you'd normally eager load the collapsed subdirectory of app/models that contains the STI, if there is one. Just because it would load less code than eager loading all the models. However, if loading the entire app/models was on purpose, that is fine of course, a remark just in case it wasn't :).

What you say re eager loading is certainly strange. Is there anything in app/models eager loaded if config.eager_load is true?

How do I check that root models are eager-loaded (vs auto-loaded when I reference them)?

fxn commented

If you execute in a shell bin/rails r 1, the application boots, runner executes the 1 script, and exits.

Eager loading happens at boot time.

Okay so I did rails r 'pp Mongoid.models' and only a very few models were printed (maybe they're referenced and autoloaded somewhere), but most models are not.

fxn commented

Interestiing. Can you share the ouput of this?

bin/rails r 'p Rails.autoloaders.main'

I suspect things are configured in some particular way.

autoloaders.txt

and the configuration for app is

    %w[
      activities assets alpha channels emails export export/export_id exposures filters holdings images native notes
      portfolios sets tracking_schedule
    ].each do |dir|
      Rails.autoloaders.main.collapse(Rails.root.join('app/models', dir))
    end
    %w[alpha connection_requests].each do |dir|
      Rails.autoloaders.main.collapse(Rails.root.join('app/modules', dir))
    end

    unless Rails.application.config.eager_load
      Rails.application.config.to_prepare do
        Rails.autoloaders.main.eager_load_dir(Rails.root.join('app/models'))
      end
    end
fxn commented

Thank you, happy to see the app makes an extensive use of collapsed directories, was worth the work!

I don't quite see what you say in that autoloader inspection. The ivar @autoloads contains constants that have autoloads set but have not been triggered. If app/models was not eager loaded, we'd see quite a bit of autoloads for them (the ones at the top level).

On the other hand @to_unload has the constants that have been loaded so far (if reloading is enabled). I see 274 occurrences of app/models there.

Can you prove that there exists a model that is not eager loaded? Can you put a trace in a model and do not have it printed? It has to be a model, not a concern. While concerns are in subdirectories, they are considered to be separate trees.

Thank you, happy to see the app makes an extensive use of collapsed directories, was worth the work!

I wish zeitwerk existed earlier and we'd structure the app better ๐Ÿ˜€

Can you prove that there exists a model that is not eager loaded? Can you put a trace in a model and do not have it printed? It has to be a model, not a concern. While concerns are in subdirectories, they are considered to be separate trees.

Sure. Only these are loaded:

rails r 'pp Mongoid.models'
--- Fund
--- Asset
Running via Spring preloader in process 34012
[Mongoid::GlobalDiscriminatorKeyAssignment::InvalidFieldHost,
 DataMigration,
 Mongoid::GridFs::Fs::File,
 Mongoid::GridFs::Fs::Chunk,
 FundClass,
 Asset,
 TrackedField,
 FundUniverse,
 TimeWithUnit,
 OrganizationRole,
 MasterOption,
 LiquidityInfo,
 SystemSetting,
 Subscription::Trade,
 User]

E.g. Asset has descendants Asset < Fund < HedgeFund, you see the --- Fund and --- Asset are printed, but --- HedgeFund is not. And interestingly, Asset is in the list of models, but Fund is not.

Also tried printing:

rails r 'pp Asset.descendants'
[Fund]
fxn commented

OK, we are progressing.

If we look at the state of the autoloader, HedgeFund has been loaded. It is in the @to_unload collection:

"HedgeFund"=>
 ["/Users/stanley/workspace/fundbase/app/models/assets/hedge_fund.rb",
  [Object, :HedgeFund]],

So the question is, how is Mongoid.models implemented?

I don't know if the descendants call is relevant because you need to install some sort of descendants tracking for that to work (like ActiveSupport::DescendantsTracker). Is Mongoid supposed to do that?

fxn commented

Mongoid seems to track models by tracking the inclusion of Mongoid::Document:

module Mongoid
  module Document
    included do
      Mongoid.register_model(self)
    end
 end

So, only those classes with actual includes are registered, subclasses aren't.

See this script for example:

require 'mongoid'

class Foo
  include Mongoid::Document
end

class Woo < Foo
end

class Bar
  include Mongoid::Document
end

class Baz < Bar
end

pp Mongoid.models # => [Mongoid::GlobalDiscriminatorKeyAssignment::InvalidFieldHost, Foo, Bar]

Woo and Baz are not included.

Yes, I suspected Mongoid.models might do something specific.. But anyway, if I'm back to simple things, I get:

rails r 'p Asset.subclasses; p Fund.subclasses'                                                                     ๎‚ฒ โœ” ๎‚ณ 3.1.4 ๏ˆ™ ๎‚ณ 12.22.9 ๎˜— 
--- Fund
--- Asset
Running via Spring preloader in process 37880
[Fund]
[]

You say you see the HedgeFund loaded, but it's not printed here ๐Ÿค”

fxn commented

When things get weird in a way that defies logic, I remove Spring from the equation. Could you try?

fxn commented

Could you also enable logging and grep by that class name? Let's work with Spring disabled until we understand what is going on.

Spring disabled. Grepped output (I didn't grep for "fund" because that's in a million places).

$ rails r 'p Asset.subclasses; p Fund.subclasses' | egrep -i 'hedgefund|hedge_fund|asset|---'
Zeitwerk@rails.main: autoload set for Asset, to be loaded from /Users/stanley/workspace/fundbase/app/models/assets/asset.rb
Zeitwerk@rails.main: autoload set for EtFund, to be loaded from /Users/stanley/workspace/fundbase/app/models/assets/et_fund.rb
Zeitwerk@rails.main: autoload set for FofFund, to be loaded from /Users/stanley/workspace/fundbase/app/models/assets/fof_fund.rb
Zeitwerk@rails.main: autoload set for Fund, to be loaded from /Users/stanley/workspace/fundbase/app/models/assets/fund.rb
Zeitwerk@rails.main: autoload set for HedgeFund, to be loaded from /Users/stanley/workspace/fundbase/app/models/assets/hedge_fund.rb
Zeitwerk@rails.main: autoload set for MutualFund, to be loaded from /Users/stanley/workspace/fundbase/app/models/assets/mutual_fund.rb
Zeitwerk@rails.main: autoload set for OtherAlternative, to be loaded from /Users/stanley/workspace/fundbase/app/models/assets/other_alternative.rb
Zeitwerk@rails.main: autoload set for PrivateEquityFund, to be loaded from /Users/stanley/workspace/fundbase/app/models/assets/private_equity_fund.rb
Zeitwerk@rails.main: autoload set for Admin::AssetsController, to be loaded from /Users/stanley/workspace/fundbase/app/controllers/admin/assets_controller.rb
--- Fund
--- Asset
Zeitwerk@rails.main: constant Asset loaded from file /Users/stanley/workspace/fundbase/app/models/assets/asset.rb
Zeitwerk@rails.main: constant Fund loaded from file /Users/stanley/workspace/fundbase/app/models/assets/fund.rb
fxn commented

I tried to reproduce this with a minimal app and could not. That is, I created a folder app/models/assets/foo.rb, collapsed app/models/assets, and eager_load_dir('app/models'). It eager loaded the file.

Can you reproduce with a minimal app?

fxn commented

BTW, I find very hard to believe that the traces do not show HedgeFund loaded and at the same time that @to_unload in the autoloader inspection has the constant. If that turns out to be the case, I'll invite you to a drink next time we have the opportunity :).

Soo I think I figured it out and it's all my mistake. The order is the problem:

# application.rb
config.eager_load = true # this is what I did just for testing - wrong place!
...
unless Rails.application.config.eager_load # wrong place again, should be later, in an initializer
  Rails.application.config.to_prepare do 
    Rails.autoloaders.main.eager_load_dir(Rails.root.join('app/models'))
  end
end

# development.rb
config.eager_load = false # should change this for testing instead

Besides the wrong order of things happening, eager_load was true during a part of initialization and false during another part (set both in application.rb and development.rb).
Correct order is: application.rb, development.rb, initializer

The drinks are on me then ๐Ÿ˜€

fxn commented

Ahhhh my friend! ๐Ÿ˜€

fxn commented

@stanley90 how's the migration going? Do you have autoloading in place or need more help?

Seems to be finished and working fine, a bit stuck in QA ๐Ÿ˜† But I think we can close this issue. Thanks for the help and I'll catch you in Sarajevo ๐Ÿ˜