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

10
如果我有3个模型之间的多态关联,如下所示:
评论(Comment)
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

如何从BookArticle模型的Title列中挑选出给定评论列表中的不同值?

例如,如果我必须列出在一段时间内给出了书籍和文章的评论的标题,则该如何操作?我可以轻松地选择评论列表,但如何选择相关的唯一书籍和文章标题呢?

例如:

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'


如果您发布一个示例,那么其他人帮助您的难度将会降低。 - Alejandro Montilla
你的关联对我来说没有意义。评论表中的ref_id列如何与文章表和书籍表的主键相关联? - David Aldridge
在定义关联时,我有 :foreign_key => 'ref_id'。 - user3075906
4个回答

1
我建议您查看这个教程:https://web.archive.org/web/20180928102044/http://karimbutt.github.io:80/blog/2015/01/03/step-by-step-guide-to-polymorphic-associations-in-rails/,看看它是否适合您。
如果您控制模型代码,我会设置注释以belong_to一个通用的可评论对象。例如:
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

然后,无论给定任何一组评论,您都可以运行

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

这看起来好像只是为了得到标题而做很多工作,但是如果您坚持这个项目,我预计图书和文章将共享这种类型的大量代码,并可能在未来添加其他可评论对象。总的来说,拥有一个通用对象将节省很多工作。


1

简单的方法:

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

获取所有评论,急切加载它们的引用,并返回包含唯一标题的数组。急切加载部分并非必需,但可能性能更好。 执行3个查询,一个用于评论,每种引用各一个。 您可以将all替换为任何范围。 请注意,这会获取所有评论及其引用,并使用Ruby将其转换为数组,而不是SQL。这可以完美地工作,但性能可能会受到影响。通常最好使用distinct获取唯一值,并使用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

这个作用域使用arel和子查询的UNION来获取标题。它基本上将ref的标题添加到注释对象中。由于作用域应该是可链接的,所以它返回一个ActiveRecord Relation而不是一个数组。要获取不同的标题,请附加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 下进行了测试。


如果书籍和文章具有相同的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' - Dmitry Polushkin

1

我认为最简单的方法是,在不直接使用 Arel API 或迭代数组的情况下,在 BookArticle 上定义 .titles_for_comments 作用域,让您在给定 comments 集合时从每个表中选择不同的标题。然后为 Comment 定义一个使用 [Book|Article].titles_for_comments.distinct_titles 作用域。例如,下面的模型和作用域定义允许您通过调用 Comment.distinct_titles 找到任何给定的 Comment::ActiveRecord_Relation 实例的不同 Book 和 Article 标题。

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

1

Try this one

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接