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.

About thoughtbot

We've been helping engineering teams deliver exceptional products for over 20 years. Our designers, developers, and product managers work closely with teams to solve your toughest software challenges through collaborative design and development. Learn more about us.