Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you fix the n+1 queries problem in ActiveRecord (Rails 3) when also using the after_initialize callback?

Model:

class Project < ActiveRecord::Base
  has_many :user_roles 
  after_initialize :add_user_roles

  def add_user_roles
    UserRoles.all.each do |ur|
      self.user_roles << ur unless self.user_roles.include?(ur)
    end
  end
end

Statement that finds the projects:

@projects = Project.includes(:user_roles)

So you can see, I'm telling it to include the user roles association in the query. However, I'm still seeing the n+1 queries problem: it finds the roles once for each project.

If I remove the usage of self.user_roles from the callback and look at the logs, I can see it finds the projects and their user roles in 2 queries - one for the projects, and one for the roles using project_id in (1,2,3,4,5...,n).

Is there a way to work around this?

Let me clarify a bit: While I'm willing to work around my specific situation if required, I'd much prefer answers that focused on how to fix the problem in general. I am capable of writing a kludge to get the data in the state that I want it without using the after_initialize callback, and hence not coming into the n+1 queries problem. However, I would rather not do that, so I prefer answers to the general problem as opposed to my specific example.

like image 875
Sammy Larbi Avatar asked Jun 30 '11 13:06

Sammy Larbi


3 Answers

take a look at rails eager loading http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

you can load association while loading object using includes

User.find(2).includes(:assets)#will load all assets with user

or you can specify in model to eager load association

app/models/user.rb

class User< AR::Base
   has_many :posts,:include=>:comments
end

class Post < AR::Base
  has_many :comments
  belongs_to :user
end

now u.posts will load comments for each post

like image 180
Naveed Avatar answered Oct 06 '22 00:10

Naveed


Even eager-loaded associations aren't available in after_initialize (they get loaded after the record is initialized). See this Rails issue for some discussion:

https://github.com/rails/rails/issues/13156

Related to the original question: it looks like every project will have the same set of UserRole objects. I'm guessing there's a has_many :through that's been sanitized out, but even so, how does a Project ever wind up not having the full set? I'm not seeing how Project and UserRole are actually connected here - from what's visible in the example, this:

class Project < ActiveRecord::Base
  def user_roles
    UserRole.all
  end
end

would accomplish the same thing as the after_initialize...

like image 22
Matt Jones Avatar answered Oct 05 '22 23:10

Matt Jones


This is likely caused by the after_initialize callback, which is run every time each of the objects are initialized. If the point of the callback is to automatically assign every role to every user (unless already assigned), then you could do this via a before_save filter instead. That way, the code won't run when doing your Project.includes(:user_roles) finder.

like image 35
Frankie Roberto Avatar answered Oct 06 '22 00:10

Frankie Roberto