has_and_belongs_to_many, en évitant les doublons dans la table de jointure
J'ai un assez simple HABTM ensemble de modèles
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
def tags= (tag_list)
self.tags.clear
tag_list.strip.split(' ').each do
self.tags.build(:name => tag)
end
end
end
Maintenant tout fonctionne bien, sauf que je reçois une tonne de doublons dans la table Tags.
Que dois-je faire pour éviter les doublons (bases sur le nom) dans la table tags?
- Voulez-vous dire en double dans la table de jointure (que le titre suggère) ou des balises de tableau?
Vous devez vous connecter pour publier un commentaire.
Éviter les doublons dans la vue seulement (Paresseux solution)
Suivantes ne pas empêcher l'écriture des relations en double dans la base de données, il assure
find
méthodes d'ignorer les doublons.Dans Les Rails 5:
Remarque:
Relation#uniq
a été déprécié dans les Rails 5 (s'engager)Dans Les Rails 4
Empêcher la duplication des des données enregistrées (meilleure solution)
Option 1: Éviter les doublons à partir du contrôleur:
Cependant, plusieurs utilisateurs pourraient tenter
post.tags.include?(tag)
en même temps, donc c'est soumis à des conditions de course. Cette question est examinée ici.Pour la robustesse vous pouvez également ajouter à cela le modèle Post (la poste.rb)
Option 2: Créer un index unique
Le plus de moyen infaillible de prévenir les doublons est de garder un double de contraintes à la couche de base de données. Ceci peut être réalisé en ajoutant un
unique index
sur la table elle-même.Une fois que vous avez l'index unique, en essayant d'ajouter une copie de l'enregistrement déclenche une
ActiveRecord::RecordNotUnique
erreur. Ce traitement est hors de la portée de cette question. Vue cette SORTE de question.tag=
méthode de quelque chose commeadd_unique_tag
En outre, les suggestions ci-dessus:
:uniq
à lahas_and_belongs_to_many
associationJe ferais une vérification explicite pour déterminer si la relation existe déjà. Par exemple:
Post
modèle, ajouterdef tag=(tag); tags << tag unless tags.include?(tag); end
pour la robustesse.unless ...include?()
en raison de conditions de course. Je suppose que la même chose pourrait être vraie pour has_and_belongs_to_many.include
est une méthode de Tableau, il faudra charger et boucle sur tous les enregistrements de la relation, qui n'est pas performant du tout. Au lieu de cela, utilisertags.exists?(tag.id)
Dans Rails4:
(attention, le
-> { uniq }
doit être directement après le nom de la relation, avant que d'autres params)Les Rails de la documentation
after_add
rappel sera répété à chaque foisafter_add
fonctionne toujours, même dans le cas d'un enregistrement n'est pas enregistré (merci à {unique})?-> {uniq}
champ d'application n'a pas empêcher la duplication de données, mais permet l'affichage d'un seul dans la vue.Vous pouvez passer à la
:uniq
option décrit dans la documentation. Notez également que le:uniq
options n'est pas d'éviter la création de doublons de relations, il assure accesseur/trouver des méthodes de sélectionner une seule fois.Si vous voulez éviter les doublons dans la table d'association, vous devez créer un index unique et gérer l'exception. Aussi validates_uniqueness_of ne fonctionne pas comme prévu, car vous pouvez tomber dans le cas d'une deuxième demande est écrit à la base de données entre le moment de la première demande, vérifie les doublons et les écrit dans la base de données.
Ensemble de l'uniq option:
Je préfère ajuster le modèle et créer les classes de cette manière:
Puis je tourne la création dans la logique de sorte que l'Étiquette de modèles ont été réutilisés, si elle existait déjà. Je suis même mis une contrainte unique sur le nom de la balise à la faire respecter. Ce qui le rend plus efficace pour la recherche de toute façon puisque vous pouvez simplement utiliser l'index sur la table de jointure (à trouver tous les messages pour une balise particulière, et toutes les balises pour un poste particulier).
Le seul hic, c'est que vous ne pouvez pas vous permettre de renommer des balises, car changer le nom de la balise aurait une incidence sur toutes les utilisations de cette balise. Permettre à l'utilisateur de supprimer la balise et en créer un nouveau à la place.
J'ai travaillé autour de ce par la création d'un before_save filtre qui corrige des trucs.
Il pourrait être légèrement optimisé pour fonctionner dans des lots avec les balises, aussi, elle doit être légèrement meilleure prise en charge transactionnelle.
C'est vraiment vieux, mais j'ai pensé que je devais partager ma manière de faire.
Dans le code où j'ai besoin d'ajouter des tags à un post, je fais quelque chose comme:
Ce qui a pour effet de automatiquement ajout/suppression de balises comme nécessaire ou ne rien faire si c'est le cas.
post.tag_ids |= [new_tag.id]
oupost.tags |= [new_tag]
, à l'aide de Array set-comme les opérateursM'travail
remplacer les << méthode dans la relation
Extraire le nom de la balise pour la sécurité. Vérifier si le tag existe dans vos balises table, puis le créer s'il n'est pas le cas:
Puis vérifier qu'il n'existe au sein de cette collection, et de pousser si ce n'est pas:
Vous devez ajouter un index sur le tag :nom de la propriété et de l'utilisation de la find_or_create méthode dans les Balises#création d'une méthode
docs
Juste d'ajouter une case dans votre contrôleur avant d'ajouter l'enregistrement. Si elle le fait, ne rien faire, si ce n'est pas, en ajouter une nouvelle:
Plus: "4.4.1.14 collection.existe?(...)"
http://edgeguides.rubyonrails.org/association_basics.html#scopes-for-has-and-belongs-to-many