2008-11-24 12 views
26

Wie kann ich Folgendes erreichen? Ich habe zwei Modelle (Blogs und Leser) und eine Tabelle JOIN, die mir erlauben, einen N zu haben: M Beziehung zwischen ihnen:Wie vermeidet man Duplikate in einer has_many: through-Beziehung?

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 

Was ich jetzt tun möchte, ist Leser in den verschiedenen Blogs. Die Bedingung ist jedoch, dass ich nur einmal einen Leser zu einem Blog hinzufügen kann. Es darf also keine Duplikate (gleich readerID, gleich blogID) in der Tabelle BlogsReaders geben. Wie kann ich das erreichen? Die zweite Frage ist, wie bekomme ich eine Liste von Blogs, die die Leser nicht bereits abonniert haben (zB um eine Drop-down-Auswahlliste zu füllen, die dann verwendet werden kann, um den Leser zu einem anderen Blog hinzuzufügen). ?

Antwort

5

Was:

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

Rails kümmert sich um die Sammlung von ids für uns mit Assoziationsmethoden! :)

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

+0

Ich möchte auch erwähnen, dass dies wahrscheinlich die bessere Methode ist, da die akzeptierte Antwort ALLE Daten aus den Zeilen (zB the_reader.blogs) auswählt, während meine Antwort nur die ids aus den Zeilen auswählt (zB the_reader). blog_ids). Das ist ein großer Leistungshit! –

+0

Dies ist eine bessere Lösung und sollte die richtige Antwort sein. Danke, Josh. –

+0

thx Josh! Sieht in der Tat schlanker aus! – Sebastian

32

Dies sollte Ihre erste Frage kümmern:

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

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

Ich habe versucht, dies für eine lange Zeit, um herauszufinden, und diese kam nie zu mir! Tolle Lösung! Vielen Dank! – Arel

+1

Bitte lesen Sie sorgfältig über Nebenläufigkeit und Integrität hier http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of –

1

Ich denke, jemand als dies mit einer besseren Antwort kommen wird.

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

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

[Bearbeiten]

Bitte sehen Joshs unten Antwort. Es ist der Weg zu gehen. (Ich wusste, dass es einen besseren Weg gibt;)

+0

Sie könnten dies auch in einer Anweisung mit find_by_sql tun. –

+0

Super! Das funktioniert perfekt! Danke vielmals!! – Sebastian

69

Einfachere Lösung, die in Rails gebaut ist:

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 

Hinweis die :uniq => true Option zum has_many Gespräch hinzugefügt.

Sie können auch has_and_belongs_to_many zwischen Blog und Reader berücksichtigen, es sei denn, Sie haben einige andere Attribute, die Sie auf dem Join-Modell haben möchten (was Sie derzeit nicht tun). Diese Methode hat auch eine :uniq Option.

Beachten Sie, dass dies Sie nicht daran hindert, die Einträge in der Tabelle zu erstellen, aber es stellt sicher, dass Sie beim Abfragen der Sammlung nur eines von jedem Objekt erhalten.

aktualisieren

In Rails 4 die Art und Weise über einen Umfang Block zu tun, ist es. Das Oben ändert sich zu.

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

Ich denke, es gibt ein Problem mit diesem Ansatz, wenn Ihr Join-Modell andere Felder hat. Zum Beispiel ein Positionsfeld, so dass jedes Kind innerhalb seines Elternteils positioniert werden kann. 'blog.readers << Leser # blog_readers.position = 1;' 'blog.readers << Leser # blog_readers.position = 2' Da zweite blog_readers eine andere Position hat, sieht die uniq-Einstellung sie nicht als existierend an Eintrag und ermöglicht es erstellt werden – ReggieB

+2

Wenn Sie einen Standard-Bereich haben, der Ihre Blogs bestellt, müssen Sie dies (oder DISTINCT wird fehlschlagen), können Sie dies verwenden: '' 'has_many: Blogs, -> {unscope (: bestellen) .uniq}, durch:: blog_readers''' – marksiemers

0

Der einfachste Weg ist es, die Beziehung zu serialisieren ein Array in:

class Blog < ActiveRecord::Base 
    has_many :blogs_readers, :dependent => :destroy 
    has_many :readers, :through => :blogs_readers 
    serialize :reader_ids, Array 
end 

dann, wenn die Werte für die Leser zuweisen, gelten Sie sie als

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

Wenn Sie Beziehungen auf diese Weise zuweisen, werden Duplikate automatisch entfernt.

14

Die Rails 5.1 Weg

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

Begründung: https://github.com/rails/rails/pull/9683 und https://github.com/rails/rails/commit/adfab2dcf4003ca564d78d4425566dd2d9cd8b4f – tmaier

+0

@pastullo Aber es ist immer noch die Daten in der mittleren Tabelle blog_readers einzufügen. Wie verhindere ich das? – Vishal

0

Die Top-Antwort sagt derzeit uniq im proc zu verwenden:

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

Dies ist jedoch die Beziehung in eine Anordnung tritt und können die Dinge brechen, sind erwartet Operationen auf einer Relation auszuführen, nicht auf einem Array.

Wenn Sie distinct verwenden es hält sie als Beziehung:

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