method_missingはBasicObjectまでメソッド探索を行ってもメソッドが見つからなかったときに呼び出されるメソッドで例えば以下のようなクラスが定義されている場合、Cクラスのインスタンスで存在しないno_existを呼び出すと
- (インスタンスの特異クラス)→C→B→A→...BasicObjectまでundefined_methodが探索されるが見つからない。
- (インスタンスの特異クラス)→C→B→A→...BasicObjectまでmethod_missingを探索し、BasicObjectに定義されているmethod_missingが呼ばれてNoMethodErrorが発生する。
class A
end
class B < A
end
class C < B
end
> C.ancestors
=> [C, B, A, Object, Kernel, BasicObject]
> C.new.undefined_method
NoMethodError: undefined method `no_exist' for #<C:0x007ffaa2a28948>
なので例えば以下のようにCクラスにmethod_missingを定義することでCクラスに対するMethodMissing例外を捕捉することが出来ます。
class A
end
class B < A
end
class C < B
def method_missing(m)
"ignore #{m}"
end
end
> C.new.undefined_method
=> "ignore no_exist"
今度はBクラスに対してもmethod_missingを定義します。するとCクラスのインスタンスからno_existを呼び出したときはCによって捕捉され、Bクラスのインスタンスから呼び出したときはBによって捕捉されています。このことからmethod_missingの探索開始地点はメソッドを呼び出したレシーバから始まっていることがわかります。
class A
end
class B < A
def method_missing(m)
"ignore #{m} by B"
end
end
class C < B
def method_missing(m)
"ignore #{m} by C"
end
end
> B.new.undefined_method
=> "ignore no_exist by B"
> C.new.undefined_method
=> "ignore no_exist by C"
では以下のようにCクラスのインスタンスから、Bクラスのメソッドが呼び出されてそのメソッド内で存在しないメソッドが呼び出された場合はどうなるでしょうか?
class A
end
class B < A
def baz
undefined_method
end
def method_missing(m)
"ignore #{m} by B"
end
end
class C < B
def method_missing(m)
"ignore #{m} by C"
end
end
> C.new.baz
C
=> "ignore undefined_method by C"
一瞬Bクラスのメソッドから存在しないメソッドを呼び出しているのでBクラスから探索が始まりそうに思ってしまいますが、この場合もあくまでもno_existメソッドはCクラスのインスタンスをレシーバとして呼び出されているのでCクラスから探索が始まります。
なので例えば以下のように書けばBクラスから探索が始まります
class A
end
class B < A
def baz
B.new.undefined_method
end
def method_missing(m)
"ignore #{m} by B"
end
end
class C < B
def method_missing(m)
"ignore #{m} by C"
end
end
> C.new.baz
=> "ignore undefined_method by B"
結局結論はとてもシンプルで
存在しないメソッドを呼び出したレシーバから探索は始まる
でした。