Dynamic class method calling in Ruby

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?

 

Igor Springer

I build web apps. From time to time I put my thoughts on paper. I hope that some of them will be valuable for you. To teach is to learn twice.