我有一个内联到Ruby代码的ERB模板:
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
我无法将变量“current”传递给模板。
错误是:
(erb):1: undefined local variable or method `current' for main:Object (NameError)
我该如何解决这个问题?
答案 0 :(得分:59)
对于简单的解决方案,请使用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
上面的代码很简单但有(至少)两个问题:1)由于它依赖于OpenStruct
,因此访问不存在的变量会返回nil
,而您可能更喜欢它失败了吵闹。 2)binding
在一个块中被调用,就是它,在一个闭包中,所以它包含了作用域中的所有局部变量(事实上,这些变量将遮蔽结构的属性!)。
所以这是另一个解决方案,更详细,但没有任何这些问题:
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
当然,如果您经常使用此功能,请务必创建一个String#erb
扩展程序,以便您编写类似"x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2)
的内容。
答案 1 :(得分:24)
使用Binding的简单解决方案:
b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)
答案 2 :(得分:10)
知道了!
我创建了一个绑定类
class BindMe
def initialize(key,val)
@key=key
@val=val
end
def get_binding
return binding()
end
end
并将实例传递给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
.erb模板文件如下所示:
Key: <%= @key %>
答案 3 :(得分:7)
在原始问题的代码中,只需替换
result = template.result
带
result = template.result(binding)
这将使用每个块的上下文而不是顶级上下文。
(刚刚将@sciurus的评论作为答案提取出来,因为它是最短且最正确的评论。)
答案 4 :(得分:6)
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
答案 5 :(得分:4)
我不能给你一个非常好的答案,为什么会发生这种情况,因为我不是100%确定ERB是如何工作的,但只是看ERB RDocs,它说你需要一个{{ 1}} binding
再次尝试上述代码,只需将a Binding or Proc object which is used to set the context of code evaluation.
替换为result = template.result
即可。
我确信/希望有人会跳进这里并提供更详细的解释。欢呼声。
编辑:有关result = template.result(binding)
的更多信息并使所有这些更清楚(至少对我而言),请查看Binding RDoc。
答案 6 :(得分:1)
也许最干净的解决方案是将特定的current
局部变量传递给erb模板,而不是传递整个binding
。 ERB#result_with_hash方法(在Ruby 2.5中引入)是可能的
DATA.keys.each do |current|
result = template.result_with_hash(current: current)
...
答案 7 :(得分:0)
编辑:这是一个肮脏的解决方法。请看我的其他答案。
这很奇怪,但添加了
current = ""
在“for-each”循环修复问题之前。
上帝保佑脚本语言及其“语言特征”...
答案 8 :(得分:0)
答案 9 :(得分:0)
正如其他人所说,要使用一些变量来评估ERB,您需要一个适当的绑定。有一些定义类和方法的解决方案,但我认为最简单,最大程度的控制和最安全的是生成一个干净的绑定并使用它来解析ERB。这是我对它的看法(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)
我认为使用eval
并且没有**
可以使用旧的ruby而不是2.1