This post originated from an RSS feed registered with Ruby Buzz
by Daniel Berger.
Original Post: Monkey patching and aliases
Feed Title: Testing 1,2,3...
Feed URL: http://djberg96.livejournal.com/data/rss
Feed Description: A blog on Ruby and other stuff.
Although Ruby's open classes are a powerful feature, there is one (well, at least one) gotcha to watch out for - aliases. You see, aliases don't survive method redefinition. Consider the following code:
class Foo
def bar
puts "hello"
end
alias baz bar
end
Foo.new.bar => 'hello'
Foo.new.baz => 'hello'
Ok, works as expected. Now, let's reopen the class and redefine the 'bar' method:
class Foo
def bar
puts "goodbye"
end
end
Foo.new.bar => 'goodbye'
Foo.new.baz => 'hello'
Whoops! Our alias doesn't survive the monkey patching!
Is it possible to have an alias survive monkey patching? Not really. The main issue is something I brought up on ruby-talk:196740 - there just isn't an easy way to determine a list of aliases for a given class because Ruby doesn't store that data internally. Ryan Davis did post a potential solution using RubyInline that was quite impressive but quite ugly.
What if we patch Ruby so that classes *did* store that sort of information? Perhaps in Object. Then we could use Module#method_added for something like this:
class Foo
def method_added(id)
if self.aliased_methods.include?(id.id2name)
self.aliased_methods.each{ |a|
alias a id.id2name if a == id.id2name
}
end
end
end
With that in place whenever you redefined an existing method the aliases for that method would be remapped automatically.
On the other hand, maybe you don't want that behavior. In fact, you could argue that the alias provides you with a handy way to get back at the original behavior if and when you want to:
class Foo
def bar
baz # Execute the orignal bar method
... # Extra stuff for our custom bar method
end
end
With that in mind, we could abstract this by altering the 'alias' keyword to accept an optional third argument. The third argument would let us declare whether or not an alias should automatically remap itself whenever its aliased method was monkey patched:
class Foo
def test
puts "this is a test'
end
alias test2 test true # Remaps aliases when monkey patched
alias test3 test false # Does not remap aliases
end
Foo.new.test => 'this is a test'
Foo.new.test2 => 'this is a test'
Foo.new.test3 => 'this is a test'
class Foo
def test
puts "something changed"
end
end
Foo.new.test => 'something changed'
Foo.new.test2 => 'something changed'
Foo.new.test3 => 'this is a test'
On a side note, maybe this could be better accomplished with annotations somehow.