Home

About

Archives

Creations

Dynamically Modifying Methods in Ruby

2010/04/24

Or, 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.