Ruby’s dynamism is awe-inspiring. One of the methods that contribute to this is method_missingmethod_missing is defined in BasicObjectmethod_missing allows us to:

  • handle errors
  • delegate methods
  • build DSLs

Let’s discuss the practical uses of this method.

What is Ruby’s method_missing?

We define methods inside classes. Ruby traverses a method lookup path when an object calls a method, starting from the object’s class and up the object’s class’s ancestor chain to reach the method. If the method the object calls is available in the lookup path, Ruby calls it. On some occasions, the method isn’t present in the ancestor chain.

All method lookups that fail end up reaching a method called method_missingmethod_missing lives inside the BasicObject class, the root of all classes.

For a class, Aircraft, that mixes in no other modules or prepends other module or inherit other classes, this is how the traversal would look like (I’ve omitted the lookup through eigenclasses for simplicity):

Ruby method_missing lookup path.

method_missing For Error Handling

Perhaps the most common use of method_missing is its support for gracefully handling errors. When an object invokes a method, Ruby navigates the method lookup chain looking for the method to execute. On reaching BasicObjectmethod_missing is called. The interpreter raises an error at this point.

Here’s an example:

class Aircraft
attr_accessor :passengers

    def initialize
      @passengers = 0
    end

    def fly; end

    def park; end

end

aircraft = Aircraft.new
aircraft.drift

Here’s what happens when you call an inexistent method on an object: When the search for a #drift method in the inheritance tree ends in vain, then the method call aircraft.drift will output:

 <main>: undefined method 'drift' for
 #<Aircraft:0x00007fe66c1591e0 @passengers=0> (NoMethodError)

It would be nice to be able to handle this error in a friendlier way. Ruby permits this by making programmers override method_missingmethod_missing takes three parameters; a method name, its list of arguments and a block. The last two are optional.

To handle these errors, you intercept the call to BasicObject’s method_missing with your own. Here’s a rewrite of our Aircraft code:

class Aircraft
attr_accessor :passengers

def initialize
@passengers = 0
end

def method_missing(method_name, \*args)
message = "You called #{method_name} with #{args}. This method doesn't exist."

    raise NoMethodError, message

end
end

aircraft = Aircraft.new
aircraft.add_passengers(275)

With this change, we now have as output:

'method_missing': You called add_passengers with [275].
This method doesn't exist. (NoMethodError)

This looks more welcoming than the error we saw before. This example shows the extent to which Ruby allows programmers to own their code. A slightly more sophisticated example may involve the use of super where if someone calls an inexistent method, you’d want first to perform some operation on the method and then only pass control to the original method_missing method as shown in this example from Ruby’s documentation:

class Roman
def roman_to_int(str) # ...
end

    def method_missing(symbol, *args)
      str = symbol.id2name
      begin
        roman_to_int(str)
      rescue
        super(symbol, *args)
      end
    end

end

r = Roman.new

r.iv #=> 4
r.xxiii #=> 23
r.mm #=> 2000
r.foo #=> NoMethodError

With more complicated usages of method_missing, come more significant responsibilities. When you override method_missing, you have to make sure that whatever method you call inside it is available. Otherwise, you’ll end up going in circles and eventually get a SystemStackError.

Expanding on the Aircraft class, here’s something more meaningful. If a method call on an Aircraft instance starts with “add”, we call public_send on the instance passing it a setter method that sets the variable of whatever comes after “add”.

class Aircraft
attr_accessor :passengers

    def initialize
      @passengers = 0
    end

    def method_missing(method_name, *args, &block)
      if method_name =~ /add_(.*)/
        public_send("#{Regexp.last_match(1)}=", *args)
      else
        super
      end
    end

end

aircraft = Aircraft.new
aircraft.add_passengers(275)

p aircraft.passengers #=> 275
p aircraft.respond_to?(:add_passengers) #=> false

In the method_missing method, if the method called on any instance of Aircraft matches “add_”, we call a setter method and pass it the arguments that came with the method call.

However, it would help if you took note that respond_to “add_passengers” returns false, this is not true, and that’s why it’s good practice always to override respond_to_missing? method anytime you override method_missing.

In our case, we add:

def respond*to_missing?(method_name, include_private = false)
method_name =~ /add*(.\*)/ || super
end
class Aircraft
attr_accessor :passengers

    def initialize
      @passengers = 0
    end

    def method_missing(method_name, *args, &block)
      if method_name =~ /add_(.*)/
        public_send("#{Regexp.last_match(1)}=", *args)
      else
        super
      end
    end

    def respond_to_missing?(method_name, include_private = false)
      method_name =~ /add_(.*)/ || super
    end

end

aircraft = Aircraft.new
aircraft.add_passengers(275)

p aircraft.passengers #=> 275
p aircraft.respond_to?(:add_passengers) #=> true

Method Delegation With method_missing

According to Wikipedia, delegation refers to evaluating a member (property or method) of one object (the receiver) in the context of another original object (the sender), this means, an object is asking another object to perform the actions in the method the receiver is calling.

You can use Ruby’s method_missing to delegate methods to another class.

Here’s a contrived example:

class Referee
def red_card?
puts "true"
end
end

class Linesman
def initialize(referee)
@referee = referee
end

    def method_missing(method, *args)
      @referee.send(method)
    end

end

linesman = Linesman.new(Referee.new)

linesman.red_card? # => true

In the code above we call red_card? on linesman, an instance of Linesman, but it’s an instance of Referee that does the job, this is an example of some form of delegation.

Ruby provides a library called delegate in its Standard Library that you might want to check out first if you need to do serious method delegation,

Building DSLs And Libraries With method_missing

domain-specific language (DSL) is a language with which you can do specific tasks. Unlike Ruby, a DSL like RSpec only useful for testing.

DSLs are relatively easier to write. DSLs are everywhere. A few examples are:

  • Capistrano
  • Rake
  • Sinatra
  • Rails Routing
  • factory_bot

Ruby’s method_missing makes building DSLs accessible. Let’s try our shot with the simplest XML generator. Here’s a modified snippet I grabbed from The Ruby Programming Language book.

class XML
class XML
def initialize(output)
@output = output
end

    def method_missing(tag, attributes = {})
      @output << "<#{tag}"

      if block_given?
        @output << '>'
        content = yield

        @output << content.to_s if content

        @output << "</#{tag}>"
      else
        @output << '/>'
      end
      nil
    end

    def self.generate(output, &block)
      XML.new(output).instance_eval(&block)
    end

end

This code allows us to write something like:

XML.generate(STDOUT) do
html do
head do
title "Page Title"
end
body do
strong do
"Strong Text"
end
end
end
end

Running this produces:

  <html><head><title/></head><body><strong>Strong Text</strong></body></html>

And just like that, we’ve harnessed the power of method_missing to create a DSL that generates XML.

An excellent example of a DSL that relies on method_missing is The Late Jim Weirich’s Builder gem. The Builder gem generates XML markup from blocks, much like what we just wrote but more sophisticated.

Another example that might seem familiar is Ruby on Rails’s dependence on method_missing to create dynamic finders like the find_by_* group of methods in ActiveRecord.

Question On The Traversal For method_missing

I learned yesterday from a group that Ruby does the traversal for method_missing twice. The first one is to reach the method_missing in BasicObject, and the second traversal is to check if you overrode method_missing: this makes sense, otherwise, how would Ruby pick up the method_missing you’ve overwritten.

I haven’t found any reference on this. Please share if you have some documentation somewhere about this fact.

Summary

We’ve seen how to use method_missing to handle errors and to do method delegation. With the help of another metaprogramming construct, we saw how to use instance_eval to write a DSL that generates XML. We looked at how Ruby traverses the ancestor chain to find methods and calls method_missing if it finds nothing. We learned that it’s a good idea to overwrite respond_to_missing? any time we overwrite method_missing?; this covers the list of things you can do with method_missing. If you can think of any other use cases, please share them in the comments below.

Last Update: January 09, 2024