ruby-on-railsperformancerails-bullet

Ruby on Rails - Bullet/N+1


On Rails 4. I recently installed the bullet gem for my development environment to clear up my app's N+1 queries. Relevant models:

Submissions: Belongs to Categories and Users. Has many SubmissionDetails.

Users: Has many Submissions

Categories: Has many Submissions. Belongs to Awards.

Awards: Has many Categories (and Submissions through Categories)

SubmissionDetails: Belongs to Submissions

In my submission's index page, I have an each do statement to display each submission made by the current user.

<% current_user.submissions.order('created_at DESC').in_groups_of(3, false) do |group| %>
  <div class="row">
    <% group.each do |submission| %>

After that, I list information about the submission, including its associated category, award, and submission details information. Bullet is saying I'm having N+1 issues with this statement:

N+1 Query detected Submission => [:category] Add to your finder: :include => [:category]
N+1 Query detected Submission => [:user] Add to your finder: :include => [:user]
N+1 Query detected Submission => [:submission_details] Add to your finder: :include => [:submission_details]

Every time I try to add .includes with all three of those models, it only picks the first one I list (this is not surprising). I figure I need to go a different route when multiple models are involved--perhaps a join statement?

(When I make :category the first item, it adds this notice):

N+1 Query detected Category => [:award] Add to your finder: :include => [:award]

(So I also need to include as part of the statement a way to make Award fit in there as well, which, again, has many Submissions through Categories).

So assuming I can't do one .includes for three different models, is there another way to go about this? Thanks.


Solution

  • Just to be more clear, let me make the details more visible:

    class User < ActiveRecord::Base
      has_many :submissions
    end
    
    class Submission < ActiveRecord::Base
      belongs_to :category
      belongs_to :user
      has_many   :submission_details
    end
    
    class SubmissionDetail < ActiveRecord::Base
      belongs_to :submission
    end
    
    class Category < ActiveRecord::Base
      belongs_to :award
      has_many   :submissions
    end
    
    class Award < ActiveRecord::Base
      has_many :categories
      has_many :submissions, through: :categories
    end
    

    If I understand correctly, for your current_user, you are listing his submissions. For each submission you want to list submission_details and the category it belongs. For every category you list the award too.

    <% current_user.submissions.order('created_at DESC').in_groups_of(3, false) do |group| %>
      <div class="row">
        <% group.each do |submission| %>
          ...
          <div><%= submission.category %></div>
          <div><%= submission.category.award %></div>
          <%= submission.submissions_details.each do |submission_detail| %>
            ...
          <% end %>
        <% end %>
      </div>
    <% end %> 
    

    You can remove N+1 problem by using includes in the following manner:

    current_user.submissions.includes(:submission_details, :category => :award)
    

    For more details about includes please refer to:

    1. Rails guides eager-loading-associations
    2. Rails guides eager-loading-multiple-associations
    3. Rails api - includes

    associations