Rubyのメソッド呼び出しとメソッド定義に一歩踏み込む


今回はメソッドに関するRubyのメタプログラミング技法についてまとめていきたいと思います。

JavaやC言語などの静的言語では、コンパイル時にすべてのメソッドに対して対応するメソッドが存在するかどうかを評価します。
存在しなければ、コンパイルエラーとなりバイナリファイルが作成されません。

一方Rubyのような動的言語では、この制約がなく、メソッドは呼ばれるときに存在すれば良く、そのメソッドをどのような型のインスタンスが呼んでいるかすら気にしません。

アヒルのように歩き、アヒルのように鳴くのなら、それはアヒルなのです。

このように、Rubyはメソッドの定義や呼び出しに対してかなり寛容です。

おさらいとして普通のメソッド呼び出し


class Awesome
    
    def hello
        'World!'
    end

end

awesome = Awesome.new

puts awesome.hello #-> World!

このように、普通のメソッド呼び出しは.演算子を用いて呼び出します。

ドット記法でなく、Object#sendを使ってメソッドを実行する

Object.send(:method,args)でもメソッドを呼び出すことが出来ます。

class Awesome
    
    def hello(word)
        word
    end
    
    private
    
    def concealed
      'You Destroy Object Oriented'
    end
end

awesome = Awesome.new

puts awesome.send(:hello,'World!') #-> World!

__send__はsendのエイリアスメソッド

また、sendというメソッド名は他のメソッド名と被りやすいため、__send__というメソッドも用意されています。これはsendのエイリアスメソッドなので、同じ挙動を示します。

puts awesome.__send__(:hello,'World!') #-> World!

privateメソッドも呼び出せる

sendメソッドでは、privateメソッドも呼び出すことが出来ます。

puts awesome.__send__ :concealed #-> You Destroy Object Oriented

プライベートメソッドのRSpecテストを行うときに、そこそこ役に立つ技法です。

defキーワードを使わずにメソッドを動的に定義する

Module#define_methodを使えば、メソッドをその場で定義できます。それにはメソッド名とブロックを渡す必要があり、ブロックがメソッドの本体になります。

class Incrementer
   
   define_method :increment do |number|
       number+1
   end
end

puts Incrementer.new.increment(999) #-> 1000

インスタンスのメソッド一覧を取得する

インスタンスのClass名が.classで取得できるように、インスタンスのメソッド名も.methodsで取得することができます。

puts Incrementer.new.methods =>メソッドがずらずらっと表示される

method_missingをオーバーライドする(ゴーストメソッド)

class EmptyObject
    
    
    def method_missing(method,*args)
        super unless method==:walk
        puts "I'm walking..."
    end
    
end

EmptyObject.new.walk # メソッドないけど歩かせてみよう
# -> "I'm walking..."

メソッドを定義せずに、あたかも歩くことができるかのようにメソッドに振る舞わせることができました。

method_missingはかなりコアな機能であり、可能であれば動的定義を使ったほうがいいですが、最終奥義がmethod_missingのオーバーライドであるということは頭の片隅に置いておいたほうがよいでしょう。

詳しく学びたい方は、「メタプログラミングRuby」を読んでみると良いでしょう、