Подтвердить что ты не робот

Шаблоны 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)

Как это исправить?

4b9b3361

Ответ 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" фиксирует проблему.

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

Ответ 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