Шаблоны 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 endThe .файл шаблона 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
Я не могу дать вам очень хороший ответ о том, почему это происходит, потому что я не на 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