通过 Rails 中缺失的记录过滤关联
Filtering an association by missing records in Rails
在我正在处理的 Rails 应用程序中,我有几个不同的模型因此关联(为清楚起见进行了压缩):
group.rb
class Group < ApplicationRecord
has_many :members, class_name: 'GroupMember'
has_many :newsletters
end
group_member.rb
class GroupMember < ApplicationRecord
belongs_to :group,
has_many :subscriptions, inverse_of: :group_member, class_name: 'Newsletter::Subscriber'
scope :subscribed_to, ->(newsletter_id) { joins(:subscriptions).merge(Newsletter::Subscriber.where(["newsletter_id = ?", newsletter_id])) }
scope :not_subscribed_to, ->(newsletter_id) { where.missing(:subscriptions) }
end
newsletter.rb
class Newsletter < ApplicationRecord
acts_as_tenant :group
has_many :subscribers, inverse_of: :newsletter
end
newsletter/subscriber.rb
class Newsletter::Subscriber < ApplicationRecord
acts_as_tenant :group
belongs_to :newsletter, inverse_of: :subscribers
belongs_to :group_member, class_name: 'GroupMember', inverse_of: :subscriptions
end
鉴于上述相关模型,这是我正在使用的框架:
- 每个组有 n 个组成员和 n 个通讯。
- 每个组成员可以有多个时事通讯订阅(组中每个时事通讯一个)
我正在尝试做的(到目前为止未成功)是找出群组中的哪些成员未订阅与该群组关联的特定时事通讯。
我可以在 GroupMember
对象上使用以下范围找出确实有订阅的成员:
scope :subscribed_to, ->(newsletter_id) { joins(:subscriptions).merge(Newsletter::Subscriber.where(["newsletter_id = ?", newsletter_id])) }
这让我可以查询,例如,current_group.members.subscribed_to(current_group.newsletters.first.id)
。
但是,我不确定如何否定它并得到那组成员的对立面。也就是说,会员未订阅该特定时事通讯。我目前定义的 :not_subscribed_to
范围并没有削减它,因为它没有考虑到我指的是哪个时事通讯。
给定变量newsletter_id
。
一种替代方法是将 WHERE NOT EXISTS(...)
与子查询一起使用:
Member
.where(
'NOT EXISTS(
SELECT 1 FROM "subscriptions"
WHERE "subscriptions"."member_id" = "members"."id"
AND "subscriptions"."newsletter_id" = ?
)', newsletter_id
)
翻译成 Arel:
Member.where(
Subscription.select('1')
.where(
Subscription.arel_table[:member_id].eq(Member.arel_table[:id])
).where(
newsletter_id: newsletter_id
).arel.exists.not
)
或组、数并有:
Member.group(:id)
.left_joins(:subscriptions)
.where(subscriptions: { newsletter_id: newsletter_id })
.having(Subscription.arel_table[:id].count.eq(0))
在我正在处理的 Rails 应用程序中,我有几个不同的模型因此关联(为清楚起见进行了压缩):
group.rb
class Group < ApplicationRecord
has_many :members, class_name: 'GroupMember'
has_many :newsletters
end
group_member.rb
class GroupMember < ApplicationRecord
belongs_to :group,
has_many :subscriptions, inverse_of: :group_member, class_name: 'Newsletter::Subscriber'
scope :subscribed_to, ->(newsletter_id) { joins(:subscriptions).merge(Newsletter::Subscriber.where(["newsletter_id = ?", newsletter_id])) }
scope :not_subscribed_to, ->(newsletter_id) { where.missing(:subscriptions) }
end
newsletter.rb
class Newsletter < ApplicationRecord
acts_as_tenant :group
has_many :subscribers, inverse_of: :newsletter
end
newsletter/subscriber.rb
class Newsletter::Subscriber < ApplicationRecord
acts_as_tenant :group
belongs_to :newsletter, inverse_of: :subscribers
belongs_to :group_member, class_name: 'GroupMember', inverse_of: :subscriptions
end
鉴于上述相关模型,这是我正在使用的框架:
- 每个组有 n 个组成员和 n 个通讯。
- 每个组成员可以有多个时事通讯订阅(组中每个时事通讯一个)
我正在尝试做的(到目前为止未成功)是找出群组中的哪些成员未订阅与该群组关联的特定时事通讯。
我可以在 GroupMember
对象上使用以下范围找出确实有订阅的成员:
scope :subscribed_to, ->(newsletter_id) { joins(:subscriptions).merge(Newsletter::Subscriber.where(["newsletter_id = ?", newsletter_id])) }
这让我可以查询,例如,current_group.members.subscribed_to(current_group.newsletters.first.id)
。
但是,我不确定如何否定它并得到那组成员的对立面。也就是说,会员未订阅该特定时事通讯。我目前定义的 :not_subscribed_to
范围并没有削减它,因为它没有考虑到我指的是哪个时事通讯。
给定变量newsletter_id
。
一种替代方法是将 WHERE NOT EXISTS(...)
与子查询一起使用:
Member
.where(
'NOT EXISTS(
SELECT 1 FROM "subscriptions"
WHERE "subscriptions"."member_id" = "members"."id"
AND "subscriptions"."newsletter_id" = ?
)', newsletter_id
)
翻译成 Arel:
Member.where(
Subscription.select('1')
.where(
Subscription.arel_table[:member_id].eq(Member.arel_table[:id])
).where(
newsletter_id: newsletter_id
).arel.exists.not
)
或组、数并有:
Member.group(:id)
.left_joins(:subscriptions)
.where(subscriptions: { newsletter_id: newsletter_id })
.having(Subscription.arel_table[:id].count.eq(0))