Active Record范围链接,忽略连接,如果已加入

时间:2015-03-27 05:59:02

标签: ruby-on-rails activerecord

我已经建模,用户参加了活动。

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}

我的问题是关于范围查询和最佳实践。

我已将大多数范围查询放在事件上。

我想获取特定用户的所有活动,其中attend_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)使用合并

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)使用包括

如果我使用包含而不是加入,我就不会重复加入。因为它分别加载事件并且足够聪明而不能复制。

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如果已经发生了忽略连接?

2 个答案:

答案 0 :(得分:0)

我会使用接近你的第一个建议的东西,除了我会直接在你的内部模型(出勤)上合并范围。

所以而不是:

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) }
  ...

答案 1 :(得分:0)

您可以将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]]

这种方法有一个缺点:你无法结合条件。但是,如果您正在使用状态列,这是有道理的。因为这种方法反映了关系的逻辑。