Rails 4 scope to find parents with no children

2019-01-02 15:37发布

I found one answer that had some usable having examples for finding parents with n children, but the same is not usable for finding parents with no children (presumably since the join excludes them).

scope :with_children, joins(:children).group("child_join_table.parent_id").having("count(child_join_table.parent_id) > 0")

Can anyone point me in the right direction?

3条回答
余生无你
2楼-- · 2019-01-02 16:20

This should do the job you want:

Rails 3 & 4

scope :without_children, includes(:children).where(:children => { :id => nil })

The big difference here is the joins becoming a includes: an include loads all the relations, if they exists, the join will load only the associated objects and ignore the object without a relation.

In fact, scope :with_children, joins(:children) should be just enough to return the Parent with at least 1 child. Try it out!

Rails 5

See @Anson's answer below


As @MauroDias pointed out, if it is a self-referential relationship between your parent and children, this code above won't work.

With a little bit of research, I found out how to do it:

Consider this model:

class Item < ActiveRecord::Base
  has_many :children, :class_name => 'Item', :foreign_key => 'parent_id'

How to return all items with no child(ren):

Item.includes(:children).where(children_items: { id: nil })

How did I find that children_items table?

Item.joins(:children) generates the following SQL:

SELECT "items".* 
FROM "items" 
 INNER JOIN "items" "children_items" 
 ON "children_items"."parent_id" = "items"."id"

So I guessed that Rails uses a table when in need of a JOIN in a self-referential case.


Similar questions:

查看更多
唯独是你
3楼-- · 2019-01-02 16:23

This is how I solved it for Rails 5:

scope :without_comments, -> do
  left_outer_joins(:comments).where(comments: { id: nil })
end
查看更多
公子世无双
4楼-- · 2019-01-02 16:33

@MrYoshiji has a solid Rails 4 answer, but for folks coming here with Rails 5 you have more options.

Using Rails 5:

As of Rails 5, you can also use left_outer_joins to avoid loading the association. It was introduced in pull request #12071.

scope :without_children, left_outer_joins(:children).where(children: { id: nil })

For parents with children, MrYoshiji's Rails 4 solution is still the one to use:

scope :with_children, joins(:children)
查看更多
登录 后发表回答