Home » Advertising » How to Use Monkey Patch in Ruby

How to Use Monkey Patch in Ruby

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

Since 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.
In eval:

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

  1. Refinements
  2. VIRTUOUS CODE
  3. Why is nobody using Refinements
  4. 3 Ways to Monkey-patch Without Making a Mess
  5. In Ruby, what are duck-typing and monkey-patching?
  6. Avoiding Monkey Patching

Leave a Reply

Your email address will not be published. Required fields are marked *

*
*

http://www.scribdbook.com/