Rails包含类别和子类别模型

时间:2017-09-21 13:27:45

标签: sql ruby-on-rails postgresql activerecord

我如何包含类别("父母")和子类别("儿童")以摆脱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的类别是&#34;类别/父级&#34;

带有parent_id!= 0的类别是&#34;子类别/儿童&#34;

我在控制器中声明了实例:

@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]]

我需要包含&#34; parent / Categories&#34;

还包括&#34; children / Subcategories&#34;从...&#34; children.first&#34;

中删除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, ...)

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

由于您要预加载文章的子类别,您应该在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

如果您这样做,则不必向关联添加-> { children }条件。