web-dev-qa-db-fra.com

Ruby templates: comment passer des variables dans ERB en ligne?

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?

53

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 %>
10

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).

60
tokland

Solution simple utilisant Binding :

b = binding
b.local_variable_set(:a, 'a')
b.local_variable_set(:b, 'b')
ERB.new(template).result(b)
25
asfer

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.)

8
geekQ
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
alvin2ye

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 .

4
theIV

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" ...

0

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

0
akostadinov
0
Alex Levine