ActiveRecord query for count of association using a join table

54 views Asked by At

I'm working an events feature, and I'm running into a rather involved query.

Here's the models for reference:

class Event < ApplicationRecord
  has_one :party

  belongs_to :host, foreign_key: :host_id, class_name: 'User'
  belongs_to :convention, optional: true
  has_many :join_requests
  
  ...
end

I'm looking to check which Events are accepting members, by checking the member_limit of the associated Party.

class Party < ApplicationRecord
  belongs_to :event
  belongs_to :host, foreign_key: :host_id, class_name: 'User'

  has_many :user_parties
  has_many :members, through: :user_parties, source: 'user'

  ...
end

Party has an integer column: member_limit -- which only allows a set amount of party members.

So far I have a scope on Event that looks like this:

scope :accepting_members, -> () do
    joins(party: :user_parties).group('parties.member_limit').select('count(user_parties.user_id) < parties.member_limit')
  end

Which doesn't work too well.

Am I on the right track?

I tried using the COUNT() aggregate, but it only returns a hash of integers.

2

There are 2 answers

0
max On BEST ANSWER

This is going to be a bit more complicated then you initially thought if you want usable output and there are quite a few ways to do this.

One way is to create a subquery for the event ids:

SELECT events.* 
FROM events
WHERE events.id IN (
  SELECT parties.events_id
  FROM parties
  OUTER LEFT JOIN user_parties ON parties.id = user_parties.party_id
  GROUP BY parties.event_id
  HAVING(
    COUNT(user_parties.user_id) < parties.member_limit
  )
)

Writing this in ActiveRecord/Arel gets a bit hairy:

subquery = Party.select(:events_id)
                .left_joins(:user_parties)
                .group(:event_id)
                .having(
                  # COUNT(user_parties.user_id) < parties.member_limit
                  UserParty.arel_table[:user_id].count.lt(
                    Party.arel_table[:member_limit]
                  )
                )
                .where(
                  # events.id = parties.event_id
                  Event.arel_table[:id].eq(Party.arel_table[:id])
                )
 
Event.where(
  id: subquery
)

Other ways to do this would be to use EXIST or a lateral join.

1
Nikhil bhatt On

I think you don't need any complex joins you can do it like this.

Since relation is as follows

  1. Event has_one Party
  2. Party has_many UserParty

You can do it like this.

# event.rb
scope :accepting_members, -> { party.user_parties.count < party.member_limit }

This will return boolean. if member limit is not reached it will return true otherwise false.