【问题标题】:Ruby: Convert an array or hash to an activerecord relationRuby:将数组或哈希转换为活动记录关系
【发布时间】:2018-04-28 20:06:29
【问题描述】:

我正在尝试将哈希转换为 activerecord 关系,但我无法这样做。我打算使用 activerecord 关系对 Category 表进行排序然后过滤。最终目标是创建一个实例方法来过滤前 5 个访问过的类别,然后我可以在控制器中使用/调用它。这就是我的想法:

类别模型:

def top_5_visited
        Article.joins(:categories).group('categories.name').sum(:impressions_count)
// Add sort
// Limit to top 5 
end

类别控制器:

@categories = Category.top_5 visited

hash{"Simula"=>7, "Haskell"=>5, "JavaScript"=>10, "C#"=>112} 将通过以下查询创建:

total_count = Article.joins(:categories).group('categories.name').sum(:impressions_count)

我也尝试使用 sort_by 方法将其转换为数组:

total_count_sorted = total_count.sort_by {|_key, value| value}

我搜索了“将数组转换为 activerecord 关系”并引用了this 帖子,但对此进行了测试:

Category.where(id: total_count_sort.map(&:id))

在 Rails 控制台中,出现此错误:

NoMethodError: ["Simula", 7]:Array 的未定义方法 id

【问题讨论】:

  • 想要的结果是什么?我猜你想要一个包含 5 个类别的 ActiveRecord::Relation,但你应该澄清这一点,因为这个问题很模糊。
  • 而您实际上想要的是根据子查询之类的东西对数据库中的记录进行排序。你不需要先把总和拿出来。您使用的是什么 RDBMS?
  • 嗨,最大,正确我想要一个 ActiveRecord::Relation 包含前 5 个访问量最大的类别。我正在使用 Postgresql。

标签: ruby-on-rails ruby activerecord


【解决方案1】:

您想要做的事情从反向结束(类别)开始,并在 ORDER 子句中使用聚合。

Category.joins(:articles)
        .order('SUM(articles.impressions_count) DESC')
        .group(:id)
        .limit(5)

irb(main):005:0> Category.joins(:articles).order("SUM(articles.impressions_count) DESC").group('categories.id').limit(5)
  Category Load (1.5ms)  SELECT  "categories".* FROM "categories" INNER JOIN "articles" ON "articles"."category_id" = "categories"."id" GROUP BY categories.id ORDER BY SUM(articles.impressions_count) DESC LIMIT $1  [["LIMIT", 5]]
=> #<ActiveRecord::Relation [#<Category id: 4, name: "C#", created_at: "2017-11-15 15:06:32", updated_at: "2017-11-15 15:06:32">, #<Category id: 3, name: "JavaScript", created_at: "2017-11-15 15:06:32", updated_at: "2017-11-15 15:06:32">, #<Category id: 1, name: "Simula", created_at: "2017-11-15 15:03:37", updated_at: "2017-11-15 15:03:37">, #<Category id: 2, name: "Haskell", created_at: "2017-11-15 15:06:32", updated_at: "2017-11-15 15:06:32">]>

您应该创建一个类方法 - 而不是实例方法,因为这基本上只是一个范围,调用实例没有意义。

class Category < ApplicationRecord
  has_many :articles

  def self.order_by_article_impressions
    self.joins(:articles)
        .order('SUM(articles.impressions_count)')
        .group(:id)
  end

  def self.top_5_visited
    order_by_article_impressions.limit(5)
  end

  # Or use `scope` which is just syntactic sugar
  scope(:top_5_visited) -> { order_by_article_impressions.limit(5) }
end

【讨论】:

  • 谢谢 max,我必须将 .group(:categories.id) 放在 .order('SUM(articles.impressions_count)') 之前,否则我会收到此错误:ActiveRecord::StatementInvalid: PG ::GroupingError: ERROR: column "categories.id" 必须出现在 GROUP BY 子句中或在聚合函数中使用
  • 这很奇怪 - 我用 PG 测试过,顺序无关紧要。 .group(:id) 也会创建 GROUP BY categories.id。如果你想明确,你应该使用 group('categories.id') 字符串而不是符号
  • .order(...).group(:id), .group(:id).order(...), .group('categories.id').order(...) 将给出完全相同的查询。
  • 哦,这是我的错字。我的意思是 .group('categories.id')。难道是因为我有一个 has_many :categories, through: :article_categories 和 has_many :articles, through: :article_categories 关系?
  • 不,它也适用于间接关联。 github.com/maxcal/sandbox/tree/47310114
【解决方案2】:

将代码改为:

Category.where(id: total_count_sort.map(&:last))

【讨论】:

    猜你喜欢
    • 2020-07-13
    • 2017-12-10
    • 2017-06-09
    • 1970-01-01
    • 1970-01-01
    • 2021-01-17
    • 1970-01-01
    • 1970-01-01
    • 2010-12-11
    相关资源
    最近更新 更多