I have an ERB template inlined into Ruby code:
require \'erb\'
DATA = {
:a => \"HELLO\",
:b => \"WORLD\",
}
template = ERB.new <<-EOF
Maybe the cleanest solution would be to pass specific current
local variable to erb template instead of passing the entire binding
. It's possible with ERB#result_with_hash method (introduced in Ruby 2.5)
DATA.keys.each do |current|
result = template.result_with_hash(current: current)
...
For a simple solution, use OpenStruct:
require 'erb'
require 'ostruct'
namespace = OpenStruct.new(name: 'Joan', last: 'Maragall')
template = 'Name: <%= name %> <%= last %>'
result = ERB.new(template).result(namespace.instance_eval { binding })
#=> Name: Joan Maragall
The code above is simple enough but has (at least) two problems: 1) Since it relies on OpenStruct
, an access to a non-existing variable returns nil
while you'd probably prefer that it failed noisily. 2) binding
is called within a block, that's it, in a closure, so it includes all the local variables in the scope (in fact, these variables will shadow the attributes of the struct!).
So here is another solution, more verbose but without any of these problems:
class Namespace
def initialize(hash)
hash.each do |key, value|
singleton_class.send(:define_method, key) { value }
end
end
def get_binding
binding
end
end
template = 'Name: <%= name %> <%= last %>'
ns = Namespace.new(name: 'Joan', last: 'Maragall')
ERB.new(template).result(ns.get_binding)
#=> Name: Joan Maragall
Of course, if you are going to use this often, make sure you create a String#erb
extension that allows you to write something like "x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2)
.
EDIT: This is a dirty workaround. Please see my other answer.
It's totally strange, but adding
current = ""
before the "for-each" loop fixes the problem.
God bless scripting languages and their "language features"...
Simple solution using Binding:
b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)
require 'erb'
class ERBContext
def initialize(hash)
hash.each_pair do |key, value|
instance_variable_set('@' + key.to_s, value)
end
end
def get_binding
binding
end
end
class String
def erb(assigns={})
ERB.new(self).result(ERBContext.new(assigns).get_binding)
end
end
REF : http://stoneship.org/essays/erb-and-the-context-object/
I can't give you a very good answer as to why this is happening because I'm not 100% sure how ERB works, but just looking at the ERB RDocs, it says that you need a binding
which is "a Binding or Proc object which is used to set the context of code evaluation".
Trying your above code again and just replacing
result = template.result
with
result = template.result(binding)
made it work.
I'm sure/hope someone will jump in here and provide a more detailed explanation of what's going on. Cheers.
EDIT: For some more information on Binding
and making all of this a little clearer (at least for me), check out the Binding RDoc.