Mieux:
Person.includes(:friends).where( :friends => { :person_id => nil } )
Pour le hmt, c'est fondamentalement la même chose, vous comptez sur le fait qu'une personne sans amis n'aura pas non plus de contacts:
Person.includes(:contacts).where( :contacts => { :person_id => nil } )
Mettre à jour
Vous avez une question à propos has_onedes commentaires, donc juste une mise à jour. L'astuce ici est qu'il includes()attend le nom de l'association mais whereattend le nom de la table. Pour un, has_onel'association sera généralement exprimée au singulier, de sorte que cela change, mais la where()partie reste telle qu'elle est. Donc, si Personseulement has_one :contactalors votre déclaration serait:
Person.includes(:contact).where( :contacts => { :person_id => nil } )
Mise à jour 2
Quelqu'un a demandé l'inverse, des amis sans personne. Comme je l'ai commenté ci-dessous, cela m'a fait réaliser que le dernier champ (ci-dessus: le :person_id) n'a pas à être lié au modèle que vous renvoyez, il doit simplement s'agir d'un champ dans la table de jointure. Ils vont tous l'être, nildonc ça peut être n'importe lequel d'entre eux. Cela conduit à une solution plus simple à ce qui précède:
Person.includes(:contacts).where( :contacts => { :id => nil } )
Et puis changer cela pour renvoyer les amis sans personne devient encore plus simple, vous ne changez que la classe à l'avant:
Friend.includes(:contacts).where( :contacts => { :id => nil } )
Mise à jour 3 - Rails 5
Merci à @Anson pour l'excellente solution Rails 5 (donnez-lui quelques + 1 pour sa réponse ci-dessous), vous pouvez utiliser left_outer_joinspour éviter de charger l'association:
Person.left_outer_joins(:contacts).where( contacts: { id: nil } )
Je l'ai inclus ici pour que les gens le trouvent, mais il mérite les + 1 pour cela. Excellent ajout!
Mise à jour 4 - Rails 6.1
Merci à Tim Park d' avoir souligné que dans la prochaine version 6.1, vous pouvez le faire:
Person.where.missing(:contacts)
Merci aussi au post auquel il a lié.