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.