🎓 A1Learner
🔍
0 of 28 lessons completed

Ruby Method Missing Chains

The intermediate course let one object answer unknown calls with method_missing. Here the first handler passes the call along to a second object, so unknown calls travel down a chain.

Forwarding to an owned object

Car holds an engine and defines no start method. Its method_missing forwards the unknown call to the engine with send, and the engine answers it.

class Engine
  def start
    "engine on"
  end
end
class Car
  def initialize
    @engine = Engine.new
  end
  def method_missing(name, *args)
    @engine.send(name, *args)
  end
end
puts Car.new.start

The second handler answers

Helper defines no whistle method, so its own method_missing answers the forwarded call. The answer names the call that travelled down the chain.

class Helper
  def method_missing(name, *args)
    "helper has no #{name}"
  end
end
class Front
  def initialize
    @helper = Helper.new
  end
  def method_missing(name, *args)
    @helper.send(name, *args)
  end
end
puts Front.new.whistle

The name arrives whole

Echo answers any unknown call with the name it received, turned into a string. The long name morning_light arrives whole, with nothing cut off.

class Echo
  def method_missing(name, *args)
    name.to_s
  end
end
class Relay
  def initialize(target)
    @target = target
  end
  def method_missing(name, *args)
    @target.send(name, *args)
  end
end
relay = Relay.new Echo.new
puts relay.morning_light

The super path raises

Tail answers its unknown calls with super, and nothing further along can answer either. The call raises, and the rescue prints only the class of the error.

class Tail
  def method_missing(name, *args)
    super
  end
end
class Head
  def initialize
    @tail = Tail.new
  end
  def method_missing(name, *args)
    @tail.send(name, *args)
  end
end
begin
  Head.new.vanish
rescue => e
  puts e.class
end

Example

class Cook
  def bake(item)
    "baked #{item}"
  end
  def method_missing(name, *args)
    "cook cannot #{name}"
  end
end
class Waiter
  def initialize
    @cook = Cook.new
  end
  def method_missing(name, *args)
    @cook.send(name, *args)
  end
end
waiter = Waiter.new
puts waiter.bake "bread"
puts waiter.fry "egg"

Key points

  • A method_missing handler can forward an unknown call with send.
  • send takes the original name and the original arguments.
  • The second object answers with its real methods or its own method_missing.
  • super inside method_missing keeps the search going and raises when nothing answers.
  • The unknown name arrives whole as one symbol.

In this example, Waiter defines no bake or fry method, so both calls reach its method_missing. Each call is forwarded to the cook with send. The cook answers bake as a real method and fry through its own method_missing.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

In a chain, how does the first method_missing pass a call to the second object?