Dynamically Modifying Methods in Ruby
2010/04/24Or, Navigating Nil Without the Tap Dancing
Recently, I came upon the need to modify a group of existing methods in Ruby. More specifically, I wanted to change the return value for a few of the built-in methods inherited by a subclass of Array I was writing. Luckily, the language provides a set of slick, if somewhat obscure, methods to achieve just this.
Aside: Method Chaining in Ruby
The motivation for this was method chaining. Array’s “bang” methods
(compact!, uniq!, etc.), which modify an array in place, all return nil when
no changes are made to the array. This is annoying, as it prevents code like
this:
[e, l, e, m, e, n, t, s].uniq!.map! { |e| e.method }.compact!.each { |e| puts e }
If any call in the chain above returns nil, everything comes to a grinding
halt. What we’d like is for these methods to return self unconditionally.
Others have come up with solutions to this problem (like
this one,
using Object#tap), but if you don’t like the methods the way they are and you
don’t need to maintain the default behavior for anyone else, why not
just change them in place?
Back to Modifying Methods
Now, in order to do this, we could just override each method one-by-one, but that’s a pain. Instead, we need a way to append a teensy little bit of code to the end of each of these methods without losing the original method definitions in the process.
With the following few lines, we can achieve this and feed the typical Rubyist’s metaprogramming addiction in one fell loop.
class DeviantArray < Array
Array.instance_methods(false).select { |m| m =~ /!/ }.each do |m|
orig = instance_method m
send :define_method, m do |*args|
orig.bind(self).call *args
self
end
end
end
The first bit,
Array.instance_methods(false).select { |m| m =~ /!/ }.each do |m|
grabs a subset of Array’s instance methods with a “!” in the name and iterates over them. Inside the loop, things get a little less clear:
orig = instance_method m
Module#instance_method,
according to ruby-doc, “Returns an
UnboundMethod
representing the given instance method.” Put more plainly, it “snaps off”
a module’s instance method, disassociating it from any particular object and
letting us pass it around without having to refer to it
by name. This is helpful, since it allows us to hold onto the content of a method
even after using its name to define new one.
The next block performs this redefinition:
send :define_method, m do |*args|
orig.bind(self).call *args
self
end
We send our subclass a signal to redefine the method m using
define_method (we use
send
to work around the fact that define_method is private). The block provided
will be the new method content; inside, we rebind our detached method to the
calling object and… [drumroll…] return self.
Now, we can do the following without worrying about nil values:
[a, r, r, a, y].flatten!.compact!.reverse!.shuffle!.uniq!.sort!
A Simpler Example
Of course, this technique isn’t just useful for changing return values. Say you wanted to quickly add a callback to a set of methods — here’s a trivial example:
class String
[:chomp, :delete, :gsub, :squeeze, :strip, :sub].each do |m|
orig = instance_method m
send :define_method, m do |*args|
before = size
after = orig.bind(self).call *args
result before, after.size
return after
end
end
def result before, after
puts 'Removed %s characters.' % (before - after)
end
end
Now, we get some useful feedback when we call the following:
>> "aabbcc".squeeze
Removed 3 characters.
=> "abc"
>> " abc ".strip
Removed 2 characters.
=> "abc"
>> "abcxyz".delete "xyz"
Removed 3 characters.
=> "abc"
You probably shouldn’t do this kind of thing in a large codebase with a lot of different authors, but if you have the freedom to tinker this can be a powerful tool.