akostadinov akostadinov - 1 year ago 64
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

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
I was thinking there must be some dark-side technique to revert the undefining of method
so I can alias it to
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
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
anyway, I knew only about
. 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

Answer Source

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)

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)

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)


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)
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download