Rails ActiveAdmin 消除对属于另一个模型的自定义属性的 n+1 个查询

Rails ActiveAdmin Eliminating n+1 queries on custom attributes belong to another model

我正在使用 activeadmin 来显示分支模型记录,分支模型有很多分支phone,我想显示为这个分支创建的第一个分支phone作为额外的自定义activeadmin分支模型屏幕上的属性,所以我这样写的

    show do
    attributes_table do
      row :id
      row :manager_name
      row :manager_email
      row :phone_number do |branch|
        branch&.branch_phones&.order(created_at: :asc)&.first&.phone_number
      end
    end
  end

  index do
    column :id
    column :manager_name
    column :manager_email
    column :phone_number do |branch|
      branch&.branch_phones&.order(created_at: :asc)&.first&.phone_number
    end
    actions
  end

该代码的问题在于它会导致我进行 n+1 次查询,每次代码获得一个分支时,它都会在其中创建一个额外的查询以获取该分支的分支 phones,这在 activeadmin

上调用分支模型屏幕时会出现类似的结果
Processing by Admin::BranchesController#index as HTML
  Parameters: {"subdomain"=>""}
  AdminUser Load (0.8ms)  SELECT  "admin_users".* FROM "admin_users" WHERE "admin_users"."id" =  ORDER BY "admin_users"."id" ASC LIMIT   [["id", 1], ["LIMIT", 1]]
  Rendering /usr/local/bundle/gems/activeadmin-2.7.0views/active_admin/resource/index.html.arb
   (0.6ms)  SELECT COUNT(*) FROM (SELECT  1 AS one FROM "branches" LIMIT  OFFSET ) subquery_for_count  [["LIMIT", 30], ["OFFSET", 0]]
  CACHE  (0.1ms)  SELECT COUNT(*) FROM (SELECT  1 AS one FROM "branches" LIMIT  OFFSET ) subquery_for_count  [["LIMIT", 30], ["OFFSET", 0]]
   (0.4ms)  SELECT COUNT(*) FROM "branches"
  CACHE  (0.0ms)  SELECT COUNT(*) FROM (SELECT  1 AS one FROM "branches" LIMIT  OFFSET ) subquery_for_count  [["LIMIT", 30], ["OFFSET", 0]]
  Branch Load (0.5ms)  SELECT  "branches".* FROM "branches" ORDER BY "branches"."id" desc LIMIT  OFFSET   [["LIMIT", 30], ["OFFSET", 0]]
  Store Load (0.6ms)  SELECT "stores".* FROM "stores" WHERE "stores"."id" IN (, , )  [["id", 21], ["id", 1], ["id", 2]]
  BranchPhone Load (0.4ms)  SELECT "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" IN (, , , , , , , , , , )  [["branch_id", 25], ["branch_id", 24], ["branch_id", 22], ["branch_id", 20], ["branch_id", 19], ["branch_id", 14], ["branch_id", 11], ["branch_id", 4], ["branch_id", 3], ["branch_id", 2], ["branch_id", 1]]
  BranchPhone Load (0.7ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 25], ["LIMIT", 1]]
  BranchPhone Load (0.5ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 24], ["LIMIT", 1]]
  BranchPhone Load (0.3ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 22], ["LIMIT", 1]]
  BranchPhone Load (0.6ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 20], ["LIMIT", 1]]
  BranchPhone Load (0.3ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 19], ["LIMIT", 1]]
  BranchPhone Load (0.3ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 14], ["LIMIT", 1]]
  BranchPhone Load (0.2ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 11], ["LIMIT", 1]]
  BranchPhone Load (0.2ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 4], ["LIMIT", 1]]
  BranchPhone Load (0.3ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 3], ["LIMIT", 1]]
  BranchPhone Load (0.2ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 2], ["LIMIT", 1]]
  BranchPhone Load (0.2ms)  SELECT  "branch_phones".* FROM "branch_phones" WHERE "branch_phones"."branch_id" =  ORDER BY "branch_phones"."created_at" ASC LIMIT   [["branch_id", 1], ["LIMIT", 1]]
  Rendered /usr/local/bundle/gems/activeadmin-2.7.0views/active_admin/resource/index.html.arb (219.4ms)
Completed 200 OK in 258ms (Views: 208.7ms | ActiveRecord: 23.2ms)

那么,有什么解决方案可以消除分支 phone 的 n+1 查询吗? 知道我试图使用这样的控制器操作来消除它

但是分支phone的N+1查询还是没有解决

您收到 (N+1) 个查询,因为您正在订购 branch_phones。默认 Rails returns 记录在同意顺序。您无需再次订购。

我相信你有一些像下面这样的联想:

# app/models/branch.rb

class Branch < ApplicationRecord
  has_many :branch_phones
end
# app/models/branch_phone.rb

class BranchPhone < ApplicationRecord
  belongs_to :branch
end

请将此行添加到您的 app/admin/brach.rb 文件中。

# app/admin/brach.rb

controller do
  def scoped_collection
    super.includes(:branch_phones)
  end
end

index do
  column :id
  column :manager_name
  column :manager_email
  column :phone_number do |branch|
    branch.branch_phones.first&.phone_number
  end

  actions
end

现在 includes 方法在 ActiveAdmin 中可用。您需要做的就是将其添加到所需的管理文件中。

# app/admin/brach.rb
ActiveAdmin.register Branch do

  includes :branch_phones

  index do
    column :id
    column :manager_name
    column :manager_email
    column :phone_number do |branch|
      branch&.branch_phones&.order(created_at: :asc)&.first&.phone_number
    end
    actions
  end
end