Объединение цепочки активных записей, игнорирование объединений, если они уже объединены
Я смоделировал, Пользователь имеет участие в событии.
class User
has_many :attendances
has_many :events, through: :attendances
class Event
has_many :attendances
scope :is_attending, -> { joins(:attendances).where(attendances:{attend_status: Attendance.attend_statuses[:attending] })}
class Attendance
belongs_to :event
belongs_to :user
enum attend_status: { attending: 0, not_attending: 1}
"Мой вопрос" посвящен запросам и лучшей практике.
Я поставил большую часть моих запросов к области в Event.
Я хочу получить все события для конкретного пользователя, где visit_status = 0
user = User.find(...)
user.events.is_attending
Логически я бы подумал, что это лучше всего читает и имеет смысл
Однако это дало бы мне двойной INNER JOIN
SELECT "events".* FROM "events"
INNER JOIN "attendances" "attendances_events" ON "attendances_events"."event_id" = "events"."id"
INNER JOIN "attendances" ON "events"."id" = "attendances"."event_id"
WHERE "attendances"."user_id" = $1 AND "attendances"."attend_status" = 0
Очевидно, это создает дубликаты, которые не то, что я хотел.
Итак, параметры, которые я знаю, я могу сделать
1) USE MERGE
Event
scope :for_user, -> (user){ joins(:attendances).where(attendances: {user: user})}
затем вызовите
Event.for_user(user).merge(Event.is_attending)
который дает мне sql
SELECT "events".* FROM "events" INNER JOIN "attendances" ON "attendances"."event_id" = "events"."id" WHERE "attendances"."user_id" = 59 AND "attendances"."attend_status" = 0
Это то, что я хочу.
Но это кажется ужасным синтаксисом и сбивает с толку.
2) ИСПОЛЬЗОВАНИЕ ВКЛЮЧАЕТ
Если я использую include вместо join, я не получаю двойное соединение. Поскольку он загружает события отдельно и достаточно умен, чтобы не дублировать.
Event
scope :is_attending, -> { includes(:attendances).where(attendances: {attend_status: Attendance.attend_statuses[:attending] })}
Однако я не хочу получать нагрузку.
3) Таблица ASSUME уже соединена за пределами области видимости
Наконец, я могу предположить, что таблица уже соединена вне вызова области,
Event
scope :is_attending, -> { where(attendances: {attend_status: Attendance.attend_statuses[:attending] })}
Но для меня это кажется глупым дизайном и делает эту область видимости менее пригодной для использования.
Итак, мои вопросы
1) Каков наилучший подход к этому?
Наиболее логичным
user.events.is_attending
- это тот, который я в идеале хочу использовать.
2) Есть ли способ сказать Active Record игнорировать объединения, если они уже произошли?
Ответы
Ответ 1
Я бы использовал что-то близкое к вашему первому предложению, за исключением того, что я бы объединил область действия непосредственно на вашей внутренней модели (Attendance).
Итак, вместо:
Event.for_user (пользователь).merge(Event.is_attending)
Я бы пошел за:
Event.for_user (пользователь).merge(Attendance.is_attending)
Это четкий синтаксис. В области видимости is_attending не требуется объединение, и каждый класс отвечает за знание того, как фильтровать себя.
Если вы часто используете его, вы можете создать область for_attending_user(user)
, чтобы скрыть слияние:
class Event
scope :for_attending_user, -> user { for_user(user).merge(Attendance.is_attending) }
...
Ответ 2
Вы можете добавить связь с условием is_attending
к модели User
:
class Attendance < ActiveRecord::Base
belongs_to :event
belongs_to :user
enum attend_status: { attending: 0, not_attending: 1}
scope :is_attending, -> { where(attend_status: attend_statuses[:attending]) }
end
class User < ActiveRecord::Base
has_many :attendances
has_many :events, through: :attendances
has_many :attending_events, -> { Attendance.is_attending }, through: :attendances, source: :event
end
Теперь вы можете посещать события без дублирования соединения:
u.attending_events
SELECT "events".* FROM "events"
INNER JOIN "attendances" ON "events"."id" = "attendances"."event_id"
WHERE "attendances"."user_id" = 1 AND "attendances"."attend_status" = 0 [["user_id", 1], ["attend_status", 0]]
Этот подход имеет недостаток: вы не можете комбинировать условия. Но это имеет смысл, если вы работаете со столбцами статуса. Потому что этот подход отражает логику отношений.