はじめに
自分がRailsエンジニアとしてインターンをしており、その中で直近で一番意味わからんな。と思ったことがこれです。自分の学習のためにもしっかりとまとめました。他の記事よりなるべく初心者にもわかりやすくを意識して書きました。
色々な人がこれについて記事を書いているので下の参考文献も合わせて確認ください。
そもそもこれどういう時使うの?
親子関係のtableにおいて親のものを呼び出す時に子のデータも一緒に呼び起こしてcontrollerの記述をスッキリできるものや、主にeach do文を使った時に発生するN+1問題を解決するためのものである。
ここではN+1問題の詳細については触れないが、ActiveRecordによるSQLの発行回数がめちゃくちゃ多くなってしまってえげつないことになるからとりあえず対処しなければならないものという認識で良い(?)。
でもこんなに種類があって、どれがどう優れていて、どう劣っているか。よくわからない時にこれを見て欲しい。
includes
とりあえずN+1問題の時はこれで解決するイメージだが、実際にどのようなSQLが働いているかというと
User.includes(:posts)
# =>
SELECT `users`.* FROM `users`
SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1, 2, 3, ...)
User.includes(:posts).where('posts.desc = "ruby is awesome"').to_a
# =>
SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "posts"."id" AS t1_r0,
"posts"."title" AS t1_r1,
"posts"."user_id" AS t1_r2, "posts"."desc" AS t1_r3
FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
WHERE (posts.desc = "ruby is awesome")
ざっくりいうとpreloadの上位互換かつeager_loadとしても使える。
preloadはどこが劣っているか
User.preload(:posts).where("posts.desc='ruby is awesome'")
を使うことができない。がincludesではそれを使うことができる。
つまりwhereで絞り込みができるpreload。
どういった場合eager_loadとして使えるのか
先ほどのwhereを使った場合はeager_loadとして挙動する。(referencesする)
LEFT OUTER JOINの動きをする。
User.includes(:posts).where(name: 'User 1').where(posts: {title: 'Post 1-1'})
SQL (0.2ms) SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."author" AS t0_r2, "users"."created_at" AS t0_r3, "users"."updated_at" AS t0_r4, "posts"."id" AS t1_r0, "posts"."title" AS t1_r1, "posts"."created_at" AS t1_r2, "posts"."updated_at" AS t1_r3, "posts"."user_id" AS t1_r4 FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."name" = ? AND "posts"."title" = ? [["name", "User 1"], ["title", "Post 1-1"]]
eager_load
正直、includesの説明と被ってしまうと思うが、includes+referenceの動きをする。
eager_loadは指定したアソシエーションをLEFT OUTER JOINで取得し、キャシュする。
また、preloadとは違いは、指定したテーブルに対しての絞り込み(where)が行える。
User.eager_load(:posts).to_a
# =>
SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "posts"."id" AS t1_r0,
"posts"."title" AS t1_r1, "posts"."user_id" AS t1_r2, "posts"."desc" AS t1_r3
FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
preload
上のincludesの説明を読んで欲しい。whereを使うことのできないincludes。
eager_loadと違って、JOINしたくない大きいテーブルを扱うときはpreloadを使うのがいい。
User.preload(:posts).to_a
# =>
SELECT "users".* FROM "users"
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1)
joins
eager_load, preload との違いはキャッシュしない。(1度しかデータを使用しない)
joinsはinner joinを行うので、データはinner join的に捨てられる。
つまりデータのしぼりこみに使える。
User.joins(:posts).where(posts: { id: 1 })
# SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `posts`.`id` = 1
まとめ
これはActiveRecordのjoinsとpreloadとincludesとeager_loadの違いに書かれていた文ですが、**そのテーブルとのJOINを禁止したいケースではpreloadを指定し、JOINしても問題なくてとりあえずeager loadingしたい場合はincludesを使い、必ずJOINしたい場合はeager_loadを使いましょう。**という文があります。
この通りだなと思ったのでここに貼らせていただきました。
参考にした文献
ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い
[Preload, Eagerload, Includes and Joins]
(http://blog.bigbinary.com/2013/07/01/preload-vs-eager-load-vs-joins-vs-includes.html)
[Making sense of ActiveRecord joins, includes, preload, and eager_load]
(http://blog.scoutapp.com/articles/2017/01/24/activerecord-includes-vs-joins-vs-preload-vs-eager_load-when-and-where)
preload, eager_load, includes, references, and joins in Rails
参考にした記事を書いた方々に感謝です。状態としては色々な記事の情報をまとめて理解しやすい言葉で説明した形になっていますが、今後、もっと理解できるようになったら修正したいと思います。