Ruby模板:如何将变量传递给内联ERB?

时间:2009-08-27 05:08:14

标签: ruby templates syntax erb

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

我该如何解决这个问题?

10 个答案:

答案 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

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

答案 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模板,而不是传递整个bindingERB#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