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

时间:2021-10-22 00:22:38

I have an ERB template inlined into Ruby code:

我有一个嵌入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

I can't pass the variable "current" into the template.

我不能将变量“current”传递到模板中。

The error is:

错误的是:

(erb):1: undefined local variable or method `current' for main:Object (NameError)

How do I fix this?

我怎么修复这个?

9 个解决方案

#1


57  

For a simple solution, use OpenStruct:

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

上面的代码非常简单,但是(至少)有两个问题:1)因为它依赖于OpenStruct,对一个不存在的变量的访问返回nil,而您可能更希望它失败得很大声。2)绑定是在块中调用的,也就是在闭包中调用的,因此它包含作用域中的所有局部变量(实际上,这些变量将隐藏结构的属性!)

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).

当然,如果您打算经常使用它,请确保创建了一个字符串#erb扩展,允许您编写“x=<%= x %>, y=<%= y %>”之类的内容。erb(x:1,y:2)。

#2


20  

Simple solution using Binding:

简单的解决方案使用绑定:

b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)

#3


10  

Got it!

得到它!

I create a bindings class

我创建了一个bindings类

class BindMe
    def initialize(key,val)
        @key=key
        @val=val
    end
    def get_binding
        return binding()
    end
end

and pass an instance to ERB

并将实例传递给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:

.erb模板文件如下:

Key: <%= @key %>

#4


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/

裁判:http://stoneship.org/essays/erb-and-the-context-object/

#5


5  

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.)

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

#6


4  

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.

我不能给你一个很好的回答,这是为什么,因为我不是100%肯定ERB是如何工作的,只是看着ERB出来时,它说,你需要一个绑定是一个绑定或Proc对象用于设置上下文代码评估。再次尝试上面的代码并替换result = template。result = template.result(绑定)使其工作。

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.

编辑:要了解更多关于绑定的信息,并使所有这些信息更清晰(至少对我而言),请查看绑定RDoc。

#7


0  

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.

在“for-each”循环修复问题之前。

God bless scripting languages and their "language features"...

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

#8


0  

This article explains this nicely.

本文很好地解释了这一点。

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

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

#9


0  

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):

正如其他人所说,要用一些变量来评估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)

I think with eval and without ** same can be made working with older ruby than 2.1

我认为使用eval和不使用** *的方法都可以使用比2.1更老的ruby

#1


57  

For a simple solution, use OpenStruct:

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

上面的代码非常简单,但是(至少)有两个问题:1)因为它依赖于OpenStruct,对一个不存在的变量的访问返回nil,而您可能更希望它失败得很大声。2)绑定是在块中调用的,也就是在闭包中调用的,因此它包含作用域中的所有局部变量(实际上,这些变量将隐藏结构的属性!)

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).

当然,如果您打算经常使用它,请确保创建了一个字符串#erb扩展,允许您编写“x=<%= x %>, y=<%= y %>”之类的内容。erb(x:1,y:2)。

#2


20  

Simple solution using Binding:

简单的解决方案使用绑定:

b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)

#3


10  

Got it!

得到它!

I create a bindings class

我创建了一个bindings类

class BindMe
    def initialize(key,val)
        @key=key
        @val=val
    end
    def get_binding
        return binding()
    end
end

and pass an instance to ERB

并将实例传递给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:

.erb模板文件如下:

Key: <%= @key %>

#4


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/

裁判:http://stoneship.org/essays/erb-and-the-context-object/

#5


5  

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.)

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

#6


4  

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.

我不能给你一个很好的回答,这是为什么,因为我不是100%肯定ERB是如何工作的,只是看着ERB出来时,它说,你需要一个绑定是一个绑定或Proc对象用于设置上下文代码评估。再次尝试上面的代码并替换result = template。result = template.result(绑定)使其工作。

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.

编辑:要了解更多关于绑定的信息,并使所有这些信息更清晰(至少对我而言),请查看绑定RDoc。

#7


0  

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.

在“for-each”循环修复问题之前。

God bless scripting languages and their "language features"...

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

#8


0  

This article explains this nicely.

本文很好地解释了这一点。

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

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

#9


0  

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):

正如其他人所说,要用一些变量来评估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)

I think with eval and without ** same can be made working with older ruby than 2.1

我认为使用eval和不使用** *的方法都可以使用比2.1更老的ruby