Always Define respond_to_missing? When Overriding method_missing

Mike Burns

method_missing is still considered scary, but here’s something more scary: forgetting to override respond_to?.

Whoa, right? Just gave you an anxiety attack there.

How about #method, though? Does that still work?

Go ahead, try it. Here, I’ll try it with you:

require 'ostruct'

class Order
  def user
    @_user ||= OpenStruct.new(name: 'Mike', age: 28, occupation: 'slacker')
  end

  def method_missing(method_name, *arguments, &block)
    if method_name.to_s =~ /user_(.*)/
      user.send($1, *arguments, &block)
    else
      super
    end
  end

  def respond_to?(method_name, include_private = false)
    method_name.to_s.start_with?('user_') || super
  end
end

OK now I’ll load that into irb and play with it:

ruby-1.9.2-p290> order = Order.new
 => #<Order:0x00000001e40948>
ruby-1.9.2-p290> order.user_name
 => "Mike"
ruby-1.9.2-p290> order.respond_to?(:user_name)
 => true
ruby-1.9.2-p290> order.method(:user_name)
NameError: undefined method `user_name' for class `Order'
  from (irb):23:in `method'
  from (irb):23
  from /home/mike/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'

Denied!

There is a solution! How would you like to solve your #method problem while also using a method with a better name? For the low price of Ruby 1.9 you, too, can have this:

require 'ostruct'

class Order
  def user
    @_user ||= OpenStruct.new(name: 'Mike', age: 28, occupation: 'slacker')
  end

  def method_missing(method_name, *arguments, &block)
    if method_name.to_s =~ /user_(.*)/
      user.send($1, *arguments, &block)
    else
      super
    end
  end

  def respond_to_missing?(method_name, include_private = false)
    method_name.to_s.start_with?('user_') || super
  end
end

But you don’t have to take my word for it:

ruby-1.9.2-p290> order = Order.new
=> #<Order:0x00000001c8d678>
ruby-1.9.2-p290> order.user_name
=> "Mike"
ruby-1.9.2-p290> order.respond_to?(:user_name)
=> true
ruby-1.9.2-p290> order.method(:user_name)
=> #<Method: Order#user_name>

If I were to tweet about this, this is what I would say: always define respond_to_missing? when overriding method_missing.

author image
Mike Burns
hound

Hound automatically reviews Ruby, JavaScript, CoffeeScript, and SCSS code in your GitHub pull requests and comments on style violations. It is free for open source repos and $12/month per private repo.