I was very happy to see that Ruby 1.8.7 made a method named Binding#eval public.
This method is very useful when you need to mix two Domain-Specific Languages. In Ruby you often evaluate a block in a different context than where it was defined, using instance_eval and friends. The problems is that a block can have code implemented in two different DSLs and the DSL processor evaluating one may not have access to the other.
This is often the case when testing something using RSpec.
require 'spec'
class MyDsl
def execute(&block)
instance_eval &block
end
end
describe "My DSL" do
it "should pass" do
#only rspec
[1,2].should eql([1,2])
#my dsl
MyDsl.new.execute do
[1,2].should eql([1,2])
end
end
end
Results in:
pcalcado@pcalcado:random-lab$spec ruby_spec.rb F 1) NoMethodError in 'My DSL should pass' undefined method `eql' for #<MyDsl:0x550fc8> ./ruby_spec.rb:17: ./ruby_spec.rb:6:in `instance_eval' ./ruby_spec.rb:6:in `execute' ./ruby_spec.rb:16: Finished in 0.006433 seconds 1 example, 1 failure pcalcado@pcalcado:random-lab$
Trying to figure out a solution for this (if you have an idea please let me know) I found out that Binding had an eval method, but it was private until now. Using Binding#eval it is possible to capture the error above in MyDSL#method_missing and try to evaluate this expression in the Proc’s Binding.
class DslA
def m
puts 'DSL A'
end
def method_missing(m, *p)
#in ruby 1.8.7 this can be binding.eval directly
@binding.send(:eval, "n")
end
def execute_local(&block)
@binding = block.binding
puts "eval in closure"
block.call
puts "eval in my binding"
instance_eval &block
end
end
class DslB
def m
puts 'DSL B'
end
def n; "I'm defined in DslB only" ; end
def run
DslA.new.execute_local { m ; puts n}
end
end
DslB.new.run
pcalcado@pcalcado:random-lab$ruby --version ruby 1.8.6 (2007-09-24 patchlevel 111) [universal-darwin9.0] pcalcado@pcalcado:random-lab$ruby ruby_eval.rb eval in closure DSL B I'm defined in DslB only eval in my binding DSL A ruby_eval.rb:15:in `send': undefined local variable or method `n' for #<Binding:0x28500> (NameError) from ruby_eval.rb:15:in `eval' from ruby_eval.rb:15:in `send' from ruby_eval.rb:15:in `method_missing' from ruby_eval.rb:35:in `run' from ruby_eval.rb:23:in `instance_eval' from ruby_eval.rb:23:in `execute_local' from ruby_eval.rb:35:in `run' from ruby_eval.rb:39 pcalcado@pcalcado:random-lab$~/bin/ruby-1.8.7 --version ruby 1.8.7 (2008-05-31 patchlevel 0) [i686-darwin9.2.2] pcalcado@pcalcado:random-lab$~/bin/ruby-1.8.7 ruby_eval.rb eval in closure DSL B I'm defined in DslB only eval in my binding DSL A I'm defined in DslB only pcalcado@pcalcado:random-lab$
Of course this could cause problems if you try to evaluate functions with side effects, it’s not perfect but at least would help. Also if you follow Language As Interface I don’t think you’ll find many issues.
My problem now is that Binding#eval in Ruby 1.8.7 receives only a String and not a block. That is an old request but I didn’t see much attention to that feature when Ruby 1.8.7 came out.

0 Responses to “Please let Binding#eval receive a block”