如何从多态关联中的关联模型中提取不同的列值?

use*_*906 9 ruby-on-rails polymorphic-associations distinct-values

如果我在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
Run Code Online (Sandbox Code Playgroud)

对于给定的注释列表,如何从Title两个列BookArticle模型中选择不同的值?

例如,如果我必须列出书籍的标题和在一段时间内给出评论的文章,​​那我该怎么做呢?我可以很容易地挑选评论列表,但我要如何挑选与独特的游戏,从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    | 
+--------------------------------------+
Run Code Online (Sandbox Code Playgroud)

我需要标题的列表是'Book1','Book3','Article1'.

Sve*_*mer 1

最简单的方法:

Comment.all.includes(:ref).map { |comment| comment.ref.title }.uniq
Run Code Online (Sandbox Code Playgroud)

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

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

可重复使用的方式:

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
Run Code Online (Sandbox Code Playgroud)

此范围使用 arel 和子查询的 UNION 来获取标题。它基本上将参考文献的标题添加到评论对象中。由于作用域应该是可链接的,因此它返回 ActiveRecord Relation 而不是数组。要获得不同的标题,请附加unique.pluck(:title)

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

此作用域生成的 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"
Run Code Online (Sandbox Code Playgroud)

使用 Rails 5.1.2 和 sqlite 进行测试。