I’m pretty sure that you have heard lots about ruby, specially as being a dynamic language, you can create methods on the fly, add instance variables, define constants and invoke existing methods dynamically , and that’s what this post is all about :
As you know in ruby you can call a public instance method directly ,ex :
s= "hi man" p s.length #=> 6 p s.include? "hi" #=> true
One way to invoke a method dynamically in ruby is to send a message to the object :
p s.send(:length) #=> 6 p s.send(:include?,"hi") #=> true
A second way is instantiate a method object and then call it:
method_object = s.method(:length) p method_object.call #=> 6 method_object = s.method(:include?) p method_object.call('hi') #=> true
And the third way is to use the eval method:
eval "s.length" #=> 6 eval "s.include? 'hi'" #=>true
Well, when to use what?
look at this script, it will be used to benchmark the 3 ways of calling :
require "benchmark" test = "hi man" m = test.method(:length) n = 100000 Benchmark.bmbm {|x| x.report("call") { n.times { m.call } } x.report("send") { n.times { test.send(:length) } } x.report("eval") { n.times { eval "test.length" } } } ####################################### ##### The results ####################################### #Rehearsal ---------------------------------------- #call 0.050000 0.020000 0.070000 ( 0.077915) #send 0.080000 0.000000 0.080000 ( 0.086071) #eval 0.360000 0.040000 0.400000 ( 0.405647) #------------------------------- total: 0.550000sec # user system total real #call 0.050000 0.020000 0.070000 ( 0.072041) #send 0.070000 0.000000 0.070000 ( 0.077674) #eval 0.370000 0.020000 0.390000 ( 0.399442)
Well as you can see, instantiating a method object is the fastest dynamic way in calling a method, also notice how slow using eval is.
Also when sending a message to an object , or when instantiating a method object , u can call private methods of that object :
class Foo private def hi puts "hi man" end end # Normal method calling f = Foo.new #=> <Foo:0x10a0d51> f.hi #=>NoMethodError: private method `hi' called for #<Foo:0x10a0d51> # Sending a message f.send :hi # hi man # Instantiating a method object f.method(:hi).call # hi man # Using eval eval "f.hi" #=>NoMethodError: private method `hi' called for #<Foo:0x10a0d51> # Using instance_eval f.instance_eval {hi} # hi man
moski doski | December 31st, 2008 at 6:39 am #
i was working on something similar today. i wanted to generate the method name on the fly , i was using eval, but turned out that passing a value to the auto-generated eval method is complicated.
when the user request /api/reset?method=video.list, i auto generate the api method name, for example API::Video::list(user) …. which was easy, but sending the object user was tough Until i found “send” … now all i do self.send(“API::Video::list”,user) and everything works like a charm.
great tutorial man, keep it up
Ruby and Metaprogramming - Khaled alHabache’s official blog | December 31st, 2008 at 7:25 am #
[...] Ruby dynamic method calling [...]
Kris | January 2nd, 2009 at 8:55 pm #
Nice tutorial. Is there a one to one correspondence between a method name and the symbol identified by the method?
khelll | January 2nd, 2009 at 11:55 pm #
@kris, i didn’t get you mean really by “the symbol identified by the method”, can you explain more?
Kris | January 3rd, 2009 at 2:20 pm #
May be I should say “Method identified by Symbol”? When you write s.send(:length), how does the runtime know that what I intend to do is call a method called “length()” on the object s unless the method “length()” is internally stored with a symbol :length. Is my assumption right? What if I said s.send(:test) instead and there is a module method (not part of String class) called “test()”?
I am new to Ruby, hence my curiosity and the question.
khelll | January 3rd, 2009 at 11:57 pm #
@Kris, ruby interpreter has a Symbol table that stores class names, method names, class,instance and global variables.
The second part of your question is related to ruby’s method lookup mechanism, please have a look here on point 7.8, and if you couldn’t get it, tell me back so that i explain it in a separated blog post.
khelll | January 4th, 2009 at 9:45 am #
@Kris, if you want to explore more also on symbols you can do this: Symbol.all_symbols.size , and then try to define any new method, and execute that instruction again, you will notice that the number increased by one.
Kris | January 4th, 2009 at 10:29 am #
Thanks Khaled. I am going to try out what you described. Appreciate your patience and help. Will keep an eye on your blog for any future articles on ruby and “symbols” related articles. It is a bit difficult to wrap my head around symbols coming from C++/Java background but I am working towards it. I would like to see a blog post on how symbols enable programming productivity constructs that might otherwise not be possible without them.
vin | April 23rd, 2009 at 2:29 am #
just came across this website from google
this helps me a lot
many thanks
Rafa1970 | February 18th, 2010 at 1:10 am #
Very useful post.