Rails: 查找嵌套关联为空的记录
Rails: Finding records where a nested association is empty
在我正在处理的 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 :authorships, inverse_of: :group_member, class_name: "Newsletter::Author"
has_many :stories, inverse_of: :author, class_name: "Newsletter::Story"
end
newsletter.rb
class Newsletter < ApplicationRecord
has_many :authors, inverse_of: :newsletter
has_many :stories
end
newsletter/author.rb
class Newsletter::Author < ApplicationRecord
belongs_to :newsletter, inverse_of: :authors
belongs_to :group_member, class_name: "GroupMember", inverse_of: :authorships
end
newsletter/story.rb
class Newsletter::Story < ApplicationRecord
belongs_to :newsletter, inverse_of: :stories, optional: true
belongs_to :author, inverse_of: :stories, class_name: "GroupMember"
enum status: {draft: "draft", submitted: "submitted", published: "published"}, _default: "draft"
end
鉴于上述相关模型,这是我正在使用的框架:
- 每个时事通讯有 n 位作者(群组成员)和 n 位时事通讯。
- 每个小组成员都可以为给定的时事通讯撰写多个故事。
- 每个故事都是以下状态之一:草稿、已提交或已发布
- 故事草稿可能与时事通讯相关联,也可能不相关联
- 已提交或已发布的故事与时事通讯相关联
我想知道给定时事通讯的哪些作者没有处于 草稿 或 已提交 状态的故事。
给定 newsletter_id
,我可以通过这样的查询找出有草稿或已提交故事的成员:
Newsletter.find(newsletter_id).authors
.joins(group_member: :stories)
.where(stories: {status: [:draft, :submitted]})
.distinct
但是,我不确定如何否定这一点并得出与那组作者相反的结论。也就是说,给定时事通讯的作者没有草稿或提交的故事。他们是否发表过故事应该没有什么区别。
编辑
几个月前,我问 关于识别不存在关联模型记录的记录。我认为这是我在这里需要做的非常相似的方法,但由于 GroupMember (as Newsletter::Author)
-> Newsletter
-> 的嵌套关联,我还没有完全破解如何将该答案应用于这个问题Newsletter::Story
这里的纯 SQL 答案也很有启发性。
你太接近了。您只需要添加 not
Newsletter.find(newsletter_id).authors
.joins(group_member: :stories)
.where.not(stories: {status: [:draft, :submitted]})
.distinct
深入研究一些 Arel 语句,我能够通过使用 NOT EXISTS
子句和 JOIN
ing Newsletter Stories 向 GroupMembers 过滤那些不符合要求的故事,从而实现我在这里需要的东西状态为 draft
或 submitted
。给定 newsletter_id
,这对我有用:
Newsletter::Author
.where(newsletter_id: newsletter_id)
.where(
GroupMember.select("1")
.where(
Newsletter::Story.arel_table[:author_id].eq(Newsletter::Author.arel_table[:group_member_id])
)
.joins(:stories)
.where.not(
Newsletter::Story.arel_table[:status].not_eq_all([:draft, :submitted])
)
.arel.exists.not
)
生成的 SQL 看起来像这样:
SELECT * FROM newsletter_authors
WHERE newsletter_authors.newsletter_id = [newsletter_id]
AND NOT (
EXISTS (
SELECT 1 FROM group_members
INNER JOIN newsletter_stories ON newsletter_stories.author_id = group_members.id
WHERE newsletter_stories.author_id = newsletter_authors.group_member_id
AND NOT (
(newsletter_stories.status != 'draft' AND newsletter_stories.status != 'submitted')
)
)
)
在我正在处理的 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 :authorships, inverse_of: :group_member, class_name: "Newsletter::Author"
has_many :stories, inverse_of: :author, class_name: "Newsletter::Story"
end
newsletter.rb
class Newsletter < ApplicationRecord
has_many :authors, inverse_of: :newsletter
has_many :stories
end
newsletter/author.rb
class Newsletter::Author < ApplicationRecord
belongs_to :newsletter, inverse_of: :authors
belongs_to :group_member, class_name: "GroupMember", inverse_of: :authorships
end
newsletter/story.rb
class Newsletter::Story < ApplicationRecord
belongs_to :newsletter, inverse_of: :stories, optional: true
belongs_to :author, inverse_of: :stories, class_name: "GroupMember"
enum status: {draft: "draft", submitted: "submitted", published: "published"}, _default: "draft"
end
鉴于上述相关模型,这是我正在使用的框架:
- 每个时事通讯有 n 位作者(群组成员)和 n 位时事通讯。
- 每个小组成员都可以为给定的时事通讯撰写多个故事。
- 每个故事都是以下状态之一:草稿、已提交或已发布
- 故事草稿可能与时事通讯相关联,也可能不相关联
- 已提交或已发布的故事与时事通讯相关联
我想知道给定时事通讯的哪些作者没有处于 草稿 或 已提交 状态的故事。
给定 newsletter_id
,我可以通过这样的查询找出有草稿或已提交故事的成员:
Newsletter.find(newsletter_id).authors
.joins(group_member: :stories)
.where(stories: {status: [:draft, :submitted]})
.distinct
但是,我不确定如何否定这一点并得出与那组作者相反的结论。也就是说,给定时事通讯的作者没有草稿或提交的故事。他们是否发表过故事应该没有什么区别。
编辑
几个月前,我问 GroupMember (as Newsletter::Author)
-> Newsletter
-> 的嵌套关联,我还没有完全破解如何将该答案应用于这个问题Newsletter::Story
这里的纯 SQL 答案也很有启发性。
你太接近了。您只需要添加 not
Newsletter.find(newsletter_id).authors
.joins(group_member: :stories)
.where.not(stories: {status: [:draft, :submitted]})
.distinct
深入研究一些 Arel 语句,我能够通过使用 NOT EXISTS
子句和 JOIN
ing Newsletter Stories 向 GroupMembers 过滤那些不符合要求的故事,从而实现我在这里需要的东西状态为 draft
或 submitted
。给定 newsletter_id
,这对我有用:
Newsletter::Author
.where(newsletter_id: newsletter_id)
.where(
GroupMember.select("1")
.where(
Newsletter::Story.arel_table[:author_id].eq(Newsletter::Author.arel_table[:group_member_id])
)
.joins(:stories)
.where.not(
Newsletter::Story.arel_table[:status].not_eq_all([:draft, :submitted])
)
.arel.exists.not
)
生成的 SQL 看起来像这样:
SELECT * FROM newsletter_authors
WHERE newsletter_authors.newsletter_id = [newsletter_id]
AND NOT (
EXISTS (
SELECT 1 FROM group_members
INNER JOIN newsletter_stories ON newsletter_stories.author_id = group_members.id
WHERE newsletter_stories.author_id = newsletter_authors.group_member_id
AND NOT (
(newsletter_stories.status != 'draft' AND newsletter_stories.status != 'submitted')
)
)
)