如何避免 N+1 查询并在活动管理表单上执行预加载

How to avoid N+1 Query and Perform Eager Loading on Active Admin Form

我遇到了 N+1 query problem in an Active Admin (Formtastic) 表格。加载对应于 belongs_to 关联的 select 输入时会发生查询。关联模型上的 display_name 引用了另一个 belongs_to 关联。以下是模型关系:

:user 
   |-- belongs_to :alum 
                     |-- belongs_to :graduation_class
                                                   |-- year

以下是模型的相关部分:

app/models/user.rb

class User < ApplicationRecord
  ...
  belongs_to :alumn, optional: true, touch: true
  ...
end

app/models/alumn.rb

class Alumn < ApplicationRecord
  belongs_to :graduation_class, touch: true
  delegate :year, to: :graduation_class
  has_many :users
  ...
  def display_name
    "#{year}: #{last_name}, #{first_name} #{middle_name}"
  end
  ...
end

这是相关的活动管理员class:

app/admin/user.rb

ActiveAdmin.register User do
  ...
  includes :alumn, alumn: :graduation_class
  ...
  form do |f|
    f.inputs do
      f.input :alumn  # This is causing the N+1 query
      ...
    end
  end
  ...
end

f.input :alumn select 字段的生成导致对 graduation_class 的 N+1 查询。这是因为 Formtastic 通过调用 alumn.display_name 生成 select 选项,而 alumn.display_name 又调用关联的 graduation_class 上的 year 方法。

我的问题是,如何以这种形式预先加载 graduation_class? Active Admin class 中的 includes :alumn, alumn: :graduation_class 似乎不起作用。

更新:

从服务器日志可以看出,正在加载GraduationClass,但仍然没有消除N+1查询:

GraduationClass Load (0.6ms)  SELECT "graduation_classes".* FROM "graduation_classes"

嵌套包含仅通过使用哈希来完成。

includes({:alumn => :graduation_class})

如果构建 Formtastic select 输入生成不需要的查询,请尝试使用 pluck 仅获取构建列表所需的字段。

我最终通过在 admin 字段上构建自定义集合解决了这个问题。这是相关代码:

app/admin/user.rb

ActiveAdmin.register User do
  ...
  includes :alumn, alumn: :graduation_class
  ...
  form do |f|
    f.inputs do
      f.input :alumn, as: :select, 
        collection: Alumn.includes(:graduation_class).where(...)
                      .collect { |a| [ a.display_name, a.id ] }
      ...
    end
  end
  ...
end

它仍然会导致额外的查询,但速度要快得多。

摘自https://rubyinrails.com/2018/02/20/rails-activeadmin-n-1-query-optimization/

将此添加到控制器块中的 Active Admin 资源

ActiveAdmin.register(MyResource) do
  # ...

  controller do
    # ...

    def scoped_collection
      super.includes whatever: :you_want
    end
  end
end