RailsではN + 1問題によるパフォーマンス悪化を避けるために、
ActiveRecordのeager loadingを利用して先に関連先のデータを取得することをよく行います。
一般的には、includesを指定することが多いですが、preload、eager_loadというメソッドも用意されているので調べてみました。
確認環境
Rails 4.2.1
preload
- preloadを使うとSQLが2本発行される
Blog.preload(:posts)
Blog Load (42.1ms) SELECT `blogs`.* FROM `blogs`
Posts Load (22.7ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`blog_id` IN (1, 2, 3, 4, 5)
- 関連先のカラムの条件を指定できない
Blog.preload(:posts).where('posts.name = "ABC"')
blog Load (1.3ms) SELECT `blogs`.* FROM `blogs` WHERE (posts.name = "ABC")
Mysql2::Error: Unknown column 'posts.name' in 'where clause': SELECT `blogs`.* FROM `blogs` WHERE (posts.name = "ABC")
includes
- preloadと同じ動作。SQLが2本発行される
Blog.includes(:posts)
Blog Load (42.1ms) SELECT `blogs`.* FROM `blogs`
Posts Load (22.7ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`blog_id` IN (1, 2, 3, 4, 5)
- 関連先のカラムの条件を指定する場合は、条件をhashで指定
条件によってはSQLをひとつにまとめて発行する
Blog.includes(:posts).where(posts: {name: 'ABC'})
SQL (0.5ms) SELECT `blogs`.`id` AS t0_r0, `blogs`.`created_at` AS t0_r1, `blogs`.`updated_at` AS t0_r2, `posts`.`id` AS t1_r0, `posts`.`blog_id` AS t1_r1, `posts`.`name` AS t1_r2 `posts`.`created_at` AS t1_r3, `posts`.`updated_at` AS t1_r4 FROM `blogs` LEFT OUTER JOIN `posts` ON `posts`.`blog_id` = `blogs`.`id` WHERE `posts`.`name` = 'ABC'
- 関連先のカラムの条件を指定する場合は、referencesを指定する
Blog.includes(:posts).where('posts.name = "ABC"').references(:posts)
- 関連先のカラムの条件指定にmergeを使う場合
Blog.includes(:posts).merge(Plan.where(name: 'ABC')).references(:posts)