Applying monkey patches in Rails
Tags: ruby, rails
Published: 2021-05-25 13:43:50

[Monkey patching](https://en.wikipedia.org/wiki/Monkey_patch) 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](https://www.justinweiss.com/articles/3-ways-to-monkey-patch-without-making-a-mess/) 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`

```ruby
module CoreExtensions
  module Array
    def to_set
      Set.new(self)
    end
  end
end
```

  
`lib/core_extensions/hash.rb`

```ruby
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:  
  

```ruby
# 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`:  
  

```ruby
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](https://github.com/rails/rails/blob/main/activestorage/app/models/active_storage/attachment.rb) for one of the above two classes, you'll see a line like this right at the bottom:  
  

```ruby
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.&nbsp;  
  
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!

This post was originally published on [my blog](https://binarysolo.chapter24.blog/applying-monkey-patches-in-rails/).