Шаблоны Ruby: как передать переменные в встроенный ERB?

у меня есть шаблон ERB, встроенный в Ruby-код:

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)

Как это исправить?

9 ответов


для простого решения используйте 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).


простое решение с помощью обязательные:

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

The .файл шаблона 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

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


Я не могу дать вам очень хороший ответ о том, почему это происходит, потому что я не на 100% уверен, как работает ERB, но просто глядя на ERB RDocs, он говорит, что вам нужно binding что это a Binding or Proc object which is used to set the context of code evaluation. повторите попытку кода выше и просто замените result = template.result С result = template.result(binding) сделал свою работу.

Я уверен/надеюсь, что кто-то прыгнет сюда и предоставит более подробное объяснение того, что происходит. Овации.

EDIT: для получения дополнительной информации о Binding и что делает все это немного яснее (по крайней мере для меня), проверьте Привязка RDoc.


редактировать: это грязный обходной путь. Пожалуйста, посмотрите мой другой ответ.

это совершенно странно, но добавление

current = ""

прежде чем цикл" для каждого " исправит проблему.

Благослови Бог скриптовые языки и их "языковые особенности"...


эта статья объясняет это красиво.

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


как говорили другие, чтобы оценить 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