2008-11-24 10 views
26

Comment puis-je obtenir les résultats suivants? J'ai deux modèles (blogs et lecteurs) et une table de jointure qui me permettra d'avoir une N: la relation M entre eux:comment éviter les doublons dans une relation has_many: through?

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, est d'ajouter les lecteurs à différents blogs. La condition, cependant, est que je ne peux ajouter un lecteur à un blog qu'une seule fois. Il ne doit donc pas y avoir de doublons (même readerID, même blogID) dans le tableau BlogsReaders. Comment puis-je atteindre cet objectif? La deuxième question est de savoir comment obtenir une liste de blogs auxquels les lecteurs ne sont pas déjà 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). ?

Répondre

5

Qu'en est-:

Blog.find(:all, 
      :conditions => ['id NOT IN (?)', the_reader.blog_ids]) 

Rails prend soin de la collection de ids pour nous avec des méthodes d'association! :)

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

+0

En outre, je voulais mentionner que c'est probablement la meilleure méthode, car la réponse acceptée sélectionne TOUTES les données de la rangée (par exemple, the_reader.blogs) alors que ma réponse sélectionne uniquement les identifiants des lignes (par exemple, the_reader. blog_ids). C'est un gros succès! –

+0

c'est une meilleure solution et devrait être la bonne réponse. Merci Josh. –

+0

thx Josh! En apparence plus mince! – Sebastian

32

Cela devrait prendre soin de votre première question:

class BlogsReaders < ActiveRecord::Base 
    belongs_to :blog 
    belongs_to :reader 

    validates_uniqueness_of :reader_id, :scope => :blog_id 
end 
+0

J'ai essayé de comprendre cela depuis longtemps, et cela ne me vint! Bonne solution! Merci! – Arel

+1

S'il vous plaît lire attentivement sur la concurrence et l'intégrité ici http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of –

1

Je pense que quelqu'un va venir avec une meilleure réponse que cela.

the_reader = Reader.find(:first, :include => :blogs) 

Blog.find(:all, 
      :conditions => ['id NOT IN (?)', the_reader.blogs.map(&:id)]) 

[modifier]

S'il vous plaît voir la réponse de Josh ci-dessous. C'est le chemin à parcourir. (Je savais qu'il y avait une meilleure façon là;)

+0

vous pouvez également le faire dans une déclaration en utilisant find_by_sql. –

+0

Génial! Cela fonctionne parfaitement! Merci beaucoup!! – Sebastian

69

solution plus simple qui est intégré dans 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 

Remarque ajouter l'option :uniq => true à l'appel has_many.

Vous pouvez également envisager has_and_belongs_to_many entre Blog et Reader, sauf si vous avez d'autres attributs que vous souhaitez avoir sur le modèle de jointure (ce qui n'est pas le cas actuellement). Cette méthode a également une opiton :uniq. Notez que cela ne vous empêche pas de créer les entrées dans la table, mais cela garantit que lorsque vous interrogez la collection, vous obtenez un seul de chaque objet.

Mise à jour

Dans Rails 4 la façon de le faire est par un bloc de portée. Le dessus se transforme en.

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 
+0

Je pense qu'il y a un problème avec cette approche si votre modèle de jointure a d'autres champs. Par exemple, un champ de positions pour que chaque enfant puisse être positionné dans son parent. 'blog.readers << lecteur # blog_readers.position = 1;' 'blog.readers << lecteur # blog_readers.position = 2' Comme second blog_readers a une position différente, le paramètre uniq ne le voit pas comme existant entry et permet de le créer – ReggieB

+2

Si vous avez une portée par défaut qui commande vos blogs, vous devrez désactiver (ou DISTINCT échouera), vous pouvez utiliser ceci: '' 'has_many: blogs, -> {unscope (: ordre) .uniq}, à travers:: blog_readers''' – marksiemers

0

les plus simples est de sérialisation 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, lors de l'attribution des valeurs aux lecteurs, vous les appliquez comme

blog.reader_ids = [1,2,3,4] 

Lors de l'attribution de relations de cette manière, les doublons sont automatiquement supprimés.

14

Les Rails 5.1 voie

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 
+0

Raisonnement: https://github.com/rails/rails/pull/9683 et https://github.com/rails/rails/commit/adfab2dcf4003ca564d78d4425566dd2d9cd8b4f – tmaier

+0

@pastullo Mais il est toujours insérer les données dans la table du milieu blog_readers. comment éviter cela? – Vishal

0

Le top réponse dit actuellement à utiliser uniq dans le proc:

class Blog < ActiveRecord::Base 
has_many :blogs_readers, dependent: :destroy 
has_many :readers, -> { uniq }, through: :blogs_readers 
end 

Cette lance cependant la relation dans un tableau et peut casser des choses qui sont s'attendre à effectuer des opérations sur une relation, pas un tableau.

Si vous utilisez distinct il garde comme une relation:

class Blog < ActiveRecord::Base 
has_many :blogs_readers, dependent: :destroy 
has_many :readers, -> { distinct }, through: :blogs_readers 
end 
Questions connexes