-3

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
akostadinov
  • 17,364
  • 6
  • 77
  • 85
  • I'm confused - why can't you call __send__ ? – Frederick Cheung Jun 27 '16 at 21:09
  • Stupid formatting - I meant `__send__` – Frederick Cheung Jun 27 '16 at 21:14
  • @FrederickCheung, because I never knew there is a `#__send__` method. I only knew `#send`. I guess that explains the negative question rating.. ruby really bad documentation, anyway, thank you – akostadinov Jun 28 '16 at 20:04
  • Ruby has amazing documentation, you just have to actually look. – 13aal Jul 03 '16 at 22:45
  • @13aal, I guess you never looked at documentation of other languages like java for example. Ruby is the lang that for me has the least clearly defined behavior in documentation compared to other languages I've used. Fortunately it's vast popularity makes most topics well covered in blogs and forums. btw looking at number of question I've asked on the site, it would appear I'm actually very smart having only a few questions... provided your assertion was correct and I don't do research prior asking. – akostadinov Jul 04 '16 at 13:24
  • @akostadinov what does your intelligence or lack of have anything to Dow with asking this question? – 13aal Jul 04 '16 at 15:49

1 Answers1

2

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
philomory
  • 1,709
  • 12
  • 13
  • hmm, never knew there is a `#__send__` method out of the box. Always used the `#send` method which does the same. my bad! – akostadinov Jun 28 '16 at 20:00
  • FYI I don't see `BasicObject.instance_method(:__send__)` working on ruby 2.2, would be useful to fix if you can. Update to answer works though so marking as solution. – akostadinov Jun 28 '16 at 20:05
  • I just tried it on 2.2.2 and it worked fine for me. What error are you seeing? Note that the first methods I wrote don't give you an actually method called `send` or `__send__` back; they are what you do _instead_ of directly writing `obj.send` or `obj.__send__`. – philomory Jun 29 '16 at 00:13
  • I think I maybe got a typo before. It is indeed working. I still don't see how to revert a method into the class itself as an instance method though. I have my immediate problem solves as I say above but interesting to know how to actually restore the method. – akostadinov Jun 29 '16 at 10:15
  • Huh, got it, will update my question. Thanks a lot, very useful. – akostadinov Jun 29 '16 at 10:17
  • 1
    Glad to help. Note that the method you added to your question, using `define_method`, will not work if the class you are working with has undef-ed `define_method` (though, usually this will not be a problem, as people usually only `undef` instance methods, not class methods). That said, the method I showed (using `def` instead of `define_method`) will _always_ work (because def is a language keyword and cannot be overruled), but it does come with a small performance penalty (not because `define_method` is faster, but because my version calls `instance_method`, `bind` and `call` on every call). – philomory Jun 29 '16 at 23:14