iconoclast
iconoclast

Reputation: 22670

Rails Model no longer available in initializer after upgrade to Rails 7.0

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?

Upvotes: 8

Views: 3795

Answers (4)

Lucas Kuhn
Lucas Kuhn

Reputation: 402

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)

Upvotes: 10

Bonnie Simon
Bonnie Simon

Reputation: 61

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

Upvotes: 1

Xavier Noria
Xavier Noria

Reputation: 2324

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

Please, read this section of the autoloading guide.

Upvotes: 3

iconoclast
iconoclast

Reputation: 22670

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

Upvotes: 3

Related Questions