Reputation: 19463
I have an ERB template inlined into Ruby code:
require 'erb'
DATA = {
:a => "HELLO",
:b => "WORLD",
}
template = ERB.new <<-EOF
current key is: <%= current %>
current value is: <%= DATA[current] %>
EOF
DATA.keys.each do |current|
result = template.result
outputFile = File.new(current.to_s,File::CREAT|File::TRUNC|File::RDWR)
outputFile.write(result)
outputFile.close
end
I can't pass the variable "current" into the template.
The error is:
(erb):1: undefined local variable or method `current' for main:Object (NameError)
How do I fix this?
Upvotes: 57
Views: 66115
Reputation: 708
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)
...
Upvotes: 3
Reputation: 25794
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.
Upvotes: 4
Reputation: 29493
In the code from original question, just replace
result = template.result
with
result = template.result(binding)
That will use the each block's context rather than the top-level context.
(Just extracted the comment by @sciurus as answer because it's the shortest and most correct one.)
Upvotes: 8
Reputation: 18674
As others said, to evaluate ERB with some set of variables, you need a proper binding. There are some solutions with defining classes and methods but I think simplest and giving most control and safest is to generate a clean binding and use it to parse the ERB. Here's my take on it (ruby 2.2.x):
module B
def self.clean_binding
binding
end
def self.binding_from_hash(**vars)
b = self.clean_binding
vars.each do |k, v|
b.local_variable_set k.to_sym, v
end
return b
end
end
my_nice_binding = B.binding_from_hash(a: 5, **other_opts)
result = ERB.new(template).result(my_nice_binding)
I think with eval
and without **
same can be made working with older ruby than 2.1
Upvotes: 1
Reputation: 67910
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)
.
Upvotes: 69
Reputation: 3573
Simple solution using Binding:
b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)
Upvotes: 31
Reputation: 147
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/
Upvotes: 7
Reputation: 19463
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"...
Upvotes: 0
Reputation: 19463
Got it!
I create a bindings class
class BindMe
def initialize(key,val)
@key=key
@val=val
end
def get_binding
return binding()
end
end
and pass an instance to ERB
dataHash.keys.each do |current|
key = current.to_s
val = dataHash[key]
# here, I pass the bindings instance to ERB
bindMe = BindMe.new(key,val)
result = template.result(bindMe.get_binding)
# unnecessary code goes here
end
The .erb template file looks like this:
Key: <%= @key %>
Upvotes: 11