Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem using OpenStruct with ERB

EDIT: forgot to include my environment info... Win7x64, RubyInstaller Ruby v1.9.1-p378

EDIT 2: just updated to v1.9.1, patch 429, and still getting this same error.

Edit 3: running this same code in Ruby v1.8.7, patch 249, works fine. so it's v1.9.1 that broke it, apparently.

I'm new to using ERB and the samples i could find are... ummm... less than helpful... having played around with ERB for about an hour, I got some basic examples working (finally), but I have no idea why this doesn't work...

require 'ostruct'
require 'erb'

data = {:bar => "bar"}
vars = OpenStruct.new(data)

template = "foo "
erb = ERB.new(template)

vars_binding = vars.send(:binding)
puts erb.result(vars_binding)

this code produces the following error:

irb(main):007:0> puts erb.result(vars_binding)
NameError: undefined local variable or method `bar' for main:Object
        from (erb):1
        from C:/Ruby/v1.9.1/lib/ruby/1.9.1/erb.rb:753:in `eval'
        from C:/Ruby/v1.9.1/lib/ruby/1.9.1/erb.rb:753:in `result'
        from (irb):7
        from C:/Ruby/v1.9.1/bin/irb:12:in `'

why is it looking at the main:Object binding? I told it to use the binding from the OpenStruct by passing in vars_binding

can someone fill me in on why it doesn't work, and help me get it to work?

like image 714
Derick Bailey Avatar asked Jul 14 '10 00:07

Derick Bailey


People also ask

Why use OpenStruct?

OpenStruct objects are useful when you need something to fit a certain method call interface (i.e. send in a duck-typed object responding to #name and #value ), or when you want to encapsulate the implementation details, but also want to avoid over-engineering the solution.

What is OpenStruct?

An OpenStruct is a data structure, similar to a Hash , that allows the definition of arbitrary attributes with their accompanying values. This is accomplished by using Ruby's metaprogramming to define methods on the class itself.


2 Answers

The problem is where the binding is being executed. The 1.8.7-way obj.send(:binding) does not work anymore (see issue2161), the environment must be the object itself. So use instance_eval:

require 'ostruct'
require 'erb'

namespace = OpenStruct.new(:first => 'Salvador', :last => 'Espriu')
template = 'Name: <%= first %> <%= last %>'
ERB.new(template).result(namespace.instance_eval { binding })
#=> Name: Salvador Espriu

More about this issue in this answer.

like image 190
tokland Avatar answered Oct 13 '22 19:10

tokland


Fix to Problem:

I stumbled upon this question when encountering the same type of error with similar code in Ruby 1.9.2.

I'm new to Ruby so I can't explain what is happening. I continued to search online and found this blog post that has an approach that seems to work. After modifying your example to incorporate this approach I end up with the following, working, code:

require 'ostruct'
require 'erb'

class ErbBinding < OpenStruct
    def get_binding
        return binding()
    end
end

data = {:bar => "baz"}
vars = ErbBinding.new(data)

template = "foo <%= bar %>"
erb = ERB.new(template)

vars_binding = vars.send(:get_binding)
puts erb.result(vars_binding)

Additional Information:

When the code is run thru the IRB, I get:

require 'ostruct'
=> true
require 'erb'
=> true

class ErbBinding < OpenStruct
    def get_binding
        return binding()
    end
end
=> nil

data = {:bar => "baz"}
=> {:bar=>"baz"}
vars = ErbBinding.new(data)
=> #<ErbBinding bar="baz">

template = "foo <%= bar %>"
=> "foo <%= bar %>"
erb = ERB.new(template)
=> #<ERB:0x2b73370 @safe_level=nil, @src="#coding:IBM437\n_erbout = ''; _erbout.concat \"foo \"; _erbout.concat(( bar ).to_s); _erbout.force_encoding(__ENCODING__)", @enc=#<Encoding:IBM437>, @filename=nil>

vars_binding = vars.send(:get_binding)
=> #<Binding:0x2b6d418>
puts erb.result(vars_binding)
foo baz
=> nil

like image 44
Matt Avatar answered Oct 13 '22 21:10

Matt