Metaprogramming Ruby - Method Missing

Question Click to View Answer

What does the following code print? Explain.

class Books; end
b = Books.new
p b.hi

This code raises the following exception: NoMethodError: undefined method `hi' for #Books:0x007faf6b1e7950

Ruby goes through the ancestor hierarchy and looks for a method that corresponds with the message :hi. It does not encounter a corresponding method, so it calls BasicObject#method_missing, which raises the NoMethodError exception.

What does the following code print? Explain.

class Charger
  private
  def method_missing(name, *args)
    "I'm a little teapot"
  end
end

p Charger.new.whoo_hoo
"I'm a little teapot"

No classes respond to the :whoo_hoo message in the charger ancestor hierarchy, so method_missing is called. This example defines Charger#method_missing, which returns a value before BasicObject#method_missing can be called to raise an exception.

What does the following code print? Explain.

class A
  private
  def method_missing(name, *args)
    args.unshift(name.to_s).join
  end
end

p A.new.meta('programming', 'ruby')
"metaprogrammingruby"

The name parameter is assigned to the method name (:meta) and args is assigned to an array of the arguments (['programming', 'ruby']).

Use method_missing to sum the arguments passed to the :add method.

class Calc
  private
  def method_missing(name, *args)
    # add code here
  end
end

Calc.new.add(1, 2, 3, 4) # should return 10
class Calc
  private
  def method_missing(name, *args)
    args.inject(&:+)
  end
end

Calc.new.add(1, 2, 3, 4) # => 10

Update the following code to raise an exception if the message is anything other than :full_name.

class Person
  private
  def method_missing(name, *args)
    args.join(" ")
  end
end

Person.new.full_name("Fat", "Joe") # => "Fat Joe"
Person.new.whatever("bob", "lob") # => raise exception
class Person
  private
  def method_missing(name, *args)
    super unless name == :full_name
    args.join(" ")
  end
end

It's usually a bad idea to always override method_missing. Using super to raise exceptions for unexpected messages is almost always a good idea.

What problem does the following code highlight?

class Calculator
  private
  def method_missing(name, *args)
    super unless name == :multiply
    args.inject(&:*)
  end
end

calc = Calculator.new
p calc.respond_to? :multiply
false

The calc object is lying to us by saying it does not respond to the :multiply message.

What problem does the following code highlight?

class Monster
  private
  def method_missing(name, *args)
    "blah monster, attack!"
  end
end

krumm = Monster.new
krumm.method :boo

This raises the following exception: NameError: undefined method

The method method is misleading us by raising and exception and suggesting that the krumm object does not respond to the :boo message.

What does the following example print? Explain.

class Redwall
  private
  def method_missing(name, *args)
    super unless name == :battle_cry
    "Eulalia"
  end

  def respond_to_missing?(name, include_private = false)
    name == :battle_cry || super
  end
end

best_book = Redwall.new
p best_book.respond_to? :battle_cry
p !!best_book.method(:battle_cry)
true
true

The respond_to_missing? method can be overwritten to have the respond_to? and method() methods behave properly.

What does the following code print? Explain.

class Object
  private
  def method_missing(name, *args)
    'This is a terrible idea'
  end
end

p 'string'
p 'boo'.fooey
p 'array'
p [].boggie_down
'string'
'This is a terrible idea'
'array'
'This is a terrible idea'

Object#method_missing is called before the default BasicObject#method_missing can be called. This code would return 'This is a terrible idea' for almost all messages sent to objects that inherit from Object cannot respond to. It's terrible code, but illustrates the method_missing lookup process and how instances of different classes can share the same method_missing.

Fix the sneaky little bug in this program? Why is the code raising the SystemStackError: stack level too deep exception?

class Seltzer
  private
  def method_missing(name, *args)
    result = 'non-alcoholic bubbly'
    3.times { result << fun }
    result
  end
end

p Seltzer.new.drank

The code accidentally used fun instead of "fun". When method_missing is not overridden, using an undefined local variable or method will raise an exception that's easy to decipher. When the program encounters fun, it interprets it as a method call and calls method_missing again. This process continues infinitely until the program realizes it's going in an infinite loop and raises a SystemStackError exception.

Falling back on super for unexpected inputs is the best way to avoid these cryptic exceptions.

class Seltzer
  private
  def method_missing(name, *args)
    super unless name == :drank
    result = 'non-alcoholic bubbly'
    3.times { result << fun }
    result
  end
end

Add code to method missing so it will respond with values assigned to instance variables for instances of the Book class.

class Book
  def initialize(title, author)
    @title = title
    @author = author
  end

  private
  def method_missing(name, *args)
    # add code here
  end
end

b = Book.new("The Intelligent Investor", "Benjamin Graham")
b.title # => "The Intelligent Investor"
b.author # => "Benjamin Graham"
def method_missing(name, *args)
  iv = "@#{name.to_s}"
  super unless instance_variables.include?(iv.to_sym)
  instance_variable_get(iv)
end