【问题标题】:How do I pluck a distinct column value from an associated model in a polymorphic association?如何从多态关联中的关联模型中提取不同的列值?
【发布时间】:2017-06-26 19:58:30
【问题描述】:

如果我在 3 个模型之间存在多态关联:

评论

belongs_to :book, :class_name => 'Book', :foreign_key => 'ref_id', conditions: "comments.ref_type = 'Book'"
belongs_to :article, :class_name => 'Article', :foreign_key => 'ref_id', conditions: "comments.ref_type = 'Article'"
belongs_to :ref, :polymorphic => true

对于给定的 cmets 列表,我如何从 BookArticle 模型的 Title 列中选择不同的值?

例如,如果我必须列出某个时间段内已获得 cmets 的书籍和文章的标题,那么我该怎么做?我可以轻松选择评论列表,但如何从 BookArticle 中选择相关的唯一标题?

例如:

Book
+--------------+
| Id |  Title  |
+----+---------+
| 1  | 'Book1' | 
| 2  | 'Book2' |
| 3  | 'Book3' |
+--------------+

Article
+-----------------+
| Id |   Title    |
+----+------------+
| 1  | 'Article1' |
| 2  | 'Article2' |
+-----------------+

Comments
+--------------------------------------+
| Id |  comment   | ref_id | ref_type  |
+----+------------+--------+-----------+
| 1  | 'comment1' |   1    |   Book    | 
| 2  | 'comment2' |   1    |   Book    | 
| 3  | 'comment3' |   1    |   Article | 
| 4  | 'comment4' |   3    |   Book    | 
+--------------------------------------+

我需要标题列表为'Book1''Book3''Article1'

【问题讨论】:

  • 如果你发布一个例子,它会让别人更容易帮助你。
  • 你的联想对我来说没有意义。 Comment 的 ref_id 列与 Article 和 Book 的主键有什么关系?
  • 我在定义关联时有 :foreign_key => 'ref_id'。

标签: ruby-on-rails polymorphic-associations distinct-values


【解决方案1】:

简单的方法:

Comment.all.includes(:ref).map { |comment| comment.ref.title }.uniq

获取所有 cmets,急切加载它们的 refs,并返回一个包含唯一标题的数组。急切加载部分并不是绝对必要的,但它可能会表现得更好。 执行 3 个查询,一个用于 cmets,一个用于每种 ref。 您可以将 all 替换为任何范围。 请注意,这会获取所有 cmets 及其引用并使用 ruby​​ 将其转换为数组,而不是 SQL。这完美地工作,但性能可能会受到影响。通常最好使用 distinct 来获取唯一值并使用 pluck 来获取这些值的数组。

这种方法适用于各种参考。因此,如果我们引入第三种 ref,例如发布,它将自动包含在此查询中。

复用方式:

class Comment < ApplicationRecord
  belongs_to :book, class_name: 'Book', foreign_key: 'ref_id'
  belongs_to :article, class_name: 'Article', foreign_key: 'ref_id'
  belongs_to :ref, polymorphic: true

  scope :with_ref_titles, lambda {
    book_titles = select('comments.*, books.title').joins(:book)
    article_titles = select('comments.*, articles.title').joins(:article)
    union = book_titles.union(article_titles)
    comment_table = arel_table
    from(comment_table.create_table_alias(union, :comments).to_sql)
  }
end

此范围使用 arel 和子查询的 UNION 来获取标题。 它基本上将 ref 的标题添加到评论对象中。由于范围应该是可链接的,它返回一个 ActiveRecord 关系而不是一个数组。要获得不同的标题,请附加 distinct.pluck(:title)

comments = Comment.with_ref_titles
comments.distinct.pluck(:title) # => ["Article1", "Book1", "Book3"]
comments.where('title LIKE "Book%"').distinct.pluck(:title) # => ["Book1", "Book3"]

此范围产生的 SQL 查询如下所示:

SELECT DISTINCT "title" FROM ( SELECT comments.*, books.title FROM "comments" INNER JOIN "books" ON "books"."id" = "comments"."ref_id" UNION SELECT comments.*, articles.title FROM "comments" INNER JOIN "articles" ON "articles"."id" = "comments"."ref_id" ) "comments"

使用 rails 5.1.2 和 sqlite 测试。

【讨论】:

  • 如果 book 和 article 的 ID 相同,则会同时加载它们,并且其中一个会出错。必须包含以下代码:``` belongs_to :book, -> { where('ref_type = ?', 'Book') }, class_name: 'Book', foreign_key: 'ref_id' belongs_to :article, -> { where ('ref_type = ?', 'Article') }, class_name: 'Article', foreign_key: 'ref_id' ```
【解决方案2】:

我认为最简单的方法是在 BookArticle 上定义 .titles_for_comments 范围,让您在给定时从每个表中选择不同的标题,而不直接使用 Arel API 或迭代数组comments 的集合。然后为Comment 定义一个.distinct_titles 范围,它同时使用[Book|Article].titles_for_comments。例如,下面的模型和范围定义让您可以通过调用 Comment.distinct_titles 来查找任何给定 Comment::ActiveRecord_Relation 实例的不同书籍和文章标题

class Book < ActiveRecord::Base
  has_many :comments, as: :ref

  def self.titles_for_comments(comment_ids)
    joins(:comments).where(comments: { id: comment_ids }).distinct.pluck(:title)
  end
end

class Article < ActiveRecord::Base
  has_many :comments, as: :ref

  def self.titles_for_comments(comment_ids)
    joins(:comments).where(comments: { id: comment_ids }).distinct.pluck(:title)
  end
end

class Comment < ActiveRecord::Base
  belongs_to :ref, polymorphic: true

  def self.distinct_titles
    comment_ids = ids
    Article.titles_for_comments(comment_ids) + Book.titles_for_comments(comment_ids)
  end
end

您可以下载此 Gist 并使用 ruby polymorphic_query_test.rb 运行它,测试应该通过 https://gist.github.com/msimonborg/907eb513fdde9ab48ee881d43ddb8378

【讨论】:

    【解决方案3】:

    试试这个

    titles_from_books = Comment.joins('INNER JOIN books on comments.ref_id = books.id').where('comments.ref_type = ?','Book').pluck('books.title')
    
    titles_from_articles = Comment.joins('INNER JOIN articles on comments.ref_id = article.id').where('comments.ref_type = ?','Article').pluck('articles.title')
    
    final_titles = (titles_from_books + titles_from_articles).uniq
    

    【讨论】:

      【解决方案4】:

      我建议你看看这个教程:http://karimbutt.github.io/blog/2015/01/03/step-by-step-guide-to-polymorphic-associations-in-rails/ 看看它是否适合你。

      如果您控制模型代码,我会将 cmets 设置为属于通用可注释对象。示例:

      class Comment < ActiveRecord::Base
        belong_to :commentable, :polymorphic => true
      end
      

      class Book < ActiveRecord::Base
        has_many :comments, as: commentable
      end
      
      class Article < ActiveRecord::Base
        has_many :comments, as: commentable
      end
      

      然后给定任何一组你可以运行的 cmets

      Comments.each do |comment|
        comment.commentable.title
      end.uniq
      

      目前,仅仅获得标题似乎需要做很多工作,但如果您坚持这个项目,我希望书籍和文章能够分享大量此类代码,因为以及将来可能添加其他可评论的对象。总体而言,拥有一个通用对象将节省大量工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多