max pleaner
max pleaner

Reputation: 26758

get count of records using nested has_many

I have a record A with id 1, it has_many B records which each have many C.

A(id=1) => [b records] => [[C records for each b]]

I need to get the count of C for the given A.

I'm pretty sure there's something to to do with joins, groups, ororder`, but I don't know exactly what. I need a way to do this in a constant time SQL operation. No iterative queries.

Upvotes: 0

Views: 802

Answers (4)

jasonbuehler
jasonbuehler

Reputation: 21

Assuming that I had a reason not to create the has many through association for cs through bs, I would do something like:

class A < ActiveRecord::Base
  has_many :bs
  # has_many :cs, through: :bs - this allows a.cs.size as has been noted here
end

class B < ActiveRecord::Base
  has_many :cs
  belongs_to :a
end

class C < ActiveRecord::Base
  belongs_to :b
end


# you can always do this if you don't want to create the above association through bs:
a.bs.flat_map(&:cs).size

Upvotes: 1

Mark Swardstrom
Mark Swardstrom

Reputation: 18070

A couple ways

C.joins(:b).where(:bs => {:a_id => a.id}).count

or

class A < ActiveRecord::Base

  has_many :bs
  has_many :cs, :through => :bs

end

# Then you can do this, nice and easy to read.  
# Use size, not count in case they are already loaded.

a.cs.size

or even

  A.where(:id => a.id).joins(:bs => :cs).count

Upvotes: 1

PinnyM
PinnyM

Reputation: 35533

Use a has_many :through association:

class A < ActiveRecord::Base
  has_many :bs
  has_many :cs, through: :bs
end

class B < ActiveRecord::Base
  belongs_to :a
  has_many :cs
end

class C < ActiveRecord::Base
  belongs_to :b
end

Now you can do:

a.cs.count

If the has-many-through a.cs association is something you won't regularly use, and you'd rather not add it to your model, then you can use merge instead:

C.joins(:b).merge(a.bs).count

Upvotes: 3

Blair Anderson
Blair Anderson

Reputation: 20161

Something like:

A.join(:b => :c).where(id: 1).count("c.id")

If you already have an instance of A:

a.b.joins(:c).count("c.id")

Upvotes: 1

Related Questions