CodeSmith
CodeSmith

Reputation: 2243

Ruby method still defined after Module/Class is reloaded

I ran into the class reloading issue while building a ruby gem for my Rails app.

Background

Inside the gem, I have the following class defined

module MyGem
  class AppConfigBase

    def app_title
      "Title Missing"
    end

  end
end

Inside the Rails app, I created a config/my_gem.rb file to hold some app specific overrides. (My goal is to let the user of the gem define some information, but have some defaults available in the super class.)

module MyGem
  class AppConfig < AppConfigBase

    def app_title
      "Great App"
    end

  end
end

At some point inside the gem I call MyGem::AppConfig.new.app_title and I receive the correct string. Yay!

Next, I decided to push the envelope a bit: I'd like to get autoloading working so developers don't have to restart the rails server every time they change this file.

I first tried to see if Rails was already setup to do this. I deleted the app_title method out of AppConfig class. When I reload the page, I still get the "Great App" string returned. :( Rails isn't reloading the config/my_gem.rb (or anything in the config directory) so this is the expected result.

Next, I tried adding the following initializer:

if Rails.env.development?
  ActionDispatch::Callbacks.after do
    load "#{Rails.root}/config/my_gem.rb"
  end
end

The above initializer partially solves the problem but I'd like to understand why it doesn't completely solve the problem. With that initializer in place,

  1. if I remove the app_title method from the subclass, I still get the wrong string (the one in the subclass method...which shouldn't exist anymore).
  2. If I restart the server, I get the superclass version of the string which is expected.
  3. NOW, if re-add the method in the subclass and call app_title, I get the subclass version for the string!

So when I add a method, the load call in the initializer appears to pickup the new method. However, if I delete a method, it doesn't seem to help.

  1. Why would that be?
  2. How can get this file to automatically reload when it's changed? (The easiest workaround I see would be to move the my_gem.rb to a config/my_gem/my_gem.rb and then add config/my_gem to the autoloads path in Rails, but I'd prefer not to create this extra directory just get something autoloaded.)

Upvotes: 3

Views: 424

Answers (1)

Chris Heald
Chris Heald

Reputation: 62668

This isn't quite how autoloading works. Rails' reloader (which is different from, but dependent on the autoloader mechanism) works by undefining constants, then recreating them by requiring the files that define them. What you really want here is to mark the constant unloadable. In your override:

module MyGem
  class AppConfig < AppConfigBase
    unloadable

    def app_title
      "Great App"
    end
  end
end

This will add the constant to a list of constants to be unloaded per-request, but it will also expect that it can load the constant by loading my_gem/app_config.rb and expecting it to be on your load paths. For this reason, this kind of overriding typically happens in lib/ rather than in config/, as the former is in your load path while the latter is not. It also means that the constant probably isn't going to be required on any request except those for which it is used.

It's conventionally expected that changing files in config will require a restart to apply.

Upvotes: 1

Related Questions