Я работаю над функцией событий и сталкиваюсь с довольно сложным запросом.
Вот модели для справки:
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
Я хочу проверить, какие мероприятия принимают участников, проверив member_limit
соответствующей стороны.
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
имеет целочисленный столбец: member_limit
-- который допускает только определенное количество членов группы.
На данный момент у меня есть область действия Event, которая выглядит так:
scope :accepting_members, -> () do
joins(party: :user_parties).group('parties.member_limit').select('count(user_parties.user_id) < parties.member_limit')
end
Что не очень хорошо работает.
Я на правильном пути?
Я пробовал использовать агрегат COUNT(), но он возвращает только хэш целых чисел.
Причина, по которой ваш подход не будет работать, заключается в том, что группа parties.member_limit
и предложение select будут применяться ко всему запросу, и вы не получите желаемого фактического результата, который представляет собой строки из таблицы событий.
Я думаю, вам не нужны какие-либо сложные соединения, вы можете сделать это вот так.
Поскольку соотношение следующее
Вы можете сделать это вот так.
# event.rb
scope :accepting_members, -> { party.user_parties.count < party.member_limit }
Это вернет логическое значение. если лимит участников не достигнут, он вернет true, в противном случае — false.
Это не очень хороший ответ. Если бы этот код действительно работал, это вызвало бы запрос N+1 и потребовало бы загрузки всей таблицы.
Это будет немного сложнее, чем вы изначально думали, если вам нужен полезный результат, и есть довольно много способов сделать это.
Один из способов — создать подзапрос для идентификаторов событий:
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
)
)
Писать это в ActiveRecord/Arel становится немного сложно:
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
)
Другой способ сделать это — использовать EXIST
или боковое соединение.
Вы действительно не на правильном пути. Здесь существуют различные подходы, например использование
WHERE events.id IN (subquery)
, где подзапрос получает идентификаторы событий,WHERE EXISTS(subquery)
или использование бокового соединения.