Problem Using Openstruct with Erb

Problem using OpenStruct with ERB

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

Cannot access `id` field of OpenStruct instance

Looks like a bug/limitation of OpenStruct under 1.8.7 where there isn't a BlankSlate object, caused by an implementation that uses method_missing to decide if it's a special property or not.

Here's a custom class similar to OpenStruct that does what you ask for under 1.8.7; feel free to expand upon it and make it more feature rich.

class MemoStruct
def initialize( h=nil )
h.each{ |k,v| add_field(k,v) } if h
end
def add_field( name, value=nil )
inst = :"@#{name}"
(class << self; self; end).class_eval do
define_method(name){ instance_variable_get inst }
define_method("#{name}="){ |v| instance_variable_set inst,v }
end
instance_variable_set(inst,value)
end
def []=( name, value )
add_field(name,value)
end
end

hash = MemoStruct.new :id=>123, :name=>"Jim"
p hash.id
#=> 123

hash["new_field"] = "stuff"
p hash.new_field
#=> stuff

How to assign ruby's OpenStruct attribute using a variable

require 'ostruct'
os = OpenStruct.new
os.one = 1
os.two = "Two"

attr_name = "sand_box"
os[attr_name] = "Play time!"

p os #-> #<OpenStruct one=1, two="Two", sand_box="Play time!">

ERB template binding not cascading to helper

This has nothing to do with ERB. Call just hello instead of ERB.new "Hi <%= hello %>!".result(binding) to see it.

Methods don't have access to local variables not defined inside them. Turn user to a method or an instance_variable to access it.

Why is this an error with ERB?

If you look at the code outputted by erb -x -T - test.erb:

#coding:ASCII-8BIT
_erbout = ''; _erbout.concat "<div class='row'>\n "
; _erbout.concat(( form.field_container :name do ).to_s); _erbout.concat "\n"
; _erbout.concat " "; _erbout.concat(( form.label :name, raw('Name' + content_tag(:span, ' *', :class => 'required')) ).to_s); _erbout.concat "\n"
; _erbout.concat " "; _erbout.concat(( form.text_field :name, :class => 'fullwidth' ).to_s); _erbout.concat "\n"
; _erbout.concat " "; _erbout.concat(( form.error_message_on :name ).to_s); _erbout.concat "\n"
; _erbout.concat " "; end ; _erbout.concat "\n"
; _erbout.concat "</div>\n"
; _erbout.force_encoding(__ENCODING__)

You can see that on the third line, a do is followed by a ). Ruby is expecting a doend block, but gets a closing parenthesis. That’s the immediate cause of the syntax error.

The reason for erb outtputting bad code is that you are using <%= when you should be using <%. Changing your code to this fixes the syntax error:

<div class='row'>
<% form.field_container :name do %>
<%= form.label :name, raw('Name' + content_tag(:span, ' *', :class => 'required')) %>
<%= form.text_field :name, :class => 'fullwidth' %>
<%= form.error_message_on :name %>
<% end %>
</div>

I can’t run this code to test if it outputs what it should after my change, but the code generated by erb looks like it will work:

#coding:ASCII-8BIT
_erbout = ''; _erbout.concat "<div class='row'>\n "
; form.field_container :name do ; _erbout.concat "\n"
; _erbout.concat " "; _erbout.concat(( form.label :name, raw('Name' + content_tag(:span, ' *', :class => 'required')) ).to_s); _erbout.concat "\n"
# more...

Edit

Since this solution apparently does break the output, I looked into what mu is too short suggested. I checked if Erubis, which Rails 3 uses by default, behaves differently from ERB. The code outputted by erubis -x -T - test.erb (with the original, unedited test.erb):

_buf = ''; _buf << '<div class=\'row\'>
'; _buf << ( form.field_container :name do ).to_s; _buf << '
'; _buf << ' '; _buf << ( form.label :name, raw('Name' + content_tag(:span, ' *', :class => 'required')) ).to_s; _buf << '
'; _buf << ' '; _buf << ( form.text_field :name, :class => 'fullwidth' ).to_s; _buf << '
'; _buf << ' '; _buf << ( form.error_message_on :name ).to_s; _buf << '
'; end
_buf << '</div>
';
_buf.to_s

Line three has the exact same problem, and erubis -x -T - test.erb | ruby -c outputs the same syntax error. So the differences between ERB and Erubis are probably not the problem.

I also tried syntax-checking this piece of code from the official Rails documentation:

<%= form_for(zone) do |f| %>
<p>
<b>Zone name</b><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>

It gets the same syntax error. So it’s not that your ERB code is badly written; your code is very similar to that example.

At this point my best guess is that erb’s -x flag, which translates an ERB template into Ruby code instead of evaluating it directly, is flawed, and does not support some features it should. Though now that I think about it, I am having trouble imagining exactly what Ruby code should be outputted when you output the result of a block that itself outputs text should work. At what times should each of the outputs be written – the result first, or the block contents first?

render erb from database into view problem please help!

I think that you would need to pass the optional binding parameter to the ERB::render method. This effectively provides the local variables in the scope of the ERB template. In other words the binding needs to provide the image_tag variable to the template.

I don't know what 'content' is in your case but the following will pass the binding from the 'parent' view assuming that @obj.image_tag is visible from that view:

<%= ERB.new("image tag - \<\%= @obj.image_tag \%\>").result(binding) %>

Render an ERB template with values from a hash

I don't know if this qualifies as "more elegant" or not:

require 'erb'
require 'ostruct'

class ErbalT < OpenStruct
def render(template)
ERB.new(template).result(binding)
end
end

et = ErbalT.new({ :first => 'Mislav', 'last' => 'Marohnic' })
puts et.render('Name: <%= first %> <%= last %>')

Or from a class method:

class ErbalT < OpenStruct
def self.render_from_hash(t, h)
ErbalT.new(h).render(t)
end

def render(template)
ERB.new(template).result(binding)
end
end

template = 'Name: <%= first %> <%= last %>'
vars = { :first => 'Mislav', 'last' => 'Marohnic' }
puts ErbalT::render_from_hash(template, vars)

(ErbalT has Erb, T for template, and sounds like "herbal tea". Naming things is hard.)

Binding in 1.9.2

After some research I found that another question in StackOverflow addresses this issue and proposes a fix that works:

Problem using OpenStruct with ERB



Related Topics



Leave a reply



Submit