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.

 

Leave a Reply

Your email address will not be published. Required fields are marked *