Read more

When overriding #method_missing, remember to override #respond_to_missing? as well

Arne Hartherz
July 12, 2012Software engineer at makandra GmbH

When you use method_missing to have an object return something on a method call, always make sure you also redefine respond_to_missing?.

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

If you don't do it, nothing will break at a first glance, but you will run into trouble eventually.

Consider this class:

class Dog
  
  def method_missing(method_name, *args, &block)
    if method_name == :bark
      'woof!'
    else
      super
    end
  end
  
end

This will allow you to say:

Dog.new.bark
=> "woof!"

But:

Dog.new.respond_to? :bark
=> false

Lots of code (gems or your own) relies on respond_to? (for a good reason). For example #respond_to? Show archive.org snapshot will call #respond_to_missing? if the method ins not defined within the receiver. When it has not been defined properly, it's return value is a false negative!

Thus, you need to patch respond_to_missing? Show archive.org snapshot as well:

class Dog
  
  def method_missing(method_name, *args, &block)
    if method_name == :bark
      'woof!'
    else
      super
    end
  end
  
  def respond_to_missing?(method_name, *args)
    method_name == :bark or super
  end
  
end
 
Dog.new.bark
=> "woof!" 
Dog.new.respond_to? :bark
=> true 

Differences between respond_to? and respond_to_missing?

Note that old articles will recommend to override respond_to? instead of defining respond_to_missing?. You should always define respond_to_missing?, so your dynamic method will work with #method Show archive.org snapshot .

Example from Marc's post:

class StereoPlayer
  def method_missing(method, *args, &block)
    if method.to_s =~ /play_(\w+)/
      puts "Here's #{$1}"
    else
      super
    end
  end
  
  def respond_to?(method, *)
    method.to_s =~ /play_(\w+)/ || super
  end
end

StereoPlayer.new.respond_to? :play_beethoven # => true
StereoPlayer.new.method :play_beethoven
# => NameError: undefined method `play_some_Beethoven'
#               for class `StereoPlayer'
Arne Hartherz
July 12, 2012Software engineer at makandra GmbH
Posted by Arne Hartherz to makandra dev (2012-07-12 14:16)