J'ai un modèle ERB intégré dans Ruby code:
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
Je ne peux pas passer la variable "actuelle" dans le modèle.
L'erreur est:
(erb):1: undefined local variable or method `current' for main:Object (NameError)
Comment puis-je réparer ça?
Je l'ai!
Je crée une classe de liaisons
class BindMe
def initialize(key,val)
@key=key
@val=val
end
def get_binding
return binding()
end
end
et passer une instance à 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
Le fichier de modèle .erb ressemble à ceci:
Key: <%= @key %>
Pour une solution simple, utilisez 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
Le code ci-dessus est assez simple mais a (au moins) deux problèmes: 1) Puisqu'il repose sur OpenStruct
, un accès à une variable non existante renvoie nil
alors que vous préféreriez probablement que il a échoué bruyamment. 2) binding
est appelé dans un bloc, c'est tout, dans une fermeture, donc il inclut toutes les variables locales dans la portée (en fait, ces variables vont masquer les attributs de la structure!).
Voici donc une autre solution, plus détaillée mais sans aucun de ces problèmes:
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
Bien sûr, si vous comptez l'utiliser souvent, assurez-vous de créer une extension String#erb
Qui vous permet d'écrire quelque chose comme "x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2)
.
Solution simple utilisant Binding :
b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)
Dans le code de la question d'origine, remplacez simplement
result = template.result
avec
result = template.result(binding)
Cela utilisera le contexte de chaque bloc plutôt que le contexte de niveau supérieur.
(Je viens d'extraire le commentaire de @sciurus comme réponse car c'est le plus court et le plus correct.)
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/
Je ne peux pas vous donner une très bonne réponse pour expliquer pourquoi cela se produit parce que je ne suis pas sûr à 100% du fonctionnement d'ERB, mais en regardant simplement ERB RDocs , cela dit que vous avez besoin d'un binding
qui est "un objet Binding ou Proc qui est utilisé pour définir le contexte de l'évaluation du code".
Réessayer le code ci-dessus et remplacer simplement
result = template.result
avec
result = template.result(binding)
l'a fait fonctionner.
Je suis sûr/j'espère que quelqu'un va intervenir ici et fournir une explication plus détaillée de ce qui se passe. À votre santé.
EDIT: Pour plus d'informations sur Binding
et pour rendre tout cela un peu plus clair (au moins pour moi), consultez le Binding RDoc .
EDIT : Ceci est une solution de contournement sale. Veuillez voir mon autre réponse.
C'est totalement étrange, mais en ajoutant
current = ""
avant la boucle "pour chaque" résout le problème.
Que Dieu bénisse les langages de script et leurs "fonctionnalités de langage" ...
Comme d'autres l'ont dit, pour évaluer ERB avec un ensemble de variables, vous avez besoin d'une liaison appropriée. Il existe des solutions pour définir des classes et des méthodes, mais je pense que le plus simple et le plus sûr et le plus sûr est de générer une liaison propre et de l'utiliser pour analyser l'ERB. Voici mon point de vue (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)
Je pense avec eval
et sans **
la même chose peut être faite en travaillant avec les anciens Ruby que 2.1
Cet article explique cela très bien.
http://www.garethrees.co.uk/2014/01/12/create-a-template-rendering-class-with-erb/