簡體   English   中英

Ruby 模板:如何將變量傳遞到內聯 ERB?

[英]Ruby templates: How to pass variables into inlined 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)

我該如何解決?

對於簡單的解決方案,請使用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)

使用Binding 的簡單解決方案:

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

知道了!

我創建了一個綁定類

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

在原始問題的代碼中,只需替換

result = template.result

result = template.result(binding)

這將使用每個塊的上下文而不是頂級上下文。

(剛剛提取了@sciurus 的評論作為答案,因為它是最短和最正確的評論。)

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/

我不能給你一個很好的答案,為什么會發生這種情況,因為我不是 100% 確定 ERB 是如何工作的,但只是看看ERB RDocs ,它說你需要一個binding ,它是“一個綁定或過程用於設置代碼評估上下文的對象”。

再次嘗試上面的代碼,只是替換

result = template.result

result = template.result(binding)

使它工作。

我確定/希望有人會跳到這里並提供有關正在發生的事情的更詳細的解釋。 干杯。

編輯:有關Binding更多信息並使所有這些更清晰(至少對我而言),請查看Binding RDoc

也許最干凈的解決方案是將特定的current局部變量傳遞給 erb 模板,而不是傳遞整個binding 可以使用ERB#result_with_hash方法(在 Ruby 2.5 中引入)

DATA.keys.each do |current|
  result = template.result_with_hash(current: current)
...

編輯:這是一個骯臟的解決方法。 請看我的另一個回答。

這很奇怪,但添加

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和不使用**可以使用比 2.1 更舊的 ruby

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM