bindingとeval

Rails1.2からstart_form_tagとかがdeprecatedになって、

<% form_tag do %>
  <%= text_field('some_object', 'some_attr') %>
<% end %>

みたいに書けるようになったわけですが、<%= form_tag do %> じゃなくて <% form_tag do %>って何よ?って調べたら、evalとbindingという魔法が使われていました。

http://www.ruby-lang.org/ja/man/?cmd=view;name=%C1%C8%A4%DF%B9%FE%A4%DF%B4%D8%BF%F4;em=eval#eval より引用

eval(expr[, binding[, fname[, lineno=1]]])
文字列 expr を Ruby プログラムとして評価してその結果を返します。
第2引数に Proc オブジェクトまたは Binding オブジェクトを与えた場合、
そのオブジェクトを生成したコンテキストで文字列を評価します。
binding も参照してください。

よくわからないのでBindingを見る
http://www.ruby-lang.org/ja/man/?cmd=view;name=Binding

Binding
ローカル変数のテーブルと self、モジュールのネストなどの情報を
保持するオブジェクトのクラス。組み込み関数 binding によってのみ生成され、
eval の第 2 引数に使用します。

どうやらbindingオブジェクトは、あるメソッドあるいはブロックが呼び出された際のコンテキストを保持するオブジェクトらしい。これをevalに指定することでそのコンテキストで文字列を評価してくれるわけデス。内部的にはローカル変数のテーブルがスタックされているというのは想像できますが、それを簡単に扱えるっていうのは驚きデス。

というわけでサンプルを書いてみました。

class A
  def foo(&block)
    eval('self.baz', block.binding)
    count = eval('count', block.binding)
    count.times(&block)
  end
end

class B
  def bar(count, msg)
    A.new.foo do
      puts msg
    end
  end
  
  def baz
    puts 'baz'
  end
end

b1 = B.new
b1.bar(3, 'abc')

結果

baz
abc
abc
abc

コードに意味はありませんが、こんな感じのコードのアプリのレベルで書いてあったら悪意を感じずにはいられません。そんな人いないでしょうけど。