Clean Monkey Patching in Ruby
I have a library and I want to change some of its behaviours. I could straighout monkey patch, or I could be a lot cleaner.
Ruby comes with so many tools to modify an existing class (and to shatter the Open/Closed Principle) but it also comes with a rather beautiful way to change the class hierarchy: prepend
.
In my case, I wanted to change the behaviour of one method in a library's class but still have access to original implementation. I wanted to be able to call super
.
Let's take this code:
module Plant
end
class Beanstalk
include Plant
def water
'grow'
end
end
So I have a little Beanstalk
and if I water
one, it'll answer with 'grow'
. Let's have a look at how ruby has built up the inheritance chain:
Beanstalk.ancestors
# => [Beanstalk, Plant, Object, Kernel, BasicObject]
That's fine, but I want to change the behaviour of the water
method and it's in some library file or other. I could just monkey-patch and reopen the class and overwrite the method with my implementation but that just blasts away the previous version of that method and I want something more subtle.
This is where prepend
comes in. In my head, I couldn't quite see what prepend was for: I imagined it prepending something to the inheritance tree before the class or module I was currently defining (which is exactly what include
does). But this is where a look at the results from ancestors
really helps.
Let's try this:
module GiantBeanstalk
def water
"#{super} very, very tall"
end
end
Beanstalk.prepend(GiantBeanstalk)
Now let's look at the inheritance tree:
Beanstalk.ancestors
# => [GiantBeanstalk, Beanstalk, Plant, Object, Kernel, BasicObject]
Ho! GiantBeanstalk
has been prepended to the ancestor chain. If I ask for a Beanstalk.new
, I'm actually going to get a GiantBeanstalk
and one that can call super
on the ancestor methods...
Beanstalk.new.water
# => "grow very, very tall"
Member discussion