Preload Active Record Associations in Rails Application

Preload Active Record Associations in Rails Application

Active Record makes database querying so simple. Chain together a few methods and bam, you’ve saved your self lines and lines of T-SQL. The problem is this simplicity masks the underlying operation and it’s very easy to not realize how inefficient your database calls are.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
class User < ActiveRecord::Base
  has_many :posts
end

class Post < ActiveRecord::Base
  has_many :comments
  belongs_to :user
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

Let’s say on a users profile page we would like to show a listing of comments from this user.

1
2
3
4
5
6
7
@user = User.find(id)

@user.posts.each do |post|
   post.comments.each do |comment|
      <%= comment.message %>
   end
end

What you end up with is something like:

1
2
3
4
5
6
7
8
9
10
User Load (1.1ms)  SELECT `users`.* FROM `users` WHERE 'id' = 1 LIMIT 1
 Post Load (0.7ms)  SELECT `posts`.* FROM `blogs` WHERE `blogs`.`user_id` = 2
 Comment Load (0.7ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 43
 Comment Load (1.7ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 55
 Comment Load (2.2ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 32
 Comment Load (0.9ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 66
 Comment Load (2.2ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 56
 Comment Load (4.8ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 65
 Comment Load (1.8ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 68
 Comment Load (0.8ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 71

The user has commented in 9 different posts, which results in 9 separate queries to the DB. This is a small scale example, but you can see how this can compound into something nasty.

he solution is to user includes method:

1
@user = User.find(id).includes(:posts => :comments)

When using includes method, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries. The actual query executed is converted into a more optimal singular query and it is that result set that is iterated over instead of the replicated find(id) style queries.

Our query stack list now looks like:

1
2
3
User Load (1.0ms)  SELECT `users`.* FROM `users` WHERE 'id' = 1 LIMIT 1
Post Load (0.4ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1)
Comment Load (0.5ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` IN (43, 55, 32, 66, 56, 65, 68, 71)

So far so good, That’s it!!! See ya!!! :)