Overview

In this post we will review how to use the method_missing method in Ruby together with some guidelines and best practices.

Do not use method_missing as the first option of solving a particular problem

First of all, it is not advised to overwrite the private method BasicObject#method_missing as the first option of solving a particular problem.

Like all metaprogramming solutions, use this technique only if it saves time or if you failed to come up with a simpler way to solve the problem.

Simple is better than complex. This is one of the main reasons this technique is not frequently applied in practice. If not properly implemented, we may end up in an infinite recursion.

So, what is method_missing used for?

It gives us one last possibility to cope with the missing method prior to raising the well known NoMethodError exception.

When a given object obj receives a nonexisting method call (message) (e.g. obj.hello), the interpreter starts crawling the ancestors chain in an attempt to find a suitable method to invoke. If Ruby fails to find a suitable method it raises NoMethodError.

We can modify this behaviour by overwriting BasicObject#method_missing somewhere in the ancestors chain and handle the missing method definitions in a custom way.

Introspection and method_missing

The intentions of using this technique is to treat (some of) the missing methods as ones that are already defined (e.g. having a certain method name pattern or some other rule).

Therefore, we need to also overwrite the BasicObject#respond_to_missing? method so introspection (e.g. with respond_to?, method, etc.) works as expected.

Example using method_missing

DISCLAIMER: This is a code-smell and one should consider using a simpler approach to such solutions.

Imagine that you want to have your Foo class to handle all methods starting with check_ in such a way that a check to an external service is made.

  foo_number = '1234567890'
  my_foo = Foo.new(foo_number)

  my_foo.check_foo_name
  my_foo.check_address
  my_foo.check_history
  my_foo.check_owners

  # =>
  # call an external service to get the data
  # for the methods prefixed with `check_`

Here is a simple example implementation of the Foo class:

  class Foo
    attr_reader :foo_number

    def initialize(foo_number)
      @foo_number = foo_number
    end

    def method_missing(method_name, *args, &block)
      if method_name.to_s.split('_')[0] == 'check'
        puts "[#{foo_number}][#{method_name}] Connecting to the server.."
        # TODO: return the result of the check here
      else
        super
      end
    end

    def respond_to_missing?(method_name, *args, &block)
      method_name.to_s.split('_')[0] == 'check' || super
    end
  end

...and now some method invocations:

  my_foo = Foo.new('1234567890')
  puts my_foo.respond_to?(:check_foo_name) # => true

  my_foo.check_foo_name
  # => [1234567890][check_foo_name] Connecting to the server..

  check_history = my_foo.method(:check_history)
  check_history.call
  # => [1234567890][check_history] Connecting to the server..

Conclusion

  • Overwrite BasicObject#method_missing only if you failed to come up with a simpler solution.
  • Always overwrite BasicObject#respond_to_missing? when defining method_missing method.
  • One should be extremely cautious when using this technique as there is a risk to end up in an infinite recursion.
  • Consider using super in the definitions of the two methods.

References