简体   繁体   English

Rails:查找嵌套关联为空的记录

[英]Rails: Finding records where a nested association is empty

In a Rails application I'm working on, I've got a few different models associated like this (condensed for clarity):在我正在处理的 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

Given the above associated models, here's the framework I'm working within:鉴于上述相关模型,这是我正在使用的框架:

  • Each Newsletter has n Authors (Group Members) and n Newsletters.每个时事通讯有n 个作者(组成员)和n 个时事通讯。
  • Each Group Member can author multiple stories for a given newsletter.每个组成员都可以为给定的时事通讯撰写多个故事。
  • Each story is one of theses status states: draft , submitted , or published每个故事都是这些状态状态之一: draftsubmittedpublished
  • A draft story may or may not be associated with a Newsletter故事草稿可能与时事通讯相关联,也可能不相关联
  • A submitted or published story is associated with a Newsletter提交或发布的故事与时事通讯相关联

I'd like to find out which Authors for a given newsletter have NO stories with a draft or submitted status.我想找出给定时事通讯的哪些作者没有处于草稿或已提交状态的故事。

Given newsletter_id , I can find out the members that DO have a draft or submitted story with a query like this:给定newsletter_id ,我可以通过这样的查询找出有草稿或提交故事的成员:

  Newsletter.find(newsletter_id).authors
    .joins(group_member: :stories)
    .where(stories: {status: [:draft, :submitted]})
    .distinct

However, I'm not sure how to negate that and get the the opposite of that set of authors.但是,我不确定如何否定这一点并得出与那组作者相反的观点。 That is, authors for a given newsletter who DON'T have draft or submitted stories.也就是说,给定时事通讯的作者没有草稿或提交的故事。 Whether or not they have published stories should make no difference.他们是否发表过故事应该没有什么区别。

EDIT编辑

I asked a similar question a few months ago about identifying records where records of an associated model did not exist.几个月前我问了一个类似的问题,关于识别不存在关联 model 记录的记录。 I think that's a very similar approach for what I need to do here, but I haven't quite cracked how to apply that answer to this question due to the nested association of GroupMember (as Newsletter::Author) -> Newsletter -> Newsletter::Story我认为这是我在这里需要做的非常相似的方法,但由于GroupMember (as Newsletter::Author) -> Newsletter -> Newsletter::Story ,我还没有完全破解如何将该答案应用到这个问题Newsletter::Story

A pure SQL answer here would also be enlightening.这里的纯 SQL 答案也很有启发性。

You're so close.你是如此接近。 You just need to add not你只需要添加not

Newsletter.find(newsletter_id).authors
    .joins(group_member: :stories)
    .where.not(stories: {status: [:draft, :submitted]})
    .distinct

Dipping into some Arel statements, I was able to achieve what I needed here by using a NOT EXISTS clause along with JOIN ing Newsletter Stories to the GroupMembers, filtering the stories which don't have a status of draft or submitted .深入了解一些 Arel 语句,我能够通过使用NOT EXISTS子句以及JOIN新闻稿故事到 GroupMembers 来实现我在这里需要的东西,过滤掉状态为draftsubmitted的故事。 Given the newsletter_id , here's what worked for me:鉴于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
    )

That generates SQL that looks something like this:生成的 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')
      )
    )
  )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM