20

I'm monkey-patching a Rails engine with something like:

SomeClass.class_eval do
  # ...
end

The first time I hit the web site, on development mode at least, it works, but the second time it's like my patch never existed. I presume it's Rails auto-reloading the engine (which is installed in vendor/) and not reloading my code. This is Rails 2.3.

Any ideas how to do it so that my code also gets reloaded?

Nakilon
  • 34,866
  • 14
  • 107
  • 142
Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
  • I hit a similar problem once and the only way I could fix it was by running rails in production mode on my dev machine :(. I'm also interested on this. – kikito Dec 16 '10 at 12:40
  • @egarcia: ouch, I hope we can find a better solution this time. – Pablo Fernandez Dec 16 '10 at 12:42
  • how do you run your code, is it webbrick, mongrel or passenger ? please post rails and server versions. – mpapis Dec 20 '10 at 23:15

6 Answers6

29

EDIT: This solution only works for Rails 3+ since it's dependent on some functionality in Rails::Railtie. Put this code in an initializer.

This question is quite old, but here's a solution I found:

Rails.configuration.to_prepare do
  SomeClass.class_eval do
    # ...
  end
end

This forces Rails to reload the class on every request in development mode, but only once in production.

James H
  • 1,824
  • 22
  • 23
9

I just wrote my first monkey-patch, and so needed to come up with a set of conventions around it. Here's what I came up with:

  1. Place your extensions under lib/ext/. (Suggested by veteran workmad3 in #rubyonrails IRC room.) In my case, I'm adding a method to the Mail::Message class (from the mail gem, used by ActionMailer), so I created:

    /lib/ext/mail/message.rb

  2. Open the class or module and add your code:

    module Mail class Message def to_is_phone? !!(self.to.first =~ /^\+1\d{10}$/) end end end

  3. Create an initalizer to load all your monkey-patches. Rails will autoload a file when a constant is referenced, but since you're adding methods to existing classes/modules rather than defining new ones, that won't work, so you have to manually require all your monkey-patches. So I created:

    /config/initializers/monkey_patches.rb

    Which contains:

    require 'ext/mail/message'

odigity
  • 7,568
  • 4
  • 37
  • 51
  • Note that I'm currently using Rails 4.1, but I think this should work in older versions as well. – odigity Apr 14 '14 at 17:52
  • 6
    `monkey_patches.rb`can instead be `Dir[Rails.root.join('lib/ext/*.rb')].each { |file| require file }` and all monkey patches will be picked up. – Dan Kohn Oct 27 '14 at 03:09
  • @DanKohn double asterisk ftw! :) ``Dir[Rails.root.join('lib/ext/**/*.rb')].each { |file| require file }`` – viktor_vangel Jul 15 '21 at 07:36
5

If you place the patch in any .rb file inside /config/initializers, it should work.

Fuad Omar
  • 81
  • 1
  • 5
0

Have a look at how this gem handles "decorating" aka monkey patching something in an engine or vice versa:

https://github.com/EPI-USE-Labs/activesupport-decorators

Pierre Pretorius
  • 2,869
  • 22
  • 21
0

Unfortunately, there is no way to hook into the reloading mechanism of Rails 2.x. What you could do, is place your patch somewhere in the app or lib directory. (lib/core_ext is probably the preferred location). Then add the directory to the autoload_paths in your config.

You might also need to open the class, rather than using class_eval.

iain
  • 16,204
  • 4
  • 37
  • 41
  • The monkey-patch is already on lib/ which is already on the autoload_paths on Rails 2. I'm not sure why, but opening the class with the class keyword instead of class_eval results into an error, an exception thrown later on. – Pablo Fernandez Dec 16 '10 at 13:01
0

It's ugly, but I found that if I put this kind of code at the bottom of environments.rb it always guaranteed correct load-order on startup.

Taryn East
  • 27,486
  • 9
  • 86
  • 108