通过 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

鉴于上述相关模型,这是我正在使用的框架:

我正在尝试做的(到目前为止未成功)是找出群组中的哪些成员未订阅与该群组关联的特定时事通讯。

我可以在 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))