74 votes

Rendu d'un modèle ERB avec les valeurs d'un hachage

Je dois négliger quelque chose de très simple, mais je n'arrive pas à trouver comment rendre un modèle ERB simple avec les valeurs d'une carte de hachage.

Je suis relativement novice en ruby, venant de python. J'ai un modèle ERB (pas HTML), qui doit être rendu avec le contexte qui doit être pris à partir d'une carte de hachage, que je reçois d'une source externe.

Cependant, la documentation de l'ERB, indique que la ERB.result La méthode prend un binding . J'ai appris qu'ils sont quelque chose qui contient les contextes de variables dans ruby (quelque chose comme locals() y globals() en python, je présume ?). Mais, je ne sais pas comment je peux construire un objet de liaison à partir de ma carte de hachage.

Un peu (un lot en fait), une recherche sur Internet m'a donné ça : http://refactormycode.com/codes/281-given-a-hash-of-variables-render-an-erb-template qui utilise une magie de métaprogrammation Ruby qui m'échappe.

Alors, n'y a-t-il pas une solution simple à ce problème ? Ou existe-t-il un meilleur moteur de création de modèles (non lié à HTML) mieux adapté à ce problème ? (J'ai choisi ERB uniquement parce qu'il est dans la stdlib).

0 votes

Je ne connais pas de moteurs de modèles Ruby qui soient "liés" au HTML ; un modèle est un modèle. Je ne suis pas sûr non plus de ce qui ne va pas dans la solution dont vous donnez le lien - est-ce que le problème est de placer le hash dans le module ?

0 votes

Dave, il n'y a rien de mal à cela en tant que tel. J'ai juste pensé qu'il pourrait y avoir une solution plus élégante pour un problème aussi simple que d'aller jusqu'à utiliser la métaprogrammation.

1 votes

82voto

Moriarty Points 623
require 'erb'
require 'ostruct'

def render(template, vars)
  ERB.new(template).result(OpenStruct.new(vars).instance_eval { binding })
end

Par exemple

render("Hey, <%= first_name %> <%= last_name %>", first_name: "James", last_name: "Moriarty")
# => "Hey, James Moriarty" 

Mise à jour :

Un exemple simple sans ERB :

def render(template, vars)
  eval template, OpenStruct.new(vars).instance_eval { binding }
end

par exemple

render '"Hey, #{first_name} #{last_name}"', first_name: "James", last_name: "Moriarty"
# => "Hey, James Moriarty

Mise à jour 2 : vérifier le commentaire de @adam-spiers ci-dessous.

3 votes

+1, c'est une façon très élégante de créer une liaison à partir d'un hachage.

12 votes

Cela semble agréable au premier abord, mais malheureusement, la liaison hérite également des locaux et des méthodes du contexte dans lequel l'élément OpenStruct est instancié. Cela permet au modèle d'accéder à bien plus de choses que prévu, et peut entraîner des bogues ou même une faille de sécurité, par ex. gist.github.com/aspiers/ad6549058ee423819976

62voto

Tom Copeland Points 736

Ruby 2.5 a ERB#résultat_avec_hash qui fournit cette fonctionnalité :

$ ruby -rerb -e 'p ERB.new("Hi <%= name %>").result_with_hash(name: "Tom")'
"Hi Tom"

61voto

Dave Newton Points 93112

Je ne sais pas si cela peut être qualifié de "plus élégant" ou non :

require 'erb'
require 'ostruct'

class ErbalT < OpenStruct
  def render(template)
    ERB.new(template).result(binding)
  end
end

et = ErbalT.new({ :first => 'Mislav', 'last' => 'Marohnic' })
puts et.render('Name: <%= first %> <%= last %>')

Ou à partir d'une méthode de classe :

class ErbalT < OpenStruct
  def self.render_from_hash(t, h)
    ErbalT.new(h).render(t)
  end

  def render(template)
    ERB.new(template).result(binding)
  end
end

template = 'Name: <%= first %> <%= last %>'
vars = { :first => 'Mislav', 'last' => 'Marohnic' }
puts ErbalT::render_from_hash(template, vars)

(ErbalT signifie Erb, T pour modèle, et sonne comme "tisane". Nommer les choses est difficile).

1 votes

Merci Dave. Oui, cette solution semble un peu meilleure, bien que je n'aie aucune idée sur OpenStruct . J'irai jusqu'à la documentation, sans problème :)

1 votes

@ShrikantSharat c'est en fait plus ou moins la même chose, mais c'est OS qui fait le méta.

32voto

Phrogz Points 112337

Si vous pouvez utiliser Erubis vous disposez déjà de cette fonctionnalité :

irb(main):001:0> require 'erubis'
#=> true
irb(main):002:0> locals = { first:'Gavin', last:'Kistner' }
#=> {:first=>"Gavin", :last=>"Kistner"}
irb(main):003:0> Erubis::Eruby.new("I am <%=first%> <%=last%>").result(locals)
#=> "I am Gavin Kistner"

0 votes

Merci pour cette suggestion, Phrongz. Mais je pense que je vais m'en tenir à stdlib pour cette fois.

5 votes

C'est pratique puisque Rails 4 utilise désormais Erubis for ERB pour son moteur de rendu.

0 votes

Pour ceux qui utilisent Rails, celui-ci semble le meilleur

9voto

Lev Lukomsky Points 1079

La partie délicate ici est de ne pas polluer la liaison avec des variables locales redondantes (comme dans les réponses les mieux notées) :

require 'erb'

class TemplateRenderer
  def self.empty_binding
    binding
  end

  def self.render(template_content, locals = {})
    b = empty_binding
    locals.each { |k, v| b.local_variable_set(k, v) }

    # puts b.local_variable_defined?(:template_content) #=> false

    ERB.new(template_content).result(b)
  end
end

# use it
TemplateRenderer.render('<%= x %> <%= y %>', x: 1, y: 2) #=> "1 2"

# use it 2: read template from file
TemplateRenderer.render(File.read('my_template.erb'), x: 1, y: 2)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X