Ruby’s method_missing

Kernel#method_missing() is called when Ruby sends a message and the receiver cannot find a corresponding method. Kernel is a module that’s mixed into Object and virtually all Ruby objects inherit from Object, so all Ruby methods respond to method_missing. method_missing can be overwritten to implement custom behavior – let’s start with a trivial example:

class Dude
  def life_outlook
    'cowabunga'
  end

  def method_missing(name, *args)
    'radical haircut'
  end
end

d = Dude.new
p d.undefined_method # => 'radical haircut'
p d.life_outlook # => 'cowabunga'

When an instance of the Dude class is sent a message it can respond to, it will respond normally. If an instance of the Dude class is sent a message it cannot respond to, it will call method_missing and return ‘radical haircut’. It’s typically a bad idea to overwrite method_missing for all messages, so let’s refactor the code to only overwrite method_missing for the following messages: :haircut, :flow, and :dome_style.

class Dude
  def method_missing(name, *args)
    return super unless [:haircut, :flow, :dome_style].include?(name)
    'radical haircut'
  end
end

p Dude.new.undefined_method # => NoMethodError: undefined method `undefined_method'
p Dude.new.haircut # => 'radical haircut'

The methods() and respond_to?() methods will not register the new messages objects can deal with when message_missing is customized.

class A
  def method_missing(name, *args)
    return "hi" if name == :hello
  end
end

a = A.new
p a.hello # => 'hi'
a.respond_to? :hello # => false
a.methods.include? :hello # => false

When method_missing is used, methods() and respond_to?() need to be updated as well, so they don’t provide incorrect answers.

The method_missing() examples have been trivial so far, but method_missing() can also be used for powerful programming techniques like creating objects that respond to messages corresponding with their instance variable values.

class Person
  def initialize
    @first_name = 'bob'
    @last_name = 'lob'
  end

  def method_missing(name, *args)
    iv = "@#{name.to_s}"
    if instance_variables.include?(iv.to_sym)
      instance_variable_get(iv)
    else
      super
    end
  end
end

p = Person.new
# instance variables are accessible as methods
p.first_name # => "bob"
p.last_name # => "lob"

# Kernel#method_missing is still called for messages
# that don't correspond to instance variable names
p.phattie # => NoMethodError: undefined method `phattie'

# when new instance variables are defined, they are accessible
p.instance_variable_set(:@height, "six foot") # => "six foot"
p.height # => "six foot"

method_missing() is a powerful Ruby programming technique – learn it!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s