Binary Solo

Applying monkey patches in Rails

posted by Ayush Newatia on May 25th, 2021
Monkey patching is one of Ruby's most powerful features. It allows programmers to add methods to core classes which can result in some very elegant APIs. However it's quite easy to shoot yourself in the foot if you don't know what you're doing.

This post from Justin Weiss is a brilliant guide on how to monkey patch responsibly. However he doesn't describe how to actually include your monkey patches in a Rails app, so that's what I'm going to describe in this post.

Following Justin's advice, the implementation of all our monkey patches should go in the lib/core_extensions directory. So we might have some files like this:

lib/core_extensions/array.rb
module CoreExtensions
  module Array
    def to_set
      Set.new(self)
    end
  end
end
 
lib/core_extensions/hash.rb 
module CoreExtensions
  module Hash
    def keys_as_set
      Set.new(keys)
    end
  end
end
 
The lib/ directory in Rails is not autoloaded, so to apply these patches we need to run some code when our app boots. The best place to do this is to create a file called monkey_patches.rb under config/initializers/. All files in this directory are executed when Rails boots.

The contents of the file would look like:

 
# Require all Ruby files in the core_extensions directory
Dir[Rails.root.join('lib', 'core_extensions', '*.rb')].each { |f| require f }
 
# Apply the monkey patches
Array.include CoreExtensions::Array
Hash.include CoreExtensions::Hash
 
This method works fine when we're patching Ruby's core classes, but if we want to patch classes in Rails frameworks such as ActiveStorage or ActionText, it's a bit more tricky as those classes may not be loaded as yet when the initializers are executed.

As you might expect from Rails, there's an elegant way to hook into the load process of those classes via ActiveSupport. So if we wanted to apply patches to ActiveStorage::Attachment and ActionText::RichText, we can include the following code in the monkey_patches.rb:

 
ActiveSupport.on_load(:action_text_rich_text) do
  ActionText::RichText.include CoreExtensions::ActionText::RichText
end
 
ActiveSupport.on_load(:active_storage_attachment) do
  ActiveStorage::Attachment.include CoreExtensions::ActiveStorage::Attachment
end
 
The above code will apply our patches right after the relevant classes are loaded!

If you look at the source code for one of the above two classes, you'll see a line like this right at the bottom:

 
ActiveSupport.run_load_hooks :active_storage_attachment, ActiveStorage::Attachment
 
This is where the name of the hook we pass into the on_load method when applying your patch is defined. 

You can also run load hooks for your own app's classes in the same way to apply some configuration at boot time. A great example would be when using the adapter pattern to integrate with external services, but that's a topic for another blog post!

Subscribe to Binary Solo


We'll send you an email every time a new post is published. We'll never spam you or sell your email address to third parties. Check out our privacy policy for details.