5

I have a Rails initializer (features.rb) that must access a model (Report).

Report.all.each do |report|
  default_to_enabled(report&.feature_name)
end

This all worked perfectly with Rails 6.1 using Zeitwerk and defaults set for 6.1:

config.load_defaults 6.1
config.autoloader = :zeitwerk

But upgrading to Rails 7, keeping defaults at 6.1 (and obviously using Zeitwerk), it is not working:

/Users/brandon/Code/Rails/portal/config/initializers/features.rb:105:in `<main>': uninitialized constant Report (NameError)

If I manually require the Report model, it doesn't solve the problem. Instead I just get

/Users/brandon/Code/Rails/portal/app/models/report.rb:1:in `<main>': uninitialized constant ApplicationRecord (NameError)
Did you mean?  ApplicationConfig

So it seems like there's a whole lot of stuff that has not yet been loaded at this point in the Rails boot-up process, but which would have been loaded at this point running on Rails 6.1.

Adding require 'rails/all' doesn't change anything.

(In case it's not obvious, this applies to all of my models, and lots of other things. None of the classes I have previously had available during initialization are now available on Rails 7.)

How can I fix this and make everything work on Rails 7?

iconoclast
  • 21,213
  • 15
  • 102
  • 138
  • 2
    In case it adds to your insight... I have two Rails 7 apps, and I just added a test initializer in each of them to determine if the models in app/models are loaded when the initializer is run... in both apps the model was *not* loaded. You may need to configure a `config.after_initialize` block in application.rb (https://guides.rubyonrails.org/configuring.html#using-initializer-files) and run your init there. – Les Nightingill Aug 19 '22 at 23:35
  • Yes, thanks, I just discovered that as a solution and was about to post it here when I found your comment. I don't know if it's the only or best solution but it is working for me. – iconoclast Aug 19 '22 at 23:47
  • Of course if anyone else comes up with a better answer, I'll accept their answer over my own. – iconoclast Aug 19 '22 at 23:56

4 Answers4

3

While poking around in the Autoloading guide it occurred to me to try this in config/application.rb:

config.autoload_once_paths << "#{root}/app/models"

but while that made my Report class available, it also created a bigger new problem with Zeitwerk.

The only thing I've found so far is to work around the limitation rather than try to resolve it (or one might say "work with the grain instead of against it") by adding this in config/application.rb instead of the code in my initializer:

config.after_initialize do
  Report.all.each do |report|
    default_to_enabled(report&.feature_name)
  end      
end
iconoclast
  • 21,213
  • 15
  • 102
  • 138
  • calling models that early (initializers) have always been a source of issues, not only with custom code, but also the one provided by some gems -- doing this means that a database should be available by default at any time -- even during CI builds and that's not always desiderable, the after_initialize afaik is a good compromise, but try to keep model loading down during early stages – John Smith Aug 20 '22 at 05:53
3

You should wrap your initializer in a Rails.application.config.to_prepare block.

In your case:

Rails.application.config.to_prepare do
  Report.all.each do |report|
    default_to_enabled(report&.feature_name)
  end
end

(Thank you @Xavier for pointing to this section)

iconoclast
  • 21,213
  • 15
  • 102
  • 138
Lucas Kuhn
  • 242
  • 3
  • 6
2

No, autoload_once_paths is not what you want, because reloading won't update the models.

Please, read this section of the autoloading guide.

Xavier Noria
  • 1,640
  • 1
  • 8
  • 10
1

I'm using rails 7.0.4

> bundle show | grep "rails ("
  * rails (7.0.4)

I tried the following and I was able to load a model inside initializer file

Rails.configuration.after_initialize do
  # Accessing BlockedIp model
  bad_ip = BlockedIp.first.ip

  Rack::Attack.blocklist("Block IPS") do |req|
    req.remote_ip == bad_ip.to_s
  end
end

Basically this loads this particular initializer only after all other configurations are loaded.

Lemme know if this worked :D

Bonnie Simon
  • 51
  • 1
  • 7