comment éviter les doublons dans un has_many: à travers la relation?
-
11-07-2019 - |
Question
Comment puis-je atteindre les objectifs suivants? J'ai deux modèles (blogs et lecteurs) et une table JOIN qui me permettront d'avoir une relation N: M entre eux:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :blogs, :through => :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
Ce que je veux faire maintenant, c'est ajouter des lecteurs à différents blogs. La condition, cependant, est que je ne peux ajouter qu'un lecteur à un blog UNE FOIS. Il ne doit donc pas y avoir de doublons (même readerID
, même blogID
) dans la table BlogsReaders
. Comment puis-je y parvenir?
La deuxième question est la suivante: comment obtenir une liste de blogs à laquelle les lecteurs ne sont pas encore abonnés (par exemple, pour remplir une liste de sélection déroulante, qui peut ensuite être utilisée pour ajouter le lecteur à un autre blog)?
La solution
Qu'en est-il de:
Blog.find(:all,
:conditions => ['id NOT IN (?)', the_reader.blog_ids])
Rails s’occupe de la collecte des identifiants pour nous avec des méthodes d’association! :)
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ ClassMethods.html
Autres conseils
Une solution plus simple intégrée à Rails:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers, :uniq => true
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :blogs, :through => :blogs_readers, :uniq => true
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
Notez l'ajout de l'option :uniq => true
à l'appel has_many
.
Vous pouvez également prendre en compte has_and_belongs_to_many
entre Blog et Reader, à moins que vous n'ayez d'autres attributs que vous souhaiteriez avoir dans le modèle de jointure (ce que vous n'avez pas actuellement). Cette méthode a également un :uniq
opiton.
Notez que cela ne vous empêche pas de créer les entrées dans la table, mais veille à ce que lorsque vous interrogez la collection, vous n'obtenez qu'un seul de chaque objet.
Mettre à jour
Dans Rails 4, le moyen de le faire consiste à utiliser un bloc scope. Le texte ci-dessus devient.
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { uniq }, through: :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :blogs, -> { uniq }, through: :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
Mise à jour pour Rails 5
L'utilisation de uniq
dans le bloc de la portée provoquera une erreur NoMethodError: undefined method 'extensions' for []:Array
. Utilisez distinct
à la place:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :blogs, -> { distinct }, through: :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
Ceci devrait répondre à votre première question:
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
validates_uniqueness_of :reader_id, :scope => :blog_id
end
La méthode Rails 5.1
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :blogs, -> { distinct }, through: :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
La réponse sur ce lien montre comment remplacer le " < < " méthode pour obtenir ce que vous recherchez sans lever d’exception ni créer de méthode distincte: idiome Rails pour éviter les doublons dans has_many: via
Je pense que quelqu'un viendra avec une meilleure réponse que celle-là.
the_reader = Reader.find(:first, :include => :blogs)
Blog.find(:all,
:conditions => ['id NOT IN (?)', the_reader.blogs.map(&:id)])
[edit]
Veuillez consulter la réponse de Josh ci-dessous. C'est le chemin à parcourir. (Je savais qu'il y avait un meilleur moyen de sortir;)
La réponse principale indique actuellement l'utilisation de uniq
dans le proc:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { uniq }, through: :blogs_readers
end
Cependant, cela entame la relation dans un tableau et peut casser les choses qui sont censées effectuer des opérations sur une relation, pas un tableau.
Si vous utilisez distinct
le conserve comme relation:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end
Le moyen le plus simple consiste à sérialiser la relation dans un tableau:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers
serialize :reader_ids, Array
end
Ensuite, lorsque vous attribuez des valeurs aux lecteurs, vous les appliquez comme suit:
blog.reader_ids = [1,2,3,4]
Lorsque vous affectez des relations de cette manière, les doublons sont automatiquement supprimés.