Replacing methods
Let's say we want to replace a method instead of just throwing it away. But we want the new method to be able to call the old implementation.
The standard way of doing this is by aliasing the old method and making the new one call the aliased version:
class Foo
def method
...
end
...
class Foo
alias :method :old_method
def method
...
old_method
end
end
This solution works perfectly for most situations, but what if we want to remove all traces of the old method?
Here's a simple trick to do exactly that:
class Foo
old_method = instance_method(:method)
define_method(:method) { |*args|
# do something here ...
old_method.bind(self).call(*args)
}
end
It shouldn't take much to understand how it works, but let's analyze it in more detail. The first line gets a hold of the old implementation of method. instance_method returns an UnboundMethod object that wraps it.
Next, we redefine method and pass it a block with the new implementation. One thing worth mentioning is that the old_method variable inside the block is bound to the variable assigned to outside of the block. The binding is maintained even after the execution reaches the end of the class definition. As a result, the new method has a direct reference to the old one... and nobody else does.
After doing whatever it has to do, the new method calls the old one after binding the UnboundMethod to the current object (i.e., the object on which method was invoked).
If you have a keen eye, you'll notice that there's something special about self. At first glance, it looks like a standard variable that just happens to hold a reference to the current object. If that were true, it would follow that the reference to self inside the block would be bound to the self representing class Foo. That is not the case, though. The binding of self is established when the block gets called.
The standard way of doing this is by aliasing the old method and making the new one call the aliased version:
class Foo
def method
...
end
...
class Foo
alias :method :old_method
def method
...
old_method
end
end
This solution works perfectly for most situations, but what if we want to remove all traces of the old method?
Here's a simple trick to do exactly that:
class Foo
old_method = instance_method(:method)
define_method(:method) { |*args|
# do something here ...
old_method.bind(self).call(*args)
}
end
It shouldn't take much to understand how it works, but let's analyze it in more detail. The first line gets a hold of the old implementation of method. instance_method returns an UnboundMethod object that wraps it.
Next, we redefine method and pass it a block with the new implementation. One thing worth mentioning is that the old_method variable inside the block is bound to the variable assigned to outside of the block. The binding is maintained even after the execution reaches the end of the class definition. As a result, the new method has a direct reference to the old one... and nobody else does.
After doing whatever it has to do, the new method calls the old one after binding the UnboundMethod to the current object (i.e., the object on which method was invoked).
If you have a keen eye, you'll notice that there's something special about self. At first glance, it looks like a standard variable that just happens to hold a reference to the current object. If that were true, it would follow that the reference to self inside the block would be bound to the self representing class Foo. That is not the case, though. The binding of self is established when the block gets called.
Labels: ruby
3 Comments:
thanks for the insight... I stopped by to learn about conditional assignment, and left with unbound methods!
How do you put old_method back as if you'd never touched it?
Tim I tried to do exactly that some months ago, check this: http://dcadenas.blogspot.com/2007/11/bit-of-metaprogramming.html
Post a Comment
<< Home