I was recently playing around with replacing methods on specific Ruby instances. Everything is open for change in Ruby so I thought it would be a piece of cake. And in a way it was. The catch was that I wanted the method definition to close over some local variables I had declared (the same way () => localVar;
will close over localVar
for .NET lambdas), and this proved to be a little trickier than I expected.
Basic instance method replacement
Say we have an incredibly exciting Greeter
class that says hello:
class Greeter def say_hello puts "Hello World!" end end greeter = Greeter.new greeter.say_hello #=> Hello World!
We can easily re-define this method on a specific instance like this:
def greeter.say_hello puts "Howdy World!" end greeter.say_hello #=> Howdy World!
Greeter
by opening up the class as Derick points out in the comments. In this case I just want to replace the method on the specific greeter
instance, not on all instances of the Greeter
class.Now say that rather than hard-coding a greeting in our re-defined method, we want to reference a local variable instead (useful for replacing methods for unit tests).
name = "Anonymous Dave" def greeter.say_hello puts "Hello #{name}" end #This won't work: # greeter.say_hello #It gives this exception: # in `say_hello': undefined local variable or method `name' for #<Greeter:0x337e1e0> (NameError)
This doesn’t work because when we get to def greeter.say_hello
we’ve changed the current scope to the greeter
instance, and greeter
knows nothing about name
; it does not exist in the scope say_hello
is running in. What we really want is to capture the name
variable via a closure when we define the method, and in Ruby, we can do this with blocks and Procs.
A detour into Ruby internals and other stuff I don’t understand
I’m completely out of my depth here, but here is my Ruby n00bie interpretation of some Ruby mumbo jumbo. First, everything in Ruby is an object, and every object has a class (as near as I can tell). Even nil
(Ruby’s null
):
puts nil.class #=> NilClass puts nil.methods.count #=> 71
If you’re from a .NET background, we’ve just called null.GetType().GetMethods().Length
and lived to tell the tale (and been told 71 apparently :)).
Anyway, the point is that everything is an object and they all have classes. Our greeter
object is an instance of the Greeter
class we defined earlier. Ruby has all kinds of interesting ways of interacting with instances and classes, including instance_eval
and class_eval
which let us run code within the context, or scope, of both instances and classes. Unfortunately none of these seem to work the scope in a way that will let us add an instance method to a specific instance using a closure (please let me know if I’m wrong).
There is another option: Ruby metaclasses (a.k.a. eigenclasses). It turns out that when I said that greeter
’s class is Greeter
, I sort of lied. In fact, Ruby itself will dissemble slightly when you run greeter.class
and it says Greeter
. Every object has a class, but also a metaclass. And a metaclass’ class has Class, as well as it’s own metaclass, which I guess is a meta-metaclass. And so on into madness.
In a valiant attempt to get back on topic, the idea is that you can add a method to a metaclass that will only be available on the instance. If you modify a method on the class then it will appear on all instances.
The next thing we need to know is that everything that inherits from Class
has a very useful method called define_method
which defines a method from a block. Unfortunately this method is private. But luckily you can invoke private methods in Ruby using the send
method. The send method is also cursed and contains potassium benzoate… kidding.
Believe it or not, we can now solve the original problem.
Instance method replacement with closures
So what we need to do is get access to the greeter
instance metaclass, and send
a define_method
call with a block that closes over our local variable. We can access an instance’s metaclass by using the class << instance syntax, which will change the current scope to instance
’s metaclass (so self
will refer to the metaclass. This is confusing; see Yehuda’s article for a great explanation). And the rest is fairly straight forward:
greeters_metaclass = class << greeter; self; end greeters_metaclass.send(:define_method, :say_hello) do puts "G'day #{name}" end greeter.say_hello #=> G'day Anonymous Dave name = "closure" greeter.say_hello #=> G'day closure
We now have all the pieces put together. We’ve got our paws on a reference to the instance’s metaclass, and we’ve defined a class using a block (do..end
) which will close over our local variable using the metaclass’ private define_method
via send
. Piece of cake, right? :)
Parting thoughts
This tripped me up for a while, and this post is my attempt to try and decipher what is happening. It’s not quite as bad as I make it sound, it just requires some understanding about Ruby’s class system and closures. Along the way I found these absolutely essential references that will hopefully clear things up a bit for you:
- Yehuda Katz has an absolutely fantastic post on scope changing during metaprogramming, including explaining metaclasses and what
self
means. - Dhananjay Nene’s examples of metaprogramming for both Ruby and Python
Next post we’ll look at a hack to make this a bit easier.