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

60 views
Skip to first unread message

Austin York

unread,
Sep 29, 2014, 11:15:36 PM9/29/14
to rubyonra...@googlegroups.com
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).

Muskalek

unread,
Sep 30, 2014, 2:30:43 AM9/30/14
to rubyonra...@googlegroups.com
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ł.
--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-ta...@googlegroups.com.
To post to this group, send email to rubyonra...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rubyonrails-talk/0c714b76-d9d0-4eee-9954-1c50779931bd%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Matt Jones

unread,
Sep 30, 2014, 1:03:30 PM9/30/14
to rubyonra...@googlegroups.com
+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_id`s 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 Jones

Timothy Mukaibo

unread,
Sep 30, 2014, 8:58:43 PM9/30/14
to rubyonra...@googlegroups.com
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..?



--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-ta...@googlegroups.com.
To post to this group, send email to rubyonra...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages