Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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"
like image 882
Tom Aranda Avatar asked Jan 04 '23 06:01

Tom Aranda


1 Answers

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.

like image 176
Tom Aranda Avatar answered Jan 13 '23 08:01

Tom Aranda