【问题标题】:Rails includes categories and sub-categories modelRails 包括类别和子类别模型
【发布时间】:2017-09-21 13:27:44
【问题描述】:

如何包含类别(“父”)和子类别(“子”)以摆脱 N+1 查询

class Category < ApplicationRecord
  belongs_to :parent, class_name: 'Category', foreign_key: :parent_id
  has_many :children, class_name: 'Category', foreign_key: :parent_id

  has_many :article_categories
  has_many :articles, through: :article_categories

  scope :roots, -> { where(parent_id: 0) }
  scope :children, -> { where.not(parent_id: 0) }
end

parent_id 为 0 的类别是“Category/Parent”

parent_id != 0 的类别是“子类别/儿童”

我在控制器中声明实例:

@articles = articles.includes(:categories)

在视图中:

@articles.each do |article|
  article.categories.roots #N+1 query solved using "includes(:categories)"
  article.categories.children.first  #N+1 query need to solve
  ..............

问题是因为article.categories.children.first,每一个新的循环都会对数据库产生一个新的请求

N+1 个请求是:

Category Load (0.8ms)  SELECT  "categories".* FROM "categories" INNER JOIN "article_categories" ON "categories"."id" = "article_categories"."category_id" WHERE "article_categories"."article_id" = $1 AND ("categories"."parent_id" != $2) ORDER BY "categories"."id" ASC LIMIT $3  [["article_id", 450], ["parent_id", 0], ["LIMIT", 1]]

我需要包含“父/类别”

还包括“children/Subcategories”以摆脱来自 ...“children.first”的 N+1 查询

更多细节:

articles.includes(:categories) =>

Article Load (3.9ms)  SELECT "articles".* FROM "articles" WHERE "articles"."customer_type" = $1 AND "articles"."aasm_state" IN ('published', 'unpublished') ORDER BY "articles"."title" ASC  [["customer_type", 0]]
ArticleCategory Load (3.6ms)  SELECT "article_categories".* FROM "article_categories" WHERE "article_categories"."article_id" IN (1, 2, ...)
Category Load (0.7ms)  SELECT "categories".* FROM "categories" WHERE "categories"."id" IN (1, 15, ...)

【问题讨论】:

    标签: sql ruby-on-rails postgresql activerecord


    【解决方案1】:

    我总是用Bullet gem来摆脱N+1问题

    【讨论】:

      【解决方案2】:

      由于您要预加载文章的子类别,您应该在Article 模型中定义此关联:

      class Article < ApplicationRecord
        has_many :article_categories
      
        # '-> { children }' executes 'children' scope defined in 'Category' model
        has_many :children_categories, -> { children }, through: :article_categories, source: :category, class_name: "Category"
      end
      

      现在您可以使用此关联预加载和获取子类别:

      # in controller
      @articles = articles.includes(:children_categories)
      
      # in view
      @articles.each do |article|
        article.children_categories.first
      

      编辑

      您可能还想将default_scope 添加到类别模型以始终仅获取子类别:

      class Category < ApplicationRecord
        default_scope { children }
      end
      

      如果您这样做,则不必将-&gt; { children } 条件添加到关联中。

      【讨论】:

      • "children_categories" 生成错误的 sql。 Article.last.children_categories.to_sql => "SELECT \"categories\".* FROM \"categories\" WHERE \"categories\".\"article_id\" = 450 AND (\"categories\".\"parent_id\ “!= 0)”。类别表没有“article_id”属性。
      • 编辑了答案
      • 不,articles.includes(:children_categories).first => ArticleCategory Load (2.7ms) SELECT "article_categories".* FROM "article_categories" WHERE ("categories"."parent_id" != $1) AND "article_categories"."article_id" = 1 [["parent_id", 0]] ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "categories" LINE 1: ...le_categories"。 * FROM "article_categories" WHERE ("categorie...
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-10-08
      • 2019-04-19
      • 2021-08-09
      • 2020-08-04
      • 1970-01-01
      • 1970-01-01
      • 2015-04-05
      相关资源
      最近更新 更多