using external API that has defined as special class where almost all standard methods are undefined to use for building xml. Where #method_missing
is responsible to generate elements based on the missing method name called over the object.
Basically in class body there is something to the effect of:
undef_method :send
Now I want to programatically call method by name. I guess I can use eval "obj.#{something}"
but I really don't like eval
.
I was thinking there must be some dark-side technique to revert the undefining of method #send
so I can alias it to #__send__
and undefine it again. That way I can happily call methods by name without eval. e.g. obj.__send__(:something, params)
.
So my question is how do I use monkey patching to revert the #send
method. I can't find anything to that effect. Even can't find anybody asking about it either.
UPDATE: My original problem was non-problem because there is a method #__send__
anyway, I knew only about #send
. The other part of the question was how to restore an undefined method. With help of @philomory asnwer here's what I've got:
[46] pry(#<CucuShift::DefaultWorld>)> class A
[46] pry(#<CucuShift::DefaultWorld>)* def gah
[46] pry(#<CucuShift::DefaultWorld>)* puts "gah"
[46] pry(#<CucuShift::DefaultWorld>)* end
[46] pry(#<CucuShift::DefaultWorld>)* end
=> :gah
[49] pry(#<CucuShift::DefaultWorld>)> class C < A
[49] pry(#<CucuShift::DefaultWorld>)* undef_method :gah
[49] pry(#<CucuShift::DefaultWorld>)* end
=> C
[50] pry(#<CucuShift::DefaultWorld>)> C.new.gah
NoMethodError: undefined method `gah' for #<C:0x000000070d7918>
[57] pry(#<CucuShift::DefaultWorld>)> class C
[57] pry(#<CucuShift::DefaultWorld>)* define_method :fff , A.instance_method(:gah)
[57] pry(#<CucuShift::DefaultWorld>)* end
=> :fff
[58] pry(#<CucuShift::DefaultWorld>)> C.new.fff
gah
The dark magic you are looking for is the UnboundMethod class. Use it like this (assuming obj
is your builder API object):
send_method = BasicObject.instance_method(:__send__)
bound_method = send_method.bind(obj)
bound_method.call(:method_you_are_calling_dynamically,*arguments)
Of course, depending on what you're trying to accomplish, you could just get the specific method you want and bind it directly, like
id_method = BasicObject.intance_method(:__id__)
bound_method = id_method.bind(obj)
bound_method.call
Or, if the builder class is using method_missing
and you just want to dynamically dispatch to that, you don't need send
or anything like it at all, just call obj.method_missing(:my_dynamic_method_name)
UPDATE:
It's worth noting that you should not need to alias send
to __send__
as __send__
is already provided as part of BasicObject and it is very unusual to un-define it. Though I suppose if you are using an earlier version of Ruby without BasicObject and __send__
you do it this way:
def obj.__send__(*args,&blk)
Object.instance_method(:send).bind(self).call(*args,&blk)
end