Please let Binding#eval receive a block

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”


  1. No Comments

Leave a Reply








Creative Commons License

This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.