Шаблоны 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 53

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. Вот мой взгляд на это (Рубин 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