Skip to content

Instantly share code, notes, and snippets.

@boykoc
Last active July 21, 2017 13:35
Show Gist options
  • Save boykoc/6514cd4773ebdd9ad49195bc34768453 to your computer and use it in GitHub Desktop.
Save boykoc/6514cd4773ebdd9ad49195bc34768453 to your computer and use it in GitHub Desktop.
Eager loading join causing n+1-1
# Create rails app to see issue.
rails new sample
rails g scaffold Author name:string
rails g scaffold Post title:string body:text author:references
rails g scaffold Comment body:text post:references
# Update post model to add association.
has_many :comments
# Update seed.rb
authors = Author.create([{name: 'User 1'}, {name: 'User 2'}])
posts = Post.create([{title: 'post 1', body: 'body of post 1', author: authors.first}, {title: 'post 2', body: 'body of post 2', author: authors.first}, {title: 'post 3', body: 'body of post 3', author: authors.second}])
comments = Comment.create([{body: 'comment 1', post: posts.first}, {body: 'comment 2', post: posts.first}, {body: 'comment 3', post: posts.first}, {body: 'comment 4', post: posts.second}, {body: 'comment 5', post: posts.third}, {body: 'comment 6', post: posts.third}, {body: 'comment 7', post: posts.third}, {body: 'comment 8', post: posts.third}])
# Run migrations and seed database.
rake db:migrate
rake db:seed
# Go to console to test output.
rails c
posts = Post.joins(:comments).select("posts.*, comments.body").includes(:author)
posts.each do |x|
puts "#{x.title} #{x.author.name} #{x.body}"
end
# Console output.
# post 1 User 1 comment 1
# Author Load (0.3ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1 [["id", 1]]
# post 1 User 1 comment 2
# Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1 [["id", 1]]
# post 1 User 1 comment 3
# post 2 User 1 comment 4
# post 3 User 2 comment 5
# Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1 [["id", 2]]
# post 3 User 2 comment 6
# Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1 [["id", 2]]
# post 3 User 2 comment 7
# Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1 [["id", 2]]
# post 3 User 2 comment 8
posts = Post.joins(:comments).select("posts.*, comments.body").includes(:author)
posts.each do |x|
puts x.association(:author).loaded?
end
# Output.
# true
# false
# false
# true
# true
# false
# false
# false
# Interestingly the following test does not fail.
class PostTest < ActiveSupport::TestCase
test "should eager load author for each post" do
posts = Post.joins(:comments).select("posts.*, comments.body").includes(:author)
posts.each do |x|
assert x.association(:author).loaded?
# Also assert x.author.loaded? passes but errors in the console.
end
end
end
# Test output.
# Running via Spring preloader in process 2614
# Run options: --seed 29018
# # Running:
# ......................
# Finished in 1.561278s, 14.0910 runs/s, 24.9795 assertions/s.
# 22 runs, 39 assertions, 0 failures, 0 errors, 0 skips
##############################################################
# This only eager loads the Author for the first instance of each post.
# Each consecutive instance of a post triggers an additional query to find their authors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment