Associations many-to-many Construction standard: Pour relier deux classes Alpha et Beta On indique l association dans les modèles de Alpha et Beta: app/model/alpha.rb: has_and_belong_to_many :beta app/model/beta.rb: has_and_belong_to_many :alpha On crée un modèle Alphas_Betas: Code: $ rails generate model alphas_betas alpha_id:integer beta_id:integer On enlève la clef primaire de ce modèle: db/migrate/*_create_alphas_betas.rb: create_table :alphas_betas, :id => false do t
Fonctionnalités But des associations On cherche à établir des raccourcis Avec le même modèle mais sans habtm, on peut écrire AlphaBeta.find_by_alpha_id(alphaobj.id).each do b_id b = Beta.find(b_id)... Avec habtm, on peut écrire: alphaobj.betas.each do b...
Intégrité de la table alphas_betas has_and_belong_to_many (habtm) dans les deux classes La table qui joint Alpha et Beta est automatiquement mise à jour Si habtm est déclaré dans la classe Alpha et qu un objet de cette classe est détruit, toutes les associations de cet objet sont automatiquement détruites dans alphas_betas Mais si on détruit un objet de la classe Beta, les associations ne sont pas détruites Pour quelles le soient, on ajoute habtm dans la classe Beta
Inflexions Pluriel/singulier Rails insiste pour utiliser des formes plurielles et singulier pour nommer les tables suivant le contexte cf. par moments AlphaBeta, ou alphas_betas,... On veut parfois nommer les associations autrement: hierarchy, depance Il faut alors indiquer à Rails qu il ne faut pas mettre de pluriel: config/initializers/inflections.rb: ActiveSupport::Inflector.inflections do inflect inflect.irregular hierarchy, hierarchy inflect.irregular beta, betas
Utilisation d une table join particulière Pour améliorer la lisibilité du programme on peut vouloir un autre nom que celui utiliser par défaut par Rails (ex: AlphasBetas) Dans ce cas, il faut préciser ce nom dans le habtm: app/models/alpha.rb: has_and_belongs_to_many :betas, :join_table => hierarchy app/models/betas.rb: has_and_belongs_to_many :alphas, :join_table => hierarchy Après la création de la table join: Code: $ rails generate model hierarchy alpha_id:integer beta_id:integer il faudra modifier le fichier généré si les inflexions n ont pas été écrites avant
Associations réflexives Cas Alpha=Beta Difficultées il faut changer le nom d un des attributs de la table join (car les deux clefs primaires sont les mêmes) Dans la classe Alpha, il on ne peut avoir qu une association passant par Hierarchy On a vu qu on avait besoin de deux associations pour pouvoir assurer l intégrité de la table join On va maintenant assurer l intégrité à la main en écrivant des fonctions appelées lors de la suppression d un élément de Alpha
Changement du nom des attributs Au lieu d utiliser une méthode betas (ou alphas) pour trouver les objets associés, on nomme la méthode assoc On précise que la classe d arrivée est Alpha Enfin, on nomme les deux clefs étrangères de la table hierarchy app/model/alpha.rb: class Alpha < ActiveRecord::Base has_and_belongs_to_many :assoc, #nom de la méthode de Alpha :class_name => Alpha, # Classe d arrivée de :assoc :join_table => hierarchy, :foreign_key => alpha_id, :association_foreign_key => association_id Cette déclaration correspond à une construction: Code: $ rails generate model hierarchy alpha_id:integer association_id:integer
Intégrité de la table join L option after_add permet de spécifier une méthode qui sera appellée lorsqu une association est ajoutée à un objet o o est le self de cette méthode, et a (le paramètre de la fonction) est l objet associé à o Si on associe a à o, alors on associe aussi (a.assoc «o) o à a Idem lorsqu on enlève une association app/model/alpha.rb: class Alpha < ActiveRecord::Base has_and_belongs_to_many :assoc, #nom de la méthode de Alpha :class_name => Alpha, # Classe d arrivée de :assoc :join_table => hierarchy, :foreign_key => alpha_id, :association_foreign_key => association_id, :after_add => :create_reverse_association, :after_remove => :remove_reverse_association def create_reverse_association(a) a.assoc << self unless a.assoc.include?(self) def remove_reverse_association(a) a.assoc.delete(self) if a.assoc.include?(self)
Quelques remarques Limitations de habtm: La table utilisée pour le join dans une association habtm n a pas de clef primaire Elle ne peut pas avoir d attributs en plus des clefs étrangères On n utilise jamais directement la table join On ne peut faire de join qu entre deux tables has_many :through On crée plusieurs associations has_many On utilise :through pour indiquer la transitivité L association peut avoir des attributs On peut instancier des objets de l association
Création de plusieurs associations Les modèles Alpha et Beta sont associés à travers un modèle Assoc: app/models/alpha.rb: class Alpha < ActiveRecord::Base has_many :assocs app/models/assoc.rb: class Assoc < ActiveRecord::Base belongs_to :alpha belongs_to :beta app/models/beta.rb: class Beta < ActiveRecord::Base has_many :assocs Il est compliqué d obtenir tous les objets de Beta associés à un objet de Alpha car a.assocs est un tableau d objets de la classe Assoc On utilise :through pour obtenir immédiatement le tableau des objets de Beta associés à a
Utilisation de :through Les modèles Alpha et Beta sont associés à travers un modèle Assoc: app/models/alpha.rb: class Alpha < ActiveRecord::Base has_many :assocs has_many :betas, :through => :assocs Génération de l association: Code: app/models/assoc.rb: class Assoc < ActiveRecord::Base belongs_to :alpha belongs_to :beta $ rails generate model Assoc alpha_id:integer beta_id:integer app/models/beta.rb: class Beta < ActiveRecord::Base has_many :assocs has_many :alphas, :through => :assocs