可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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)
.
回答2:
Simple solution using Binding:
b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)
回答3:
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 %>
回答4:
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.)
回答5:
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/
回答6:
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.
回答7:
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"...
回答8:
This article explains this nicely.
http://www.garethrees.co.uk/2014/01/12/create-a-template-rendering-class-with-erb/
回答9:
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