Шаблоны Ruby: Как передать переменные во встроенный ERB? - PullRequest
53 голосов
/ 27 августа 2009

У меня есть шаблон 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 ]

59 голосов
/ 28 марта 2011

Для простого решения используйте 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).

24 голосов
/ 18 января 2015

Простое решение с использованием Связывание :

b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)
10 голосов
/ 27 августа 2009

Понял!

Я создаю класс привязок

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 %>
7 голосов
/ 06 января 2016

В коде из исходного вопроса просто замените

result = template.result

с

result = template.result(binding)

Это будет использовать контекст каждого блока, а не контекст верхнего уровня.

(Просто извлек комментарий @sciurus как ответ, потому что он самый короткий и правильный).

6 голосов
/ 08 января 2014
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/

4 голосов
/ 27 августа 2009

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

Я уверен / надеюсь, что кто-нибудь сюда прыгнет и предоставит более подробное объяснение того, что происходит. Приветствия.

РЕДАКТИРОВАТЬ: Для получения дополнительной информации о Binding и сделать все это немного яснее (по крайней мере для меня), проверьте Binding RDoc .

0 голосов
/ 29 декабря 2015

Как уже говорили другие, для оценки 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

0 голосов
/ 06 апреля 2015

Эта статья хорошо объясняет это.

http://www.garethrees.co.uk/2014/01/12/create-a-template-rendering-class-with-erb/

0 голосов
/ 27 августа 2009

РЕДАКТИРОВАТЬ : Это грязный обходной путь. Пожалуйста, смотрите мой другой ответ.

Это странно, но добавление

current = ""

до того, как цикл «для каждого» решит проблему.

Да благословит Бог языки сценариев и их "языковые особенности" ...

...