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 do
…end
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
How to Remove Duplicates in a Hash in Ruby on Rails
Splitting String into Pair of Characters in Ruby
Stop Loading Page Watir-Webdriver
Ruby on Rails Incompatible Library
Twitter Bootstrap Displays Button with Greyed Text
Session Not Destroyed When Closing Browser - Railstutorial.Org
Undefined Method 'Group_By_Day' - Rails 3.2
Redirect_Uri_Mismatch. Login with Google Using Ruby on Rails
Can You Install Documentation for Existing Gems
Ruby on Rails 4 - Simple_Form Multiple Select Input
Ruby on Rails Multiple Http Request at the Same Time
No Such File to Load -- SQLite3/Sqlite3_Native
Change Default Date Format in Ruby on Rails
Rails Shows "Warning: Can't Verify Csrf Token Authenticity" from a Restkit Post