#22 Eager Loading
A good place to start when you’re trying to improve the performance of your Rails applications is to look at the database access. Reducing the number of queries made to the database can speed up your application significantly. One way to do this is to use a technique called eager loading.
Our app has Task and Project models.
The page above lists a large number of tasks, and the project that each one belongs to. In our TaskController
we’re getting all of the tasks and looping through them in the view.
<h1>Tasks</h1> <ul> <% @tasks.each do |task| %> <li><%= link_to task.name, task %> in <%= task.project.name %></li> <% end %> </ul>The view code for the Tasks index page.
The view page gets the name of the task and then the name of its project through an association. The problem with this approach is that a SQL query is generated each time we get the task’s project name. You can see this by looking inside the development log.
Project Load (0.2ms) SELECT * FROM "projects" WHERE ("projects"."id" = 60) CACHE (0.0ms) SELECT * FROM "projects" WHERE ("projects"."id" = 60) CACHE (0.0ms) SELECT * FROM "projects" WHERE ("projects"."id" = 60) CACHE (0.0ms) SELECT * FROM "projects" WHERE ("projects"."id" = 60) CACHE (0.0ms) SELECT * FROM "projects" WHERE ("projects"."id" = 60)
Our request makes repeated database calls.
We can see from the development log that the same call is made again and again as we get the name for a task’s project. Rails helps to reduce the number of calls by caching requests and retrieving repeated requests from the cache, rather than from the database. Caching was introduced in Rails 2.0; if we’d made this request in a Rails 1.x app then every request for a task’s project name would have caused a call to the database.
Caching is useful, but eager loading reduce the calls to the database even further. We enable it by making a change in the TasksController.
class TasksController < ApplicationController def index @tasks = Task.find(:all, :include => :project) end end
What we have done above is added a parameter to the line that fetches all of the tasks so that it gets the related projects too. The reason we use the singular :project
, rather than :projects
is that we use the name of the association, and in the Task
model we have belongs_to => :project
. Now, if we reload our index page and look in the development log we can see that the number of database requests is now reduced from 101 to two, which will improve the performance of our application.
Including More Than One Association
As well as belonging to a Project
, our Task
model has another association with the Comment
model.
class Task < ActiveRecord::Base belongs_to :project has_many :comments end
We can fetch the comments for a task along with its project by including both association names in an array. (Note that for comments we use the plural form as this is the name of the association.)
class TasksController < ApplicationController def index @tasks = Task.find(:all, :include => [:project, :comments]) end end
We can make use of even more complex associations. If our Comment model looks like this…
class Comment < ActiveRecord::Base belongs_to :task belongs_to :user end
…we can fetch the users for the comments by using a hash.
@tasks = Task.find(:all, :include => [:project, {:comments => :user }])
Using eager loading provides a great way of reducing your Rails application’s database access and can provide real. There is more information about them on the Rails API site at http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html.