dev @ purchease
comment nous codons chez purchease
Association Polymorphique
Publié par david le 25/09/2020 - railsMotivation
Sur l’application FidMarques, on offre aux utilisateurs des mécaniques différentes de fidélisation. A titre d’exemple, on récompense nos utilisateurs par un point de fidélité pour chaque Euro dépensé sur la marque ou bien par une pièce de puzzle pour chaque produit différent acheté sur la marque.
Comme les mécaniques sont très différentes, on aura choisi des modèles distincts pour les représenter. Malgré tout, on aura envie d’associer ces modèles, par exemple à un utilisateur, avec une abstraction indépendante du modèle. Typiquement, un utilisateur aura des cartes de fidélités (certaines étant des souscriptions à un programme à point, certaines étant des souscriptions à un programme puzzle). Il serait fastidieux, partout dans le code de devoir les différencier, et compliqué d’ajouter une nouvelle mécanique.
Fonctionnement en pratique
Considérons le modèle suivant :
class User < ApplicationRecord
end
On cherche maintenant à représenter la souscription à un programme ( il s’agit en fait d’un modèle de jointure entre un utilisateur et un programme ) ; Voilà ce que l’on a envie d’écrire :
class LoyaltyProgramUser < ApplicationRecord
belongs_to :user
belongs_to :loyalty_program
end
Le chemin de l’héritage
Ca serait terminé si on avait un modèle LoyaltyProgram. On pourrait tout à fait imaginer le modèle suivant :
class LoyaltyProgram < ApplicationRecord
end
class LoyaltyProgramWithPoints < LoyaltyProgram
end
class LoyaltyProgramWithPuzzle < LoyaltyProgram
end
On a terminé ! Les deux types de programme héritent d’une même classe : l’héritage en rails nous permet de faire çà, qui se traduira par l’existence d’une table loyalty_programs, avec une colonne type permettant de spécifier la sous-classe. Cette solution est très satisfaisante dans de nombreuses situations. Il existe un écueil : si nos modèles’fils’ ont très peu de caractéristiques communes, la table devrait accueillir l’union des attributs nécessaires au fonctionnement de chacun d’eux, dont la moitié sera inutile selon quelle classe est instanciée.
Il y a un autre chemin
Partons donc du principe que nos modèles sont différents :
class LoyaltyProgramWithPoints < ApplicationRecord
end
class LoyaltyProgramWithPuzzle < ApplicationRecord
end
On définira notre relation ainsi :
class LoyaltyProgramUser < ApplicationRecord
belongs_to :user
belongs_to :loyalty_program, polymorphic: true
end
On pourra également définir la relation inverse en spécifiant un nom ‘générique’ :
class LoyaltyProgramWithPoints < ApplicationRecord
has_many :loyalty_program_users, as: loyalty_program
end
class LoyaltyProgramWithPuzzle < ApplicationRecord
has_many :loyalty_program_users, as: loyalty_program
end
Et en base de données ?
Les migrations sous-jacentes ressembleront à ca ;
class CreateLoyaltyProgramUser < ActiveRecord::Migration[5.2]
def change
create_table :loyalty_program_users do |t|
t.bigint :user_id
t.bigint :loyalty_program_id
t.string :loyalty_program_type
t.timestamps
end
add_index :loyalty_program, [:loyalty_program_type, :loyalty_program_id]
end
end
( On n’oublie pas l’index sur les deux colonnes !!! ).
Pour ceux qui veulent briller en société, il existe un raccourci :
class CreatePictures < ActiveRecord::Migration[5.0]
def change
create_table :pictures do |t|
t.string :name
t.references :loyalty_program, polymorphic: true
t.timestamps
end
end
end
Reférence : https://guides.rubyonrails.org/association_basics.html#polymorphic-associations