首页 > 解决方案 > 组合包含连接和分组的范围

问题描述

我正在寻找一个 DRY 解决方案来解决在 Rails 中组合两个或多个包含不同连接和分组的范围的问题。我有一个带有::with_authors范围的评论模型(以防止在引用作者姓名时出现 N+1),如下所示:

# comment.rb
scope :with_authors, -> do
  select("comments.*, users.username AS author_name")
  .joins(:author)
end

而且我对允许评论和其他模型从用户那里获得投票的多态关联有一个“可投票”的关注。这包括::with_votes如下范围:

# votable.rb
scope :with_votes, -> do
  select( "#{self.table_name}.*, COALESCE(SUM(votes.value), 0) AS vote_sum" )
  .left_joins(:votes)
  .group("#{self.table_name}.id")
end

两个范围都需要显示带有作者姓名和收到的投票数的评论列表。但是将两者结合起来,post.comments.with_authors.with_votes会导致熟悉的错误:

> PG::GroupingError: ERROR:  column "users.username" must appear in the
> GROUP BY clause or be used in an aggregate function

我意识到我可以编写一个单独的范围来执行连接并相应地更改分组,但这不能转移到支持投票的其他模型,并且违反了每个方法或范围应该做一件事的原则。我也尝试使用合并 - post.comments.with_authors.merge( post.comments.with_votes ) - 但这给出了同样的错误。有没有办法编写一个或两个范围,以便它们一起工作,但它们也可以单独使用,同时确保 ::with_votes 适用于使用可投票关联的其他模型?非常感谢期待!

标签: ruby-on-railsrubypostgresqlgroup-by

解决方案


一种方法是使用子查询而不是将分组应用于基本查询。例如:

scope :with_votes, -> do
  select( <<~SQL )
    (
      SELECT COUNT(*)
      FROM votes
      WHERE votable_type = '#{self.class.name}' AND 
            votable_id = '#{self.table_name}.id'
    ) AS votes
  SQL
end

推荐阅读