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