Editorial Note: This article was originally co-authored by Darya Kuritsyna and Igor Bilan of Active Bridge.
One of the most powerful Ruby features is the ability to re-open any class and change its methods.
Actually, you can reopen any class and change how it works. This includes the default Ruby classes like String, Array, or Hash. It sounds extremely challenging. Being able to change the expected outcome of a method can cause all sorts of weird behavior and difficult to track down bugs. Monkey Patch to the rescue! In Ruby, a Monkey Patch (MP) is any dynamic modification to a class and is often used as a synonym for dynamically modifying any class (add new or overwrite existing methods) at runtime.
As you see, Ruby is like a sharp knife, it can be extremely effective, but it’s usually your own fault if you cut yourself. Let me explain. On the one hand, there are some disadvantages to working with a sharp knife.
When you monkey patch a class (disadvantages):
- If two libraries monkey-patch the same method, the first monkey-patch will get overwritten and disappear forever.
- If there’s an error, it’ll look like the error happened inside the class.
- It’s harder to turn off your monkey patches.
- If you, say, forgot to require ‘class’ before running this monkey patch, you’ll accidentally redefine the class instead of patching it.
- Instead, you could put your monkey patches in a module, but both variants have the same problem: the patch is global and your changes could unexpectedly be overwritten by the third library.
On the other hand, the MP is a pretty powerful instrument in the right hands, and you are free to use it. Just remember that abusing the patching can easily introduce bugs and make the debugging very difficult.
In case you only need the different behavior in a few specific places, and not throughout the whole system, you can use Refinements. Refinements are a mechanism to add new or redefine existing methods for the behavior of an object in a limited and controlled way.
How to Use Refinements
class C def foo puts "C#foo" end end module M refine C do def foo puts "instance method: C.new#foo in M" end end refine C.singleton_class do def foo_method_of_a_class puts "method of a class: C#foo in M" end end end
using is a method, refinements are only active when it is called. Here are examples of where a refinement MP is and is not active.
In a file:
# not activated here using M # activated here class Foo # activated here def foo # activated here end # activated here end # activated here
In a class: # not activated here class Foo # not activated here def foo # not activated here end using M # activated here def bar # activated here end # activated here end # not activated here
Note that the refinements in M are not activated automatically if the class Foo is reopened later.
# not activated here eval <<EOF # not activated here using M # activated here EOF # not activated here
When not evaluated:
# not activated here if false using M end # not activated here
Is it Okay to Monkey Patch?
Actually, there are some cases where reopening a class does make sense. And there are many cases where it’s fine to Monkey Patch, but it should definitely not be your first weapon of choice.
Monkey patching allows you to come up with a "quick and dirty" solution to a problem, but you should use it very sparingly. Refinements are a way to limit the scope of an extension to a class to only the code we control.
Enjoy your code!
Some useful literature to figure out how you can implement MP: