Archives de catégorie : Ruby On Rails

Ruby on Rails: design pattern Factory

Pour traiter du design pattern Factory en Ruby (dans le framework Rails), j’ai repris les exemples faits en PHP il y a déjà 7 ans !

Comme les principes ne diffèrent pas d’un langage à l’autre, seul le code figurera ici. Pour les explications, je vous renvoie au lien ci-dessus !

# on importe de quoi utiliser constantize
require 'active_support/inflector'

# l'interface des fabriques (ici une classe abstraite)
class FabriqueFacture
  def initialize
    raise "Impossible d'instancier la classe abstraite FabriqueFacture"
  end

  def fabriquer
    raise NotImplementedError
  end
end

# les fabriques concrètes
class FabriqueEnteteFacture < FabriqueFacture
  CLASSE_CIBLE = 'Entete'
  private_constant :CLASSE_CIBLE

  def initialize
    # on écrase le constructeur qui lève une exception dans la classe mère
  end

  def fabriquer
    CLASSE_CIBLE.constantize.new
  end
end

class FabriqueCorpsFacture < FabriqueFacture
  CLASSE_CIBLE = 'Corps'
  private_constant :CLASSE_CIBLE

  def initialize(produits)
    @produits = produits
  end

  def fabriquer
    CLASSE_CIBLE.constantize.new(@produits)
  end

  private

  attr_accessor :produits
end

class FabriquePiedPageFacture < FabriqueFacture
  CLASSE_CIBLE = 'PiedPage'
  private_constant :CLASSE_CIBLE

  def initialize
    # même remarque que pour le constructeur de FabriqueEnteteFacture
  end

  def fabriquer
    CLASSE_CIBLE.constantize.new
  end
end

# les produits CONCRETS
class PiedPage
  def formater
    "Je formate mon pied de page"
  end
end

class Entete
  def formater()
    "Je formate mon entête"
  end
end

class Corps
  def initialize (produits)
    @produits = produits
  end

  def formater
    "Je formate mon corps avec mes #{@produits.count} produits"
  end
end

# le code client pour tester toute la chaîne

class Facturation
  def initialize(fabrique_entete, fabrique_corps, fabrique_pied)
    if !((fabrique_entete.is_a? FabriqueFacture) && 
        (fabrique_corps.is_a? FabriqueFacture) && 
        (fabrique_pied.is_a? FabriqueFacture))
      raise "Les fabriques doivent dériver FabriqueFacture"
    end
    @entete = fabrique_entete.fabriquer
    @corps = fabrique_corps.fabriquer
    @pied = fabrique_pied.fabriquer
  end

  def declencher
    puts @entete.formater
    puts @corps.formater
    puts @pied.formater
  end

  private

  attr_accessor :entete, :corps, :pied
end

produits = ['Gourde', 'Ballon', 'Pioche']

facturation = Facturation.new(
    FabriqueEnteteFacture.new,
    FabriqueCorpsFacture.new(produits),
    FabriquePiedPageFacture.new
)

facturation.declencher

Ruby On Rails – utiliser first_or_create

Lorsque vous utilisez ActiveRecord, vous vous intéressez forcément tôt ou tard à la fonction first_or_create et en général, vous l’utiliser comme suit:


MonJoliModele.first_or_create(attribut: "unevaleur")

Ce qu’on pense faire…

En procédant ainsi, vous pensez « Soit un enregistrement avec comme valeur du champ attribut « unevaleur » est trouvé, soit je vais en créer un ». Et bien non ! Ouvrez-vous un rails console et regardez ce qui se passe en réalité. Et oui, Rails prend le premier enregistrement de la table liée au modèle, sans même tenir compte de la valeur du champ « attribut » que vous lui donnez, pensant logiquement qu’il allait en faire quelque chose…Évidemment si votre table est vide alors Rails créera cet enregistrement avec la valeur passée en paramètre, mais c’est loin d’être tout le temps le cas.

Développeur sceptique: ne me dites pas que vous n'avez jamais fait cette tête, vous mentiriez !

Développeur sceptique: ne me dites pas que vous n’avez jamais fait cette tête, vous mentiriez !


Ce que vous vouliez faire en réalité, c’était:

MonJoliModele.where(attribut: "unevaleur").first_or_create(attribut: "unevaleur")

ou, plus élégant :

critere = {attribut: "unevaleur"}
MonJoliModele.where(critere).first_or_create(critere)

Attention donc aux effets de bord possiblement dus à la mauvaise utilisation de cette fonction. Notez qu’on peut utiliser un bloc aussi. Mais attention, il n’est exécuté qu’en cas de création d’un nouvel enregistrement et vous l’utiliserez évidemment si vous avez de nombreuses affectations à y faire, et pas forcément deux comme dans mon exemple:


critere = {attribut: "unevaleur"}
MonJoliModele.where(critere).first_or_create do |modele|
modele.autreattribut = "une autre valeur"
modele.encoreunautreattribut = "encore une autre valeur"
end

Voilà donc pour ce crash course sur l’utilisation de first_or_create, fonction dont le nom assez ambigu peut être source de confusion et qui en même temps nous épargne d’avoir à faire un find_by suivi d’un create, ce qui n’est ni très esthétique, ni très performant !