Today I was looking for the best way to invoke dynamic class method. After some reading and testing I found a few less and more effective ways. Let’s start with a simple `Person` class with one public class method:
class Person class << self def name name puts name end end end
The easiest and the most natural way is directly invoking `name` method with the required parameter:
Person.name("John")
Above solution is the best as long as the method name is known, but my requirement was to invoke dynamic method, more precisely by interpolating the method name.
a 1. solution: `eval` (the worst one)
`eval` method is a part of `Kernel` module and it evaluates the Ruby expression in string:
2.2.2 :005 > eval "puts 'Hello World'" Hello World => nil 2.2.2 :006 > eval '2+2' => 4
So to invoke `name` method of `Person` class we can type:
eval "Person.name('John')"
or with using interpolation:
dynamic_method_name = 'name' eval "Person.#{ dynamic_method_name }('John')"
It is possible to achieve the desirable result, however using `eval` is reasonable only in specific cases and I do not recommend using it at all.
2. solution: method call
Person.method("name").call("Igor") # with interpolation dynamic_method_name = 'name' Person.method("#{ dynamic_method_name }").call('John')
3. solution: `send` method
According to documentation of Ruby `Object` class `send` method
invokes the method identified by symbol, passing it any arguments specified.
dynamic_method_name = 'name' Person.send(dynamic_method_name, 'John')
4. solution `public_send` method (the most reasonable one)
This one is very similar to the `send` one, but it calls public methods only. That is exactly what I wanted – invoke public method.
dynamic_method_name = 'name' Person.public_send(dynamic_method_name, 'John')
Benchmarking
As you just have seen there are at least four different ways to invoke dynamic class method. The last question for today is: which one is the fastest? I did a quick benchmarking test:
Benchmark.bm(4) do |b| b.report('name:') { 1_000_000.times { Person.name('John') } } b.report('eval:') { 1_000_000.times { eval "Person.name('John')" } } b.report('method:') { 1_000_000.times { Person.method('name').call('John') } } b.report('send') { 1_000_000.times { Person.send('name', 'John') } } b.report('public_send') { 1_000_000.times { Person.public_send('name', 'John') } } end
And the winner is..
user system total real name: 0.140000 0.000000 0.140000 (0.146393) eval: 7.350000 0.200000 7.550000 (8.113254) method: 1.130000 0.020000 1.150000 (1.225879) send 0.350000 0.010000 0.360000 (0.371235) public_send 0.420000 0.010000 0.430000 (0.445576)
..`send` method!
Quick summary
As I wrote `eval` should not be used in this case at all (not only because it is the slowest one). Even though `public_send` is a bit slower than the winner I recommend using it if you need to invoke dynamic public class method.
Do you know any other solutions to the above problem?