Sunday, December 24, 2006

Ruby: Alias method alternative

Originally blogged by Martin @ split-s. I'm reposting because I don't see this method used very often.

Using Ruby's alias it is possible to reopen a class, override a method and still call the original.
class ClockRadio
def on!
@on = true
end

def on?
@on
end
end

class ClockRadio
alias :old_on! :on!

def on!
old_on!
@display_time = true
end

def display_time?
@display_time
end
end
While this works, it can cause unexpected results and leave around artifacts. In isolation this doesn't look risky; however, in a large codebase someone could easily define old_on! or use it as their alias name also. The other, much smaller, issue is that old_on! will be left as a method on ClockRadio when you actually have no desire to expose this method outside of calling it from on!.

An alternative is to capture the on! method as an unbound method, bind it to the current instance, and call it explicitly.
class ClockRadio
on = self.instance_method(:on!)

define_method(:on!) do
on.bind(self).call
@display_time = true
end

def display_time?
@display_time
end
end
The above version ensures that the correct version of on! will be called from the on! implementation defined in the reopened version ClockRadio. This version also lets the reference to the old_on! fall out of scope after the class is defined; therefore, there are no additional methods left around as side effects.

Below are the tests that prove the concept.
class AliasMethodAlternativeTest < Test::Unit::TestCase

def test_aliased_method_returns_true_for_on
radio = ClockRadio.new
radio.on!
assert_equal true, radio.on?
end

def test_aliased_method_returns_true_for_display_time
radio = ClockRadio.new
radio.on!
assert_equal true, radio.display_time?
end

end
Post a Comment