2008-11-24 21 views
26

Jak mogę osiągnąć następujące czynności? Mam dwa modele (blogi i czytelników) oraz tabeli łączącej, która pozwoli mi mieć N: M relacji między nimi:jak uniknąć duplikatów w relacji 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 

co chcę teraz zrobić, to dodać czytelników do różnych blogów. Warunkiem jest jednak to, że mogę dodać czytelnika tylko do bloga RAZ. Więc nie może być żadnych duplikatów (ten sam readerID, ten sam blogID) w tabeli BlogsReaders. Jak mogę to osiągnąć?

Drugie pytanie brzmi: jak uzyskać listę blogów, których już nie subskrybują czytelnicy (np. Wypełnić listę wyboru, którą można następnie wykorzystać do dodania czytelnika do innego bloga) ?

Odpowiedz

5

Co o:

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

Szyny dba o gromadzenie identyfikatorów dla nas z metodami stowarzyszenia! :)

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

+0

Chciałbym również wspomnieć, że jest to prawdopodobnie lepsza metoda, ponieważ zaakceptowana odpowiedź wybiera WSZYSTKIE dane z wiersza (ów) (np. The_reader.blogs), podczas gdy moja odpowiedź wybiera tylko identyfikatory z wierszy (np. The_reader. blog_ids). To jest hit wielkiej wydajności! –

+0

jest to lepsze rozwiązanie i powinno być właściwą odpowiedzią. Dzięki Josh. –

+0

thx Josh! Wygląda na szczuplejsze! – Sebastian

32

To powinno dbać o swoje pierwsze pytanie:

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

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

Próbowałem dowiedzieć się tego od dłuższego czasu, a to nigdy nie przyszło mi do głowy! Świetne rozwiązanie! Dzięki! – Arel

+1

Należy uważnie przeczytać o współbieżności i integralności tutaj http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of –

1

myślę ktoś przyjdzie wraz z lepszą odpowiedź niż ta.

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

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

[edytuj]

Proszę zobaczyć odpowiedź Josha poniżej. To jest droga. (Wiedziałam, że lepszym sposobem tam;)

+0

można to również zrobić w jednej instrukcji za pomocą find_by_sql. –

+0

Awesome! Działa to doskonale! Wielkie dzięki!! – Sebastian

69

Prostsze rozwiązanie, które jest wbudowane w 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 

Uwaga dodanie opcji do wywołania has_many:uniq => true.

Możesz również rozważyć has_and_belongs_to_many między blogiem a czytnikiem, chyba że masz inne atrybuty, które chciałbyś mieć w modelu łączenia (obecnie nie). Ta metoda ma także opiton :uniq.

Należy zauważyć, że to nie przeszkadza w tworzeniu wpisów w tabeli, ale zapewnia, że ​​przy zapytaniu o kolekcję otrzymasz tylko jeden z każdego obiektu.

Aktualizacja

W Rails 4 sposobem na to jest za pomocą bloku zakres. Powyżej zmienia się na.

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

Myślę, że istnieje problem z tym podejściem, jeśli twój model łączenia ma dowolne inne pola. Na przykład pole pozycji, w którym każde dziecko może zostać umieszczone w obrębie rodzica. 'blog.readers << czytnik # blog_readers.position = 1;' 'blog.readers << czytnik # blog_readers.position = 2' Ponieważ drugi blog_readers ma inną pozycję, ustawienie uniq nie widzi go jako istniejącego Wpis i pozwala go utworzyć – ReggieB

+2

Jeśli masz domyślny zakres, który zamawia twoje blogi, musisz to zignorować (lub DISTINCT się nie powiedzie), możesz użyć tego: '' 'has_many: blogi, -> {unscope (: order) .uniq}, through:: blog_readers''' – marksiemers

0

Najprostszym sposobem jest serializacji relację do tablicy:

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

Następnie podczas przypisywania wartości do czytelników, zastosować je jako

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

Podczas przypisywania relacji w ten sposób duplikaty są automatycznie usuwane.

14

szynach 5,1 sposobem

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

Powód: https://github.com/rails/rails/pull/9683 i https://github.com/rails/rails/commit/adfab2dcf4003ca564d78d4425566dd2d9cd8b4f – tmaier

+0

@pastullo Ale nadal wstawia dane w środkowej tabeli blog_readers. jak temu zapobiec? – Vishal

0

Górny odpowiedź obecnie mówi używać uniq w proc:

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

To jednak kopie relacji do tablicy i może złamać rzeczy, które są spodziewa się wykonać operacje na relacji, a nie tablicy.

Jeśli używasz distinct utrzymuje go jako relacja:

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