14

I'm working on a gem that sets properties on ActiveRecord models (such as table_name) dynamically based on a user config option.

I have an initializer that achieves this. My problem however is that in dev mode, these classes are reloaded, so they don't maintain these values set.

So I thought I'd use a railtie to hook into the point where these files are reloaded and run my config again on the models. My problem however is that config.to_prepare in the railtie appears to run before the reload! actually takes place. I can prove this with a bit of logging:

module MyMod
  class Railtie < Rails::Railtie

    config.to_prepare do
      Rails.logger.debug("Contact object_id: #{Contact.object_id}")
    end
  end
end

if I load up my console, I get the first log:

Contact object_id: 2202692040

If I check Contact.object_id it matches up:

Contact.object_id  #=> 2202692040

Then I reload!

reload!

Rails logger from my to_prepare logs:

Contact object_id: 2202692040

So it still has the old object_id, but when I check it in the console:

Contact.object_id  #=> 2197355080

Which is the newly loaded class object id.

So how do I get to_prepare to run after the files are reloaded? Using Rails 3.0.10

update

I've also tried manually attaching this action to the after_prepare callback on the ActionDispatch::Callbacks like so:

initializer "apartment.init" do
  ActionDispatch::Callbacks.set_callback(:prepare, :after) do
    Rails.logger.debug("Contact object_id: #{Contact.object_id}")
  end
end

It does indeed run the callback after the config.to_prepare but it still appears to happen before the files are reloaded... I get the same behaviour as above.

brad
  • 31,987
  • 28
  • 102
  • 155
  • have you tried something like http://stackoverflow.com/questions/5308970/why-does-code-need-to-be-reloaded-in-rails-3/5309126#5309126 – montrealmike May 17 '13 at 18:27

2 Answers2

18

Write an initializer that, if cache_classes is false, uses ActionDispatch::Reloader to set a to_prepare callback that runs your gem's installation routine.

initializer 'foobar.install' do
  if Rails.configuration.cache_classes
    FooBar.install!
  else
    ActionDispatch::Reloader.to_prepare do
      FooBar.install!
    end
  end
end

It'll work both in the console with the reload! method and in the Rack application server.

user229044
  • 232,980
  • 40
  • 330
  • 338
vjt
  • 486
  • 4
  • 9
  • It seems that `to_prepare` [is called once](http://apidock.com/rails/v3.0.9/ActionDispatch/Callbacks/to_prepare/class) when classes are cached too, so you can just do: { ActionDispatch::Reloader.to_prepare do FooBar.install! end } – hlascelles Apr 13 '16 at 10:26
  • 1
    initializer did not work for me, but I removed that and it worked, is hard to find things as this in docs, thanks – sites Feb 09 '17 at 17:03
  • 3
    For new Rails versions 5.x, we need to use `ActiveSupport::Reloader.to_prepare` instead of `ActionDispatch::Reloader.to_prepare` – Blue Smith Aug 10 '17 at 08:29
  • 1
    For new Rails versions 6.x, we now have to use `Rails.application.reloader.to_prepare` instead of `ActiveSupport::Reloader.to_prepare` – Ian Letourneau Oct 04 '20 at 21:38
0

I believe the Rails reloader only unhooks the constants. The models are reloaded with autoloading when the constants are referenced in your app.

In your callback, I think you have to trigger the load manually by referencing all the models. Maybe your gem can keep a list of all the models that include it, then just simply reference the constants to autoload them...

model_names.each { |model_name| model_name.constantize }

You can build the list with self.included:

module MyGem
  self.included(base)
    @model_names ||= Set.new
    @model_names += base.to_s
  end
end
Brian Hempel
  • 8,844
  • 2
  • 24
  • 19