2012年8月27日月曜日

delegation.rbでのModuleの再オープン

delegation.rbを見ていて疑問に思ったことがありました。
一部ソースコードは省略しています。
  def delegate(*methods)
    module_eval(<<-EOS, file, line - 1)
      def #{method_prefix}#{method}(*args, &block)        # def customer_name(*args, &block)
        #{to}.__send__(:#{method}, *args, &block)         #   client.__send__(:name, *args, &block)
      rescue NoMethodError                                # rescue NoMethodError
        if #{to}.nil?                                     #   if client.nil?
          #{exception}                                    #     # add helpful message to the exception
        else                                              #   else
          raise                                           #     raise
        end                                               #   end
      end                                                 # end
    EOS
  end
これ、メソッドの中でmodule_evalを使っているんですよね。
 え、そんなことできたっけ?って感じでした。 例えば、これとか。
class Zombie
  def hi
    module_eval <<-EOS
      def hello_zombie
        puts "Hello Zombie"
      end
    EOS
  end 
end

Zombie.new.hi
moduke_eval2.rb:7:in `hi': undefined method `module_eval' for # (NoMethodErr
こんな感じでエラーが出ます。 正しくはこう。
class Zombie
  module_eval <<-EOS
    def hello_zombie
      puts "Hello Zombie"
    end
  EOS
end

Zombie.new.hello_zombie
じゃあ、Railsではどうやってメソッドの中にmodule_evalを入れているのか?
もう一度、delegation.rbを見てみる。
class Module
end
なんとModuleを再オープンしていた。class Delegationではないらしい。
 それをquerying.rbでrequireして使っていた。
 へ〜、こうやるとメソッドの中で使えるようになるんだ〜、ってことで試してみる。
class Module
  def delegate(value)
    module_eval <<-EOS
      def #{value}
        p "hello Zombie"
      end
    EOS
  end 
end

module Human
  delegate("hello")
end

class Zombie
  include Human
end
Zombie.new.hello
Ruby では、モジュールもオブジェクトの一つで Module クラスのインスタンスです。
なのでclass Moduleを再オープンしてdelegateを定義。 もうちょっと、確認する。
Module.methods.grep(/eval/)
Module.instance_methods.grep(/eval/)
実行結果
[:module_eval, :class_eval, :instance_eval]
[:module_eval, :class_eval, :instance_eval]
class Zombie
end
Zombie.methods.grep(/eval/)
Zombie.instance_methods.grep(/eval/)
実行結果
[:module_eval, :class_eval, :instance_eval]
[:instance_eval]
なるほど。つまり、インスタンスメソッドとしてmodule_evalを持っていれば、
メソッド定義の中でmodule_evalを使えるってことか。
そんなわけで納得できました。
Railsのソースコードは勉強になりますね。

0 件のコメント: