Объединение цепочки активных записей, игнорирование объединений, если они уже объединены

Я смоделировал, Пользователь имеет участие в событии.

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]]

Этот подход имеет недостаток: вы не можете комбинировать условия. Но это имеет смысл, если вы работаете со столбцами статуса. Потому что этот подход отражает логику отношений.