akostadinov akostadinov - 5 months ago 8
Ruby Question

calling method that was undefined in ruby

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

Answer

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
Comments