ruby-on-railsactiveadminformtastic

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


I am experiencing an N+1 query problem in an Active Admin (Formtastic) form. The query occurs when loading a select input that corresponds to a belongs_to association. The display_name on the associated model references another belongs_to association. Here are the model relationships:

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

Here are the relevant portions of the models:

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

This is the relevant Active Admin 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

The generation of the f.input :alumn select field is causing an N+1 query on graduation_class. This is because Formtastic generates the select options by calling alumn.display_name which in turn invokes the year method on the associated graduation_class.

My question is, how can I eager load graduation_class in this form? The includes :alumn, alumn: :graduation_class in the Active Admin class does not seem to work.

Update:

I can see from the server log, that GraduationClass is being loaded, but it still does not eliminate the N+1 query:

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

Solution

  • I finally solved this by building a custom collection on the admin field. Here is the relevant code:

    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
    

    It still results in an extra query, but it is much faster.