From: Ken Bloom Date: 2006-09-09T00:10:17+09:00 Subject: Re: [CHALLENGE] better alias_method On Fri, 08 Sep 2006 14:50:22 +0000, Ken Bloom wrote: > On Fri, 08 Sep 2006 14:34:39 +0900, ara.t.howard wrote: > >> i've been wanting a better alias_method for quite some time. essentially i'd >> like a way out of the trap where executing >> >> alias_method '__fubar__', 'fubar' >> >> goes haywire when __fubar__ already exists. we've all seen it happen before. >> >> anyhow. the interface it'd like would be > > It would be nice to have this built in to the language -- a way to blur > the difference between overriding an inherited method and overriding a > method defined on the current class, so that `super' would call whatever > the previous version of the was. > > Try this idea on for size. I wrote it a few days ago. Look at the > unit tests at the end to see how it's intended to be used. I think a > little syntactic sugar would be nice, but it gets the job done. > > class Module > private > def override(method_name,&block) > begin > #case 1: I'm overriding a method defined on this object > old_method=instance_method(method_name) > alias_for_old=unused_alias > alias_method alias_for_old, method_name > rescue NameError > begin > #case 2: I'm overriding a method that I inherit from > old_method=superclass.instance_method(method_name) > rescue NameError > #case 3: I'm not overriding anything. Just use a simple > #function that we can call without side effects > old_method=Object.instance_method(:nil?) > end > end > define_method(method_name) do |*args| > block.call(old_method.bind(self),*args) > end > end > def unused_alias > newalias=nil > while newalias==nil or instance_methods.include?(newalias) > newalias=:"__kenoverride__#{rand(10**20)}__" > end > newalias > end > end > > if __FILE__==$0 > require 'test/unit' > class TestOverride < Test::Unit::TestCase > def setup > eval <<-"end;" > class A > def foo; "a"; end > end > end; > end > > > def test_alias > assert_nothing_raised do > eval <<-"end;" > class A > override(:foo){|old| old.call+"b"} > end > end; > end > > assert_equal A.new.foo,"ab" > > end > > def test_inherit > assert_nothing_raised do > eval <<-"end;" > class B override(:foo){|old| "c"+old.call } > end > end; > end > > #I don't want to rely on whether test_inherit or test_alias > #comes first, so I'll test for the results of both orders. > assert ["ca","cab"].include?(B.new.foo) > end > > #I'm not really sure whether this is the appropriate behavior > #or whether throwing an exception is more appropriate. > def test_neither > assert_nothing_raised do > eval <<-"end;" > class C > override(:foo){|old| "d" } > end > end; > end > > assert_equal C.new.foo,"d" > end > end > end > > One more unit test that this passes. (This one seems to go to the heart of the issue you guys are having.) def test_longchain assert_nothing_raised do eval 'class D; def foo; "a"; end; end' end assert_equal "a", D.new.foo 10.times do |n| assert_nothing_raised do eval 'class D; override :foo do |old| old.call+"a"; end; end' end assert_equal "a"*(n+2), D.new.foo end end -- Ken Bloom. PhD candidate. Linguistic Cognition Laboratory. Department of Computer Science. Illinois Institute of Technology. http://www.iit.edu/~kbloom1/