method_missingとは
method_missingとはRubyリファレンスマニュアルに記載の通り、呼びだされたメソッドが定義されていなかった時、Rubyインタプリタがこのメソッドを呼び出します。
また、本来存在しないメソッドを呼び出すので、これらの呼び出されたメソッドはゴーストメソッドとも呼ばれます。
魔術師としての使い方
呼び出しに失敗したメソッドの名前 (Symbol) が name にその時の引数が第二引数以降に渡されます。デフォルトではこのメソッドは例外NoMethodErrorを発生
させます。
class Foo
end
foo = Foo.new
# 未定義のメソッドを呼び出すとNoMethodErrorとなる
foo.sample_method #=> NoMethodError!
しかし、method_missingをオーバーライドすることで未定義のメソッドが呼び出されても、意図的に未定義メソッドに対して任意の処理
を加えることが出来る。
class Foo
def method_missing(method, *args)
# 任意の処理
puts "#{method}が呼び出されましたが未定義です。"
end
end
foo = Foo.new
# 未定義のメソッドを呼び出すしてもNoMethodErrorとならずに、任意の処理が出来る
foo.sample_method #=> "sample_methodが呼び出されましたが未定義です。"
method_missingという魔術の闇
以下のコードには無限ループするというエラーが隠れています。どこで発生しているか分かりますでしょうか??
class Roulette
def method_missing(name, *args)
person = name.to_s.upcase
3.times do
number = rand(10) + 1
puts "#{number}..."
end
"#{person} got a #{number}"
end
end
rand_num = Roulette.new
puts rand_num.tanaka
#=> 4...
#=> 2...
#=> 9...
#=> 7...
#=> …
#=> …
#=> SystemStackError: stack level too deep from..
ブロックローカル変数であるnumberが、ブロック外部で参照しようとしていることがエラーの原因となっています。
Rubyは「number」が見つからないので、method_missingメソッドを呼び出そうとするが、method_missingメソッド内で発生しているバグなので無限ループ
を起こしてしまいます。
注意点
method_missingメソッド自体にバグが潜んでいると、問題が特定しづらいため、必要のないゴーストメソッドは導入しないようにしましょう。
修正後コード
class Roulette
def method_missing(name, *args)
person = name.to_s.capitalize
# 想定される名前以外は通常のmethod_missingを呼び出す
super unless %w[tanaka sato suzuki].include?(person)
# numberをブロック外から参照できるように事前に定義しておく
number = 0
3.times do
number = rand(10) + 1
puts "#{number}..."
end
"#{person} got a #{number}"
end
end
rand_num = Roulette.new
# NoMethodError発生
puts rand_num.foo
=> NoMethodError: undefined method `foo' for #<Roulette:0x007fe4bb9ac7a0>
puts number_of.tanaka
#=> 3...
#=> 5...
#=> 5...
#=> tanaka got a 5
まとめ
method_missingは、魔術の一種として用いることができますが、リスクも伴っているので、利用するときは本当にこのやり方でいいか?
を意識した上で利用しましょう!