Ben d'état

Ben Scott

~/Replacing a method on an instance in Ruby

18 Dec 2010

I just read a post by David Tchepak where he describes a way of replacing a method on an instance of a class on the fly, allowing the replacement to close over locals. This would be useful for unit testing at the least. The technique that Dave uses is cool – this is a simplified version:

name = "Anonymous Dave"
greeter.extend do
  self.send(:define_method, :say_hello) do
    puts "G'day #{name}"

This is replacing the greeter.say_hello method with the closure on the fourth line. Since I’m spoiled by C#’s lambda syntax, I wanted to get this onto one line:

greeter.extend { self.send(:define_method, :say_hello) { puts "G'day #{name}" }}

Nice, but there is a bit of repetition. .extend is a method on the Object class, which extends the instance with the new module. Unfortunately Object doesn’t have a method to just replace a single method. The following opens the Object and moves much of the boilerplate code into a new method to do that:

class Object
  def redefine(name, &block)
    self.extend {
      self.send(:define_method, name, block)

redefine probably isn’t the best name, since there is no need for the named method to exist before redefining it. This is the new way to replace the say_hello method:

greeter.redefine(:say_hello) { puts "G'day #{name}" }

Methods with parameters can be defined in the same way:

greeter.redefine(:say_hello_to) { |another_name| puts "Hello to #{another_name}" }
greeter.say_hello_to 'Dave'

Very cool. Many thanks to Dave Tchepak for his article, otherwise I wouldn’t have thought this was possible at all.

comments powered by Disqus