Lepszy:
Person.includes(:friends).where( :friends => { :person_id => nil } )
Hmt to w zasadzie to samo, polegasz na tym, że osoba bez przyjaciół również nie będzie miała kontaktów:
Person.includes(:contacts).where( :contacts => { :person_id => nil } )
Aktualizacja
Mam pytanie has_one
w komentarzach, więc po prostu aktualizuję. Sztuczka polega na tym, że includes()
oczekuje nazwy skojarzenia, ale where
oczekuje nazwy tabeli. W przypadku a has_one
skojarzenie będzie zazwyczaj wyrażane w liczbie pojedynczej, więc zmienia się, ale where()
część pozostaje taka, jaka jest. Więc jeśli Person
tylko has_one :contact
wtedy twoje oświadczenie byłoby:
Person.includes(:contact).where( :contacts => { :person_id => nil } )
Zaktualizuj 2
Ktoś zapytał o odwrotność, przyjaciół bez ludzi. Jak skomentowałem poniżej, to faktycznie uświadomiło mi, że ostatnie pole (powyżej: the :person_id
) tak naprawdę nie musi być związane z modelem, który zwracasz, po prostu musi to być pole w tabeli łączenia. Wszyscy będą, nil
więc może to być każdy z nich. Prowadzi to do prostszego rozwiązania powyższego:
Person.includes(:contacts).where( :contacts => { :id => nil } )
A potem przełączenie tego, aby zwrócić przyjaciół bez ludzi, staje się jeszcze prostsze, zmieniasz tylko klasę z przodu:
Friend.includes(:contacts).where( :contacts => { :id => nil } )
Aktualizacja 3 - Rails 5
Dzięki @Anson za doskonałe rozwiązanie Rails 5 (daj mu kilka + 1-ek za jego odpowiedź poniżej), możesz użyć, left_outer_joins
aby uniknąć ładowania skojarzenia:
Person.left_outer_joins(:contacts).where( contacts: { id: nil } )
Umieściłem go tutaj, aby ludzie go mogli znaleźć, ale zasługuje na +1 za to. Świetny dodatek!
Aktualizacja 4 - Rails 6.1.0
Podziękowania dla Tima Park za wskazanie, że w nadchodzącym 6.1 możesz to zrobić:
Person.where.missing(:contacts)
Dzięki postowi, do którego też się podlinkował