simanのブログ

ゆるふわプログラマー。競技プログラミングやってます。Ruby好き

rubyでmethod_missingからのdefine_method

method_missing

Rubyでは通常、存在しないメソッドを呼び出した場合エラーが発生する。

class Siman
end

s = Siman.new
s.hello
method_missing.rb:5:in `<main>': undefined method `hello' for #<Siman:0x007f811c066798> (NoMethodError)

NoMethodErrorが発生。

このエラーメッセージは実はmethod_missingというメソッドが内部で呼び出されて表示させているものなのである。


なのでこのmethod_missingをオーバーライドすることで独自のエラーメッセージを表示させることが出来る。

class Siman
  def method_missing(name, *args)
    puts "#{name}メソッドは存在していません。"
  end
end

s = Siman.new
s.hello
helloメソッドは存在していません。

method_missingの第1引数は呼びだそうとしたメソッド名を表し、第2引数はその呼びだそうとしたメソッドの引数を表している。


define_method

rubyにはdefine_methodというメソッドを動的に定義できるメソッドが存在する。

class Siman
  define_method :hello do
    puts "hello world" 
  end
end

s = Siman.new
s.hello
hello world

「普通にdefで定義すればいいじゃん」と思うかもしれないが、ここで重要なのは「define_methodはプログラム実行中でも実行できる」ということである。つまり、先ほどのmethod_missingと組み合わせると「存在しないメソッドを生成」というとんでもないことが出来てしまう。(黒魔術)


send

sendメソッドはメソッドを動的に呼び出すためのメソッドである。呼び出しに変数などを使用できるため、柔軟性が高い。

class Siman
  def say(word)
    puts "siman like #{word}"
  end
end

s = Siman.new

words = %w(ruby python c++ java php)
words.each do |word|
  s.send(:say, word)
end
siman like ruby
siman like python
siman like c++
siman like java
siman like php

第1引数には呼び出したいメソッドの名前、第2引数にはメソッドに渡す引数を指定する。


存在しないメソッドを召喚

ここまでで紹介した「method_missing」「define_method」「send」メソッドを使用して、「存在しないメソッドを呼び出す」黒魔術を実行する。

class Siman
  def method_missing(name, *args)
    Siman.class_eval{
      define_method "#{name}" do |*args|
        puts "#{args.join(' ')}"
      end
    }
    send(name, *args)
  end
end

s = Siman.new
s.say("hello world")
hello world

method_missing -> define_method -> sendの華麗なコンボにより


* 存在しないメソッドの呼び出しに成功してしまった *


そんなRubyが僕は大好きです。