25

I've got an ActiveAdmin index page

ActiveAdmin.register Bill

And I am trying to display links to associated models

index do
  column "User" do |bill|
   link_to bill.user.name, admin_user_path(bill.user)
  end
end

But I run into the N+1 query problem - there's a query to fetch each user.

Is there a way of eager loading the bills' users?

tomblomfield
  • 889
  • 2
  • 9
  • 11

5 Answers5

34

The way to do this is to override the scoped_collection method (as noted in Jeff Ancel's answer) but call super to retain the existing scope. This way you retain any pagination/filtering which has been applied by ActiveAdmin, rather than starting from scratch.

ActiveAdmin.register Bill do
  controller do
    def scoped_collection
      super.includes :user
    end
  end

  index do
    column "User" do |bill|
     link_to bill.user.name, admin_user_path(bill.user)
    end
  end
end

As noted in official documentation at http://activeadmin.info/docs/2-resource-customization.html

Jay
  • 4,240
  • 3
  • 26
  • 39
33

There is an answer on a different post, but it describes well what you need to do here.

  controller do
    def scoped_collection
      Bill.includes(:user)
    end
  end

Here, you will need to make sure you follow scope. So if your controller is scope_to'ed, then you will want to replace the model name above with the scope_to'ed param.

Jeff Ancel
  • 3,076
  • 3
  • 32
  • 39
11

The existing answers were right at the time, but ActiveAdmin supports eager loading with a much more convenient syntax now:

ActiveAdmin.register Bill do
  includes :user
end

See the docs for resource customization

Thilo
  • 17,565
  • 5
  • 68
  • 84
  • 1
    Yes, [this eager loading DSL](https://activeadmin.info/2-resource-customization.html#eager-loading) was [added by Timo in 2014](https://github.com/activeadmin/activeadmin/pull/3464). – Piers C Nov 20 '19 at 20:52
1

I've found scoped_collection loads all the entries, instead of just the ones for the page you are displaying. I think a better option is apply_collection_decorator that will only preload the items you are effectively displaying.

controller do
  def apply_collection_decorator(collection)
    collection.includes(:user)
  end
end
ZZ4
  • 56
  • 1
  • 5
1

IMPORTANT EDIT NOTE : what follows is actually false, see the comments for an explanation. However I leave this answer where it stands because it seems I'm not the only one to get confused by the guides, so maybe someone else will find it useful.

i assume that

class Bill < ActiveRecord::Base
  belongs_to :user
end

so according to RoR guides it is already eager-loaded :

There’s no need to use :include for immediate associations – that is, if you have Order belongs_to :customer, then the customer is eager-loaded automatically when it’s needed.

you should check your SQL log if it's true (didn't know that myself, i was just verifying something about :include to answer you when i saw this... let me know)

m_x
  • 12,357
  • 7
  • 46
  • 60
  • i think it's very doubtful, but who knows ? – m_x Dec 14 '11 at 23:20
  • 2
    _the customer is eager-loaded automatically when it’s needed_ - that just means, if you have 1000 orders and iterate over them, you call `order1.customer`, and `customer1` is loaded because it is needed. Then you access `order2.customer`, but hey, you didn't say you were going to need it, so another query to database is done. But it still doesn't know you're going to need `customer3` on your next iteration, so you'll get 1000 queries instead of 1. – RocketR Mar 16 '12 at 16:29
  • thanks for clearing that. I find this way to expose things confusing, but english is not my native language. – m_x Mar 19 '12 at 09:48
  • That is talking about adding :include to the definition of an association, and saying an association doesn't need to self-include. I believe that's rather different than adding an includes call to a scope which should eager load the added models. (I say 'should' because...well guess why I'm on this site right now) – elc Feb 12 '13 at 19:55