57

我有一个内嵌到 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)

我该如何解决?

4

10 回答 10

68

对于一个简单的解决方案,请使用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).

于 2011-03-28T16:27:11.873 回答
30

使用Binding的简单解决方案:

b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)
于 2015-01-18T02:53:30.563 回答
11

知道了!

我创建了一个绑定类

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 %>
于 2009-08-27T13:35:26.250 回答
8

在原始问题的代码中,只需替换

result = template.result

result = template.result(binding)

这将使用每个块的上下文而不是顶级上下文。

(刚刚提取了@sciurus 的评论作为答案,因为它是最短和最正确的。)

于 2016-01-06T18:32:02.750 回答
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

参考:http ://stoneship.org/essays/erb-and-the-context-object/

于 2014-01-08T00:25:34.970 回答
4

关于为什么会发生这种情况,我不能给你一个很好的答案,因为我不是 100% 确定 ERB 是如何工作的,但只是看看ERB RDocs,它说你需要一个binding“绑定或 Proc 对象用于设置代码评估的上下文”。

再次尝试上面的代码并替换

result = template.result

result = template.result(binding)

让它工作。

我确定/希望有人会跳到这里并提供更详细的解释。干杯。

编辑:有关更多信息Binding并使所有这些更清晰(至少对我而言),请查看Binding RDoc

于 2009-08-27T05:14:48.070 回答
1

正如其他人所说,要使用一组变量评估 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 更旧的 ruby

于 2015-12-28T21:45:19.560 回答
1

也许最干净的解决方案是将特定的current局部变量传递给 erb 模板,而不是传递整个binding. 可以使用ERB#result_with_hash方法(在 Ruby 2.5 中引入)

DATA.keys.each do |current|
  result = template.result_with_hash(current: current)
...
于 2020-01-03T13:49:48.093 回答
0

编辑:这是一个肮脏的解决方法。请看我的另一个回答。

这完全奇怪,但添加

current = ""

在“for-each”循环解决问题之前。

上帝保佑脚本语言及其“语言特性”...

于 2009-08-27T09:37:35.900 回答
0

这篇文章很好地解释了这一点。

http://www.garethrees.co.uk/2014/01/12/create-a-template-rendering-class-with-erb/

于 2015-04-06T20:59:26.240 回答