想要在Rails 3中find没有关联logging的logging
考虑一个简单的关联…
class Person has_many :friends end class Friend belongs_to :person end 让所有在ARel和/或meta_where中没有朋友的人最干净的方法是什么?
然后呢怎么样has_many:通过版本
 class Person has_many :contacts has_many :friends, :through => :contacts, :uniq => true end class Friend has_many :contacts has_many :people, :through => :contacts, :uniq => true end class Contact belongs_to :friend belongs_to :person end 
我真的不想使用counter_cache – 而我从我读过的东西不能用has_many:通过
我不想拉所有的person.friendslogging,并在Ruby中通过它们循环 – 我想有一个查询/范围,我可以使用meta_searchgem
我不介意查询的性能成本
而离实际的SQL越远越好…
这仍然和SQL非常接近,但是在第一种情况下,它应该让所有没有朋友的人都可以看到:
 Person.where('id NOT IN (SELECT DISTINCT(person_id) FROM friends)') 
更好:
 Person.includes(:friends).where( :friends => { :person_id => nil } ) 
对于这个基本上是一样的事情,你依靠一个没有朋友的人也没有联系的事实:
 Person.includes(:contacts).where( :contacts => { :person_id => nil } ) 
更新
 在评论中有一个关于has_one的问题,所以只是更新。 这里的技巧是includes()期望关联的名字,但是where需要表的名字。 对于一个人来说,协会通常会以单数formsexpression,所以变化,但是where()部分保持原样。 所以如果一个Person只有一个Person那么你的陈述就是: 
 Person.includes(:contact).where( :contacts => { :person_id => nil } ) 
更新2
 有人问起了反面,没有人的朋友。 正如我在下面评论的那样,这实际上让我意识到最后一个字段(上面的:person_id )实际上并不需要与你要返回的模型相关联,它只是连接表中的一个字段。 他们都将是nil所以它可以是任何一个。 这导致了上述更简单的解决scheme: 
 Person.includes(:contacts).where( :contacts => { :id => nil } ) 
然后切换这个让没有人的朋友变得更简单,你只改变前面的类:
 Friend.includes(:contacts).where( :contacts => { :id => nil } ) 
更新3 – Rails 5
 感谢@Anson的优秀的Rails 5解决scheme(给他一些+ 1的答案,下面),你可以使用left_outer_joins来避免加载关联: 
 Person.left_outer_joins(:contacts).where( contacts: { id: nil } ) 
我已经把它包含在这里,所以人们会find它,但他值得这个+ 1。 伟大的加法!
smathy有一个好的Rails 3的答案。
  对于Rails 5 ,可以使用left_outer_joins来避免加载关联。 
 Person.left_outer_joins(:contacts).where( contacts: { id: nil } ) 
查看api文档 。 它在拉取请求#12071中被引入。
没有朋友的人
 Person.includes(:friends).where("friends.person_id IS NULL") 
或者至less有一个朋友
 Person.includes(:friends).where("friends.person_id IS NOT NULL") 
 你可以通过在Friend上设置示波器来与Arel做到这一点 
 class Friend belongs_to :person scope :to_somebody, ->{ where arel_table[:person_id].not_eq(nil) } scope :to_nobody, ->{ where arel_table[:person_id].eq(nil) } end 
那么,至less有一个朋友的人:
 Person.includes(:friends).merge(Friend.to_somebody) 
没有朋友的:
 Person.includes(:friends).merge(Friend.to_nobody) 
dmarkow和Unixmonkey的答案都给了我我需要的东西 – 谢谢!
我试了两个在我的真实应用程序,并得到他们的时间 – 这是两个范围:
 class Person has_many :contacts has_many :friends, :through => :contacts, :uniq => true scope :without_friends_v1, -> { where("(select count(*) from contacts where person_id=people.id) = 0") } scope :without_friends_v2, -> { where("id NOT IN (SELECT DISTINCT(person_id) FROM contacts)") } end 
运行一个真正的应用程序 – 约700人“logging的小桌子 – 平均5次运行
  Unixmonkey的方法( :without_friends_v1 without_friends_v1)813ms /查询 
  dmarkow的方法( :without_friends_v2 without_friends_v2)891ms /查询(减慢〜10%) 
 但是,然后它发生在我身上,我不需要调用DISTINCT()...我正在寻找Personlogging没有Contacts – 所以他们只需要person_ids联系人person_ids列表。 所以我尝试了这个范围: 
  scope :without_friends_v3, -> { where("id NOT IN (SELECT person_id FROM contacts)") } 
得到相同的结果,但平均为425毫秒/电话 – 接近一半的时间…
 现在你可能需要DISTINCT在其他类似的查询 – 但对我的情况这似乎工作正常。 
谢谢你的帮助
不幸的是,你可能正在研究一个涉及SQL的解决scheme,但你可以在一个范围内设置它,然后使用该范围:
 class Person has_many :contacts has_many :friends, :through => :contacts, :uniq => true scope :without_friends, where("(select count(*) from contacts where person_id=people.id) = 0") end 
 然后为了得到它们,你可以做Person.without_friends ,也可以用其他Arel方法链接: Person.without_friends.order("name").limit(10) 
一个不存在的相关子查询应该是快速的,特别是当行数和子对父logging的比率增加时。
 scope :without_friends, where("NOT EXISTS (SELECT null FROM contacts where contacts.person_id = people.id)") 
另外,要被一个朋友过滤掉,例如:
 Friend.where.not(id: other_friend.friends.pluck(:id))