Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/9fb0e9a54adb718e626e8e3dcd33e439 to your computer and use it in GitHub Desktop.
Save JoshCheek/9fb0e9a54adb718e626e8e3dcd33e439 to your computer and use it in GitHub Desktop.
ActiveRecord: Comparing different ways to load associated data (`includes` vs `preload` vs `eager_load`)
# Config
require 'active_record'
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
# Migrations
ActiveRecord::Schema.define do
self.verbose = false
create_table(:users) { |t| t.string :name }
create_table :posts do |t|
t.string :name
t.integer :user_id
end
end
# Models
class User < ActiveRecord::Base
has_many :posts
def self.to_text
all.map { |u| "#{u.name}: #{u.posts.pluck(:name).join(', ')}" }.join(', ')
end
end
class Post < ActiveRecord::Base
belongs_to :user
end
# Helper method so we can see what queries it ran
def self.capture_queries(&block)
queries = []
capturer = -> *, data { queries << data.values_at(:name, :sql).select(&:present?).join(": ") }
ActiveSupport::Notifications.subscribed capturer, "sql.active_record", &block
queries
end
# Seed data
User.create! name: 'Josh', posts: [Post.new(name: "josh's post")]
User.create! name: 'Becky', posts: [Post.new(name: "becky post")]
# ===== Comparing Different Ways To Access Associated Data =====
# Doing nothing leads to N+1
capture_queries do
User.all.to_text # => "Josh: josh's post, Becky: becky post"
end
# => ["User Load: SELECT \"users\".* FROM \"users\"",
# "Post Pluck: SELECT \"posts\".\"name\" FROM \"posts\" WHERE \"posts\".\"user_id\" = ?",
# "Post Pluck: SELECT \"posts\".\"name\" FROM \"posts\" WHERE \"posts\".\"user_id\" = ?"]
# Use `eager_load` to do a left outer join.
# This may be what you were looking for, it's similar to your example (note that
# this is not necessarily better, it saves a query, but could potentially lead to
# many rows of almost entirely duplicated data being returned for each record)
capture_queries do
User.eager_load(:posts).to_text # => "Josh: josh's post, Becky: becky post"
end
# => ["SQL: SELECT \"users\".\"id\" AS t0_r0, \"users\".\"name\" AS t0_r1, \"posts\".\"id\" AS t1_r0, \"posts\".\"name\" AS t1_r1, \"posts\".\"user_id\" AS t1_r2 FROM \"users\" LEFT OUTER JOIN \"posts\" ON \"posts\".\"user_id\" = \"users\".\"id\""]
# I'll reformat it so it's easier to see how it works:
.first[5..].gsub(/(?=FROM|LEFT)/,"\n").gsub(/(?<=,)/, "\n ")
# => "SELECT \"users\".\"id\" AS t0_r0,\n" +
# " \"users\".\"name\" AS t0_r1,\n" +
# " \"posts\".\"id\" AS t1_r0,\n" +
# " \"posts\".\"name\" AS t1_r1,\n" +
# " \"posts\".\"user_id\" AS t1_r2 \n" +
# "FROM \"users\" \n" +
# "LEFT OUTER JOIN \"posts\" ON \"posts\".\"user_id\" = \"users\".\"id\""
# Use `preload` to batch the association queries into a single additional query
capture_queries do
User.preload(:posts).to_text # => "Josh: josh's post, Becky: becky post"
end
# => ["User Load: SELECT \"users\".* FROM \"users\"",
# "Post Load: SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" IN (?, ?)"]
# Use `includes` most of the time, it will choose between `eager_load` and `preload`, based on simple heuristics.
# It chooses `preload` by default
capture_queries do
User.includes(:posts).to_text # => "Josh: josh's post, Becky: becky post"
end
# => ["User Load: SELECT \"users\".* FROM \"users\"",
# "Post Load: SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" IN (?, ?)"]
# Here, we've implied we need the association in the query (via `references`), so it chooses `eager_load`
capture_queries do
User.includes(:posts).references(:posts).to_text # => "Josh: josh's post, Becky: becky post"
end
# => ["SQL: SELECT \"users\".\"id\" AS t0_r0, \"users\".\"name\" AS t0_r1, \"posts\".\"id\" AS t1_r0, \"posts\".\"name\" AS t1_r1, \"posts\".\"user_id\" AS t1_r2 FROM \"users\" LEFT OUTER JOIN \"posts\" ON \"posts\".\"user_id\" = \"users\".\"id\""]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment