Шаблоны 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)
Как это исправить?
Ответы
Ответ 1
Получил это!
Я создаю класс привязок
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 %>
Ответ 2
Для простого решения используйте 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
вызывается внутри блока, что он в замыкании, поэтому он включает в себя все локальные переменные в области видимости (на самом деле эти переменные будут затенять атрибуты struct!).
Итак, вот еще одно решение, более подробное, но без каких-либо из этих проблем:
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)
.
Ответ 3
Простое решение с использованием Binding:
b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)
Ответ 4
В коде исходного вопроса просто замените
result = template.result
с
result = template.result(binding)
Это будет использовать контекст каждого блока, а не контекст верхнего уровня.
(Просто извлек комментарий @sciurus в качестве ответа, потому что он самый короткий и самый правильный.)
Ответ 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/
Ответ 6
Я не могу дать вам очень хороший ответ относительно того, почему это происходит, потому что я не на 100% уверен, как работает ERB, но просто глядя на ERB RDocs, он говорит, что вам нужен binding
, который является "объектом Binding или Proc, который используется для установки контекста оценки кода".
Еще раз попробуйте приведенный выше код и просто замените
result = template.result
с
result = template.result(binding)
заставил это работать.
Я уверен/надеюсь, что кто-нибудь сюда заскочит и предоставит более подробное объяснение того, что происходит. Приветствия.
ОБНОВЛЕНИЕ: Для получения дополнительной информации о Binding
и сделать все это немного яснее (по крайней мере для меня), посмотрите Binding RDoc.
Ответ 7
РЕДАКТИРОВАТЬ. Это грязное обходное решение. См. Мой другой ответ.
Это совершенно странно, но добавив
current = ""
прежде чем цикл "for-each" фиксирует проблему.
Бог благословляет языки сценариев и их "языковые особенности"...
Ответ 8
Эта статья прекрасно объясняет это.
http://www.garethrees.co.uk/2014/01/12/create-a-template-rendering-class-with-erb/
Ответ 9
Как говорили другие, для оценки 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