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.
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:
associations
To include those associations, I would create a scope for submissions. While you're at it, add a latest
scope.
class Submission
scope :eager, -> { includes(:submission_details, :category => [:award]) }
scope :latest, -> { order("created_at DESC") }
end
Then simply
current_user.submissions.latest.eager [...]
You shouldn't have to include :user
, but I've noticed Rails isn't too clever about such references.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With