Forget the monkey patch: an introduction to Refinement

One of the biggest problems in Ruby always was the impossibility to redefine class methods. Popular in many other languages, such practice was made possible in Ruby using a workaround: monkey patching

What’s monkey patching?

Quite probably you’ve already made one but haven’t notice: monkey patching is the ability to extend and/or modify a software in runtime.

This approach become popular in Ruby due the ease to redefine a class and overwriting their methods (the so desired overload).

Let’s take an example:

class Cow
def say
puts 'Moo'.upcase
end
end

class Dog
def say
puts 'Roof'.upcase
end
end

cow = Cow.new
dog = Dog.new

cow.say # => "MOO"
dog.say # => "ROOF"

The snippet above define two classes: Cow and Dog, both implementing the method say, which basically write the animal’s sound in uppercase.

However, let’s say that, for some reason, you’ve defined the following snippet somewhere else in your project:

class String
def upcase
self.reverse
end
end

After running your cow/dog say methods, you got a strange behaviour:

cow.say # => "ooM"
dog.say # => "fooR"

What the hell happened?

You, my friend, just got monkey patched!

The snippet above, besides useful in some other part of the system, has messed with the whole Ruby runtime. It’s redefining the String method upcase, making it reverse the string instead actually uppercasing it. The act of redefine this method on runtime is called monkey patching.

It can be useful in some situations but, if used without restrictions, can lead to hard to find bugs on your system.

With that in mind, from Ruby 2.0, we got a better and more secure solution, called Refinements.

Refinement

Refinement, in small words, is the act of monkey patching without messing with the runtime environment.

Using our previous example, you’ve faced the problem introduced by your last monkey patching, but still want a solution for it.

So, check the module below:

module StringRefinement
refine String do
def upcase
self.reverse
end
end
end

cow.say # => "MOO"
dog.say # => "ROOF"

using StringRefinement

cow.say # => "ooM"
dog.say # => "fooR"

The example above makes use of refinement. Refinement is a module that makes use of the keyword refine to (duh) refine a class. In the example above, the module StringRefinement is refining the class String and overloading its method upcase.

The interesting part is: the refinement isn’t used unless it’s explicitly invoked. That’s why the first two calls of say printed the sounds in uppercase, and the last two printed it reversed, since the refinement was invoked right before them.

This is specially useful if you need to refine a method only in a specific context.

For example: if you want to turn your Cow class in a CrazyCow, you can just include the refinement in the new class:

class CrazyCow
using StringRefinement

def say
puts 'Moo'.upcase
end
end

crazycow = CrazyCow.new

crazycow.say # => "ooM"
cow.say # => "MOO"
dog.say # => "ROOF"

The refinement was just used in the CrazyCow class context, keeping the other classes and, specially, the Ruby runtime, intact.s

Liked this post?

Have questions? Suggestions?

Follow me on Twitter and let's engage in a conversation