【问题标题】:Combining scopes that contain joins and groupings组合包含连接和分组的范围
【发布时间】:2020-08-26 01:29:34
【问题描述】:

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

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

我对允许 cmets 和其他模型从用户那里获得投票的多态关联有一个“可投票”的担忧。这包括一个::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

两个范围都需要显示带有作者姓名和收到的投票数的 cmets 列表。但是将两者结合起来,post.cmets.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.cmets.with_authors.merge( post.cmets.with_votes ) - 但这给出了同样的错误。有没有办法编写一个或两个范围,以便它们一起工作,但它们也可以单独使用,同时确保 ::with_votes 适用于使用可投票关联的其他模型?非常感谢期待!

【问题讨论】:

    标签: ruby-on-rails ruby postgresql group-by


    【解决方案1】:

    我发现为了防止 group_by 错误,您可以简单地将包含所需附加字段的附加 group 语句链接到预定义的范围或查询。就我而言,这意味着更少的代码重复,我最终定义了如下注释范围,确保封装了额外的 group 语句:

    scope :with_votes_and_authors, -> do
      with_authors.with_votes.group("users.username")
    end
    

    【讨论】:

      【解决方案2】:

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

      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
      

      【讨论】:

      • 您好 Itay,感谢您的建议!不幸的是,这个解决方案在与关联结合时表现不佳,这主要是我使用这些范围的地方:post.cmets.with_votes 引发错误,因为 self.class.name 返回“associationrelation”而不是“cmets”。我将在下面分享我确定的解决方案,以防它对其他人有帮助。
      猜你喜欢
      • 1970-01-01
      • 2017-01-30
      • 1970-01-01
      • 2016-08-18
      • 2016-04-14
      • 1970-01-01
      • 2020-04-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多