Quand utiliser une relation «has_many: through» dans Rails?

123

J'essaie de comprendre ce que has_many :throughc'est et quand l'utiliser (et comment). Cependant, je ne comprends pas. Je lis Beginning Rails 3 et j'ai essayé Google, mais je n'arrive pas à comprendre.

Lucky Luke
la source

Réponses:

177

Disons que vous avez deux modèles: Useret Group.

Si vous souhaitez que les utilisateurs appartiennent à des groupes, vous pouvez faire quelque chose comme ceci:

class Group < ActiveRecord::Base
  has_many :users
end

class User < ActiveRecord::Base
  belongs_to :group
end

Et si vous vouliez suivre des métadonnées supplémentaires autour de l'association? Par exemple, lorsque l'utilisateur a rejoint le groupe, ou peut-être quel est le rôle de l'utilisateur dans le groupe?

C'est ici que vous faites de l'association un objet de première classe:

class GroupMembership < ActiveRecord::Base
  belongs_to :user
  belongs_to :group

  # has attributes for date_joined and role
end

Cela introduit une nouvelle table et élimine la group_idcolonne de la table de l'utilisateur.

Le problème avec ce code est que vous devez mettre à jour partout où vous utilisez la classe d'utilisateurs et la modifier:

user.groups.first.name

# becomes

user.group_memberships.first.group.name

Ce type de code est nul et cela rend l'introduction de changements comme celui-ci douloureux.

has_many :through vous offre le meilleur des deux mondes:

class User < ActiveRecord::Base
  has_many :groups, :through => :group_memberships  # Edit :needs to be plural same as the has_many relationship   
  has_many :group_memberships
end

Vous pouvez désormais le traiter comme un système normal has_many, mais profitez du modèle d'association lorsque vous en avez besoin.

Notez que vous pouvez également le faire avec has_one.

Modifier: faciliter l'ajout d'un utilisateur à un groupe

def add_group(group, role = "member")
  self.group_associations.build(:group => group, :role => role)
end
Ben Scheirman
la source
2
C'est là que j'ajouterais une méthode sur le usermodèle pour ajouter le groupe. Quelque chose comme la modification que je viens de faire. J'espère que cela t'aides.
Ben Scheirman
Je vois, dois-je aussi écrire user.groups << group? Ou est-ce que tout est géré par cette association?
LuckyLuke
J'ai une classe qui est la même que celle de group_membership et j'ai des problèmes en utilisant Factory Girl avec elle, pourriez-vous m'aider?
Ayman Salah
J'utiliserais "has_many: group_memberships". L'utilisation du singulier fonctionnera, mais user.group_membership constituera une collection et user.group_memberships ne fonctionnera pas.
calasyr
C'était une faute de frappe. Fixé.
Ben Scheirman
212

Disons que vous avez ces modèles:

Car
Engine
Piston

Une voiture has_one :engine
Un moteur belongs_to :car
Un moteur has_many :pistons
Pistonbelongs_to :engine

Un has_many :pistons, through: :engine
piston de voiturehas_one :car, through: :engine

Vous déléguez essentiellement une relation de modèle à un autre modèle, donc au lieu d'avoir à appeler car.engine.pistons, vous pouvez simplement fairecar.pistons

cpuguy83
la source
9
Cela a beaucoup de sens!
RajG
24
J'appelle cela SACA - ShortAndClearAnswer.
Asme juste
18

Tables de jointure ActiveRecord

has_many :throughet les has_and_belongs_to_manyrelations fonctionnent via une table de jointure , qui est une table intermédiaire qui représente la relation entre d'autres tables. Contrairement à une requête JOIN, les données sont en fait stockées dans une table.

Différences pratiques

Avec has_and_belongs_to_many, vous n'avez pas besoin d'une clé primaire et vous accédez aux enregistrements via des relations ActiveRecord plutôt que via un modèle ActiveRecord. Vous utilisez généralement HABTM lorsque vous souhaitez lier deux modèles avec une relation plusieurs-à-plusieurs.

Vous utilisez une has_many :throughrelation lorsque vous souhaitez interagir avec la table de jointure en tant que modèle Rails, avec des clés primaires et la possibilité d'ajouter des colonnes personnalisées aux données jointes. Ce dernier est particulièrement important pour les données pertinentes pour les lignes jointes, mais n'appartiennent pas vraiment aux modèles associés - par exemple, le stockage d'une valeur calculée dérivée des champs de la ligne jointe.

Voir également

Dans A Guide to Active Record Associations , la recommandation se lit comme suit:

La règle de base la plus simple est que vous devez configurer une relation has_many: through si vous devez travailler avec le modèle de relation en tant qu'entité indépendante. Si vous n'avez rien à faire avec le modèle de relation, il peut être plus simple de configurer une relation has_and_belongs_to_many (bien que vous devrez vous rappeler de créer la table de jointure dans la base de données).

Vous devez utiliser has_many: through si vous avez besoin de validations, de rappels ou d'attributs supplémentaires sur le modèle de jointure.

Todd A. Jacobs
la source