読者です 読者をやめる 読者になる 読者になる

Ruby 1.9でmodule_evalとブロック付きメソッド呼び出しの回避策

Ruby 1.9のmodule_evalとブロック付きメソッド呼び出しの件で、回避策を発見しました。

問題の挙動を詳しく見てみると、↓こういうことみたいです。

# メソッドを追加されるクラス
class Test
end


# 直接module_evalをブロック付きメソッド呼び出し
# => OK
Test.module_eval {
  def test1
    p :test1
  end
}
Test.new.test1  #=> :test1


# メソッド付き呼び出しで作ったProcでmodule_evalをメソッド付き呼び出し
# => NG!!
def test2(&prc)
  Test.module_eval(&prc)
end
test2 {
  def test2
    p :test2
  end
}
Test.new.test2  #=> NoMethodErrorになる


# Procオブジェクトをmodule_evalをブロック付きメソッド呼び出し
# => OK
prc = Proc.new {
  def test3
    p :test3
  end
}
Test.module_eval(&prc)
Test.new.test3   #=> :test3


# Procオブジェクトをメソッドに渡してからmodule_evalをブロック付きメソッド呼び出し
# => NG!!
def test4(prc)
  Test.module_eval(&prc)
end
prc = Proc.new {
  def test4
    p :test4
  end
}
test4(prc)
Test.new.test4  #=> NoMethodErrorになる


# module_evalをProcのbindingで実行
# => OK
def test5(prc)
  Kernel.const_set(:GLOBAL_ACCESSABLE, prc)
  prc.binding.eval "Test.module_eval(&::GLOBAL_ACCESSABLE)"
  Kernel.module_eval { remove_const(:GLOBAL_ACCESSABLE) }
end
prc = Proc.new {
  def test5
    p :test5
  end
}
test5(prc)
Test.new.test5  #=> :test5


# ブロック付きメソッド呼び出しで作ったProcのbindingでmodule_eval
# => OK
def test6(&prc)
  Kernel.const_set(:GLOBAL_ACCESSABLE, prc)
  prc.binding.eval "Test.module_eval(&::GLOBAL_ACCESSABLE)"
  Kernel.module_eval { remove_const(:GLOBAL_ACCESSABLE) }
end
test6 {
  def test6
    p :test6
  end
}
Test.new.test6  #=> :test6


何が何だか良く分からないのですが、とりあえずtest6の方法で希望の動作になるようです。
ここでは::GLOBAL_ACCESSABLEという定数を使っていますが、これはどこからでもアクセスできる位置であればどこでもよくて、Objectのクラス変数でも、適当な名前空間(Module)を作ってそこに定義してもOKです。(要するにProc#bindingの中でそのprocを呼べればいい)