#---method_missing---

When you call a method on an object, Ruby will search for it in a bunch of places before finally throwing in the towel and calling the aptly named method: method_missing.

method_missing is a private method defined in the top-level Basic_Object class. When ruby calls it, it simply raises a NoMethodError exception to let you, the user, know that the method you called does not exist.

class Dog
end
d = Dog.new
## raises NoMethodError without rescue nil
p d.do_you_speak_english? rescue nil

You can get the exact same effect by calling method_missing directly on the object:

p d.send(:method_missing, :do_you_speak_english?) \
  rescue nil

(We have to use send because method_missing is a private method.)

How method_missing works

What makes it special are 2 things:

  • It’s signature. You call it by passing the name of the method, the arguments that were passed to it, and any block that was passed to it.
  • It can be overridden in your own classes to make it do something, instead of just raising the NoMethodError.
class Dog
  def method_missing(method_name, *args, &blk)
    res = "You called method: #{method_name}\n"
    res += "with args: #{args.join(',')}\n" if args.any?
    res += "with a block whose result is: #{yield}" \
      if block_given?
    res
  end
end
d = Dog.new
puts d.do_you_speak_english?('yes', 'no') { 'maybe' }
## => "You called method: do_you_speak_english?"
## "with args: yes,no"
## "with a block whose result is: maybe"
p d.say_something!
## => "You called method: say_something!"

This is how Rails’ ActiveRecord models work under the hood for methods such as find_by_name or find_by_name_and_age where name and age are database columns. Rails just overrides method_missing to handle these method calls and return the appropriate results.

Important Considerations

  • Alway call super in your overridden method_missing method if you don’t handle a particular scenario. This allows Ruby to continue searching for the method in the normal way, and eventually raise a NoMethodError if it still can’t find it.

Without super:

class Dog
  def method_missing(method_name, *args, &blk)
    if method_name.end_with?('?')
      true
    end
  end
end
d = Dog.new
p d.bark? # => true

But what happens if you call a method that doesn’t end with ’?’ that’s not defined in the class elsewhere? You’d expect it to raise a NoMethodError, right? See:

p d.speak # => nil

That’s not right. This gives the impression that the dog can understand the speak command but it just returns nil for some reason. Instead, it should really be blowing up with a NoMethodError because it doesn’t understand the speak command at all.

This is because our method_missing method above, only handles methods that end with ’?’. And for all others, it just doesn’t do anything. That’s why nil is returned.
Instead, we should be raising a NoMethodError in those cases.

But a better way is to just call super which makes this the responsibility of the original method_missing method defined in BasicObject:

class Dog
  def method_missing(method_name, *args, &blk)
    if method_name.end_with?('?')
      true
    else
      super # Add this
    end
  end
end
p d.bark? # => true
## => raises NoMethodError without rescue, as expected
p d.speak rescue nil
  • Always override respond_to_missing? as well to maintain consistency with Ruby’s introspection methods.

While all dog objects can now respond to any question-mark methods, do they say so?

p d.respond_to?(:bark?) # => false

Oops. But it does, doesn’t it?

p d.bark? # => true

respond_to?(method_name, include_private = false) is a method in Object class. So it’s available in all the objects of classes you define. It returns true if the object can respond to the method. This is useful to write code without lots of exception handling.

In order for respond_to? to return true for the methods you handle in method_missing, you need to override respond_to_missing? method as well, usually with the same logic as in method_missing.

So, for our example above:

class Dog
  def respond_to_missing?(method_name, \
                          include_private = false)
    method_name.end_with?('?') || super
  end
end

Now the dog knows how to respond to bark?

p d.respond_to?(:bark?) # => true

Notes