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

Pair with one of our expert developers to level up your skills with Coaching by thoughtbot. Save time learning best practices and techniques for reducing technical debt in Ember, Ruby, Haskell, and Go in 1-on-1 sessions tailored to your goals.