How to avoid N+1 queries in Rails "each" loops?

In a todo list-style app, I have the following ActiveRecord model
method:

class Task < ActiveRecord::Base

def project_name
project.tasks.length > 0 ? “#{project.name} - #{name}” :
project.name
end
end

The idea is to provide additional project information if there are one
or
more tasks on the project.

However, when invoked regularly on views this creates performance
concerns
(especially with a growing data set).

What is the best way to optimize this query so that it doesn’t create
N+1
query type issues when invoked from “each” loops in the view?

(Feel free to post answers on StackOverflow
http://stackoverflow.com/questions/26112070/how-to-avoid-n1-queries-in-rails-each-loops
).

You would probably want to make a counter cache for tasks on the project
(you can find out more here:
http://api.rubyonrails.org/v4.1.1/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to),
and additional when retrieving tasks from the database, when you know
you will need their project data, use eager loading (more here:
http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations).

Michał.

On Monday, 29 September 2014 23:15:36 UTC-4, Austin York wrote:

The idea is to provide additional project information if there are one or
more tasks on the project.

However, when invoked regularly on views this creates performance concerns
(especially with a growing data set).

What is the best way to optimize this query so that it doesn’t create N+1
query type issues when invoked from “each” loops in the view?

+1 what Michał said about eager-loading. One additional tricky thing:
prefer size over length for associations and relations. In plain
Ruby,
size, length and count are more or less identical - but for
ActiveRecord collections they have slightly different meanings:

  • length is the most straightforward: it’s ALWAYS the number of
    records
    in the collection. If the collection isn’t currently loaded, calling
    length on it will trigger a SQL query to load all the records.

  • count is the opposite: it ALWAYS runs a SQL query. It doesn’t load
    records, it uses SQL’s COUNT() function. It can also return things
    that
    aren’t numbers; doing Task.group(:project_id).count will give you back
    a
    hash with project_ids as keys and the number of matching tasks as
    values.

  • size is the middle: if the collection is loaded, it works like
    length. Otherwise it works like count

–Matt J.

Wow, that’s a great explanation Matt. I was not aware of the subtle
differences.

It sounds like a sensible default would be to use size, unless you
know
you need a specific behaviour…?