Monkey Patching. When you first try Ruby, it’s amazing. You can add methods right to core classes! You don’t have to call
Time.now.advance(days: -1), you can write
1.day.ago! It makes Ruby a joy to read and write. Until…
You hit weird bugs because a patch changed
You get confused about which code actually ran, so you can’t debug it when it breaks.
And you finally figure out that all your problems were caused six months ago, when you monkey-patched
Enumerable to make one line of code five characters shorter.
But what’s the alternative? No convenience at all? Code that looks like
GroupableArray.new([1, 2, 3, 4]).in_groups_of(2)? Waiting for
blank? to make it into core Ruby before you’re allowed to have nice user input handling?
You don’t want to give up on monkey patches entirely. But how can you write monkey patches that won’t make you want to fire yourself for incompetence the next time you see them?
Put them in a module
When you monkey patch a class, don’t just reopen the class and shove your patch into it:
1 2 3 4 5
If two libraries monkey-patch the same method, you won’t be able to tell.
The first monkey-patch will get overwritten and disappear forever.
If there’s an error, it’ll look like the error happened inside
While technically true, it’s not that helpful.
It’s harder to turn off your monkey patches.
You have to either comment out your entire patch, or skip requiring your monkey patch file if you want to run code without it.
If you, say, forgot to
require 'date'before running this monkey patch, you’ll accidentally redefine
DateTimeinstead of patching it.
Instead, put your monkey patches in a module:
1 2 3 4 5 6 7 8 9
This way, you can organize related monkey patches together. When there’s an error, it’s clear exactly where the problem code came from. And you can include them one group at a time:
If you don’t want the patch anymore, just comment out that line.
Keep them together
When you monkey patch core classes, you add to the core Ruby APIs. Every app with core patches feels a little bit different. So you have to have a way to quickly learn those changes when you jump into a new codebase. You have to know where your monkey patches live.
I mostly follow Rails’ monkey-patching convention. Patches go into
lib/core_extensions/class_name/group.rb. So this patch:
1 2 3 4 5 6 7 8 9
would go into
Any new developer could browse through the Ruby files in
lib/core_extensions and learn what you added to Ruby. And they’ll actually use those convenient new methods you wrote, instead of those methods just getting in the way.
Think through the edge cases
I don’t know why
Enumerable doesn’t have a
sum method. So often, I wish I could write
[1, 2, 3].sum, or
["a", "b", "c"].sum, or
[Article.new, Article.new, Article.new].sum… Oh.
When you monkey patch a class, you’re usually thinking about one thing you want to make easier. You want to calculate a sum of numbers, but forget that Arrays can hold other things.
Right now, it makes sense. You’d never try to calculate the average of a bunch of hashes. But when you attach methods to an object that just plain fail when you call them sometimes, you’ll confuse yourself later on.
You can deal with this in a few ways. From best to worst:
Handle unexpected input reasonably.
This works well if your patch works with strings. You can get something reasonable from almost anything if you call
to_son it first. And Confident Ruby will teach you a ton of ways to deal with different kinds of input.
Handle the error in a clearer way.
This could be as easy as throwing an
ArgumentErrorwith a good message when you see input you’re not expecting. Don’t depend on someone else understanding random
Document the kind of input you expect in a comment.
The other two options are better, if you can use them. But if you can’t check for edge cases inside your patch, at least document them. That way, once your caller figures out it’s your patch that’s causing their problem, they’ll have an idea of what you were trying to do.
My all-time favorite monkey patch
Finally, I want to leave you with my all-time favorite monkey patch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
It makes attaching CSS classes to HTML elements so much nicer.
Sensible monkey patching
Monkey-patching core classes isn’t all bad. When you do it well, it makes your code feel more like Ruby. But just like any of Ruby’s sharp edges, you have to take extra care when you use them.
If you keep your patches together, group them into modules, and handle the unexpected, your monkey patches will be as safe as they can be.
What’s the best monkey patch you’ve ever written (or seen)? Leave a comment and tell me about it!