web-dev-qa-db-ja.com

Ruby templates:変数をインライン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)

どうすれば修正できますか?

とった!

バインディングクラスを作成します

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 %>

簡単な解決策として、 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
_

上記のコードは非常に単純ですが、(少なくとも)2つの問題があります。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
_

もちろん、これを頻繁に使用する場合は、"x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2)のようなものを記述できる_String#erb_拡張を作成してください。

60
tokland

Binding を使用した簡単なソリューション:

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

元の質問のコードでは、単に置き換える

result = template.result

result = template.result(binding)

これは、トップレベルのコンテキストではなく、各ブロックのコンテキストを使用します。

(コメントが@sciurusによって抽出されたのは、最短で最も正確なコメントだからです。)

8
geekQ
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
alvin2ye

私はERBがどのように機能するかを100%確信していないので、なぜこれが起こっているのかについて非常に良い答えを与えることはできませんが、単に ERB RDocs を見て、bindingが必要だと言いますこれは、「コード評価のコンテキストを設定するために使用されるBindingまたはProcオブジェクト」です。

上記のコードをもう一度試して、単に置き換える

result = template.result

result = template.result(binding)

動作させた。

誰かがここに飛び込んで、何が起こっているかについてのより詳細な説明を提供してくれると確信しています。乾杯。

編集:Bindingの詳細と、これをすべて明確にするために(少なくとも私にとって)、 Binding RDoc をチェックしてください。

4
theIV

[〜#〜] edit [〜#〜]:これは汚い回避策です。私の他の答えをご覧ください。

それは全く奇妙ですが、追加します

current = ""

「for-each」ループが問題を修正する前。

神の祝福スクリプト言語とその「言語機能」...

他の人が言ったように、いくつかの変数セットで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よりも

0
akostadinov

この記事ではこれをうまく説明しています。

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

0
Alex Levine