ERB で Binding を使うとローカル変数が勝手に変更されるという問題
通常、ERB#result() には Binding を渡すけど、個人的にこれが大っ嫌い。なんでかというと、eRubyスクリプトを実行することで、メインプログラム内のローカル変数が勝手に変更されてしまうから。
つまりだな、
s = <<END <% for item in list %> <p><%= item %></p> <% end %> END list = ['AAA', 'BBB', 'CCC'] require 'erb' item = 'foo' # ローカル変数を設定 print ERB.new(s).result(binding) p item #=> "CCC" に変更されている!
というのがすごく嫌いというわけ。
#んなもん当たり前じゃないか! と思った人とはたぶん仲良くはなれない。
これが嫌いだから、
print ERB.new(s).result(:list=>list)
のようにできるべきだと思ってるし、Erubis::Eruby#result()はそうなっている。
Erubisにはもうひとつ、Erubis::Eruby#evaluate()というのがある。使い方はresult()と変わらないんだけど、メインプログラムから渡される変数にはローカル変数ではなくインスタンス変数を使う点が違う。
## ローカル変数ではなくインスタンス変数を使うのがポイント input = <<END <h1><%= @title %></h1> <% for item in @list %> <b><%= item %></b> <% end %> END ## result() のかわりに evaluate() を使う title = 'Example' list = [10, 20, 30] require 'erubis' item = 'foo' # ローカル変数を設定 eruby = Erubis::Eruby.new(input) print eruby.evaluate(:title=>title, :items=>items) # Hash p item #=> "foo" で変わっていない!
Erubis::Eruby#evaluate() は内部で Object#instance_eval を使っている。eval() と違って、instance_eval() は Proc オブジェクトを引数にとれるため、Erubis::Eruby#evaluate() は内部でコードをProcオブジェクトに変換してから instance_eval() で実行している。こうすることで、文字列を毎回 eval() する方法と比べて構文解析にかかるコストをなくしている。また ERB#def_method と比べても速度に遜色ないし、柔軟性もより高い。
もっと大事なことは、Bindingを使わないので、eRubyスクリプト内でのローカル変数の操作がメインプログラムに影響することがない。素晴らしい。ただしselfが変わることには注意。