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