2009-08-27 11 views
50

Mam szablon ERB inlined do kodu Ruby:Szablony Ruby: jak przekazywać zmienne do wbudowanego ERB?

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 

nie mogę przekazać zmienna „prąd” w szablonie.

Błąd jest:

(erb):1: undefined local variable or method `current' for main:Object (NameError) 

Jak mogę rozwiązać ten problem?

Odpowiedz

10

Got it!

utworzyć klasę powiązań

class BindMe 
    def initialize(key,val) 
     @key=key 
     @val=val 
    end 
    def get_binding 
     return binding() 
    end 
end 

i przekazać instancję do 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 plik szablon wygląda tak:

Key: <%= @key %> 
+8

To jest niepotrzebne. W kodzie z oryginalnego pytania, po prostu zastąp "result = template.result" z "result = template.result (binding)", który użyje kontekstu każdego bloku, a nie kontekstu najwyższego poziomu. – sciurus

4

Nie mogę dać ci bardzo dobrej odpowiedzi na pytanie, dlaczego tak się dzieje, ponieważ nie jestem w 100% pewny, jak działa ERB, ale po prostu patrząc na ERB RDocs, mówi, że potrzebujesz binding, który jest a Binding or Proc object which is used to set the context of code evaluation.. powyższy kod ponownie i po prostu zastąpienie result = template.result z result = template.result(binding) sprawiło, że działa.

Jestem pewna/mam nadzieję, że ktoś tu wskoczy i przedstawi bardziej szczegółowe wyjaśnienie tego, co się dzieje. Twoje zdrowie.

EDYCJA: Aby uzyskać więcej informacji na temat Binding i uczynić to wszystko trochę bardziej przejrzystym (przynajmniej dla mnie), sprawdź Binding RDoc.

0

EDIT: To jest brudne obejście. Zobacz moją drugą odpowiedź.

To zupełnie dziwne, ale dodanie

current = "" 

przed "dla-siebie" pętla rozwiązuje ten problem.

Bóg błogosławi języków skryptowych i ich funkcje "język" ...

+0

Myślę, że to dlatego, że parametry bloku nie są prawdziwe związany zmienne w Ruby 1.8. To zmieniło się w Rubim 1.9. –

+1

Domyślnym wiązaniem używanym przez ERB do oceny zmiennych jest powiązanie najwyższego poziomu. Twoja zmienna "current" nie istnieje w powiązaniu najwyższego poziomu, chyba że najpierw użyjesz go tam (przypisz do niego wartość). – molf

+0

Więc w Rubim 1.9 to nie zadziała? –

57

Dla prostego rozwiązania, użyj 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 

Powyższy kod jest dość prosty, ale ma (przynajmniej) dwa problemy: 1) Ponieważ opiera się na OpenStruct, dostęp do nieistniejącej zmiennej zwraca nil, podczas gdy prawdopodobnie wolałbyś, żeby nie zawiódł. 2) binding jest wywoływany w bloku, to jest to, w zamknięciu, więc zawiera wszystkie zmienne lokalne w zakresie (w rzeczywistości te zmienne będą zacieniały atrybuty struktury!).

Więc tutaj jest inne rozwiązanie, bardziej gadatliwy, ale bez żadnego z tych problemów:

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 

Oczywiście, jeśli masz zamiar używać tego często, upewnij się stworzyć String#erb rozszerzenie, które pozwala na pisanie coś w rodzaju "x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2).

+0

Czy to testujesz? W moim systemie twój dokładny kod generuje "NameError: undefined local variable or method" name "dla main: Object." (Edycja: Wydaje się być wersją 1.9.2 http://stackoverflow.com/questions/3242470/problem-using-openstruct-with-erb) –

+0

@Ryan. Rzeczywiście, przetestowałem to tylko 1.8.7, zaktualizowano. Dodam odpowiedź na pytanie, które łączysz, myślę, że 'instance_eval' jest najłatwiejszym rozwiązaniem. Dziękuję za rozwiązanie problemu. – tokland

+0

@Ryan, dodano nowe rozwiązanie w http://stackoverflow.com/a/8293786/188031. – tokland

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/

19

Proste rozwiązanie wykorzystujące Binding:

b = binding 
b.local_variable_set(:a, 'a') 
b.local_variable_set(:b, 'b') 
ERB.new(template).result(b) 
+1

' local_variable_set' został wprowadzony w Ruby 2.1. – kbrock

0

Jak mówili inni, aby ocenić ERB z jakiegoś zestawu zmiennych, trzeba właściwego wiązania. Istnieje kilka rozwiązań definiujących klasy i metody, ale myślę, że najprostsze i zapewniające największą kontrolę i najbezpieczniejsze jest wygenerowanie czystego wiązania i wykorzystanie go do analizy ERB. Oto moje zdanie na jej temat (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) 

myślę ze eval i bez ** same mogą być wykonane ze starszymi rubin niż 2,1

5

W kodzie z pierwotnego pytania, po prostu zastąpić

result = template.result 

z

result = template.result(binding) 

który będzie korzystał th w kontekście każdego bloku, a nie w kontekście najwyższego poziomu.

(Tylko wyodrębnione komentarz przez @sciurus jak odpowiedź, bo to najkrótsza i najbardziej prawidłowa.)

Powiązane problemy