SmartHRさんのTechBlogを読んで学びになったので、思考の整理のためにまとめました。
今回読んだTechBlogがこちらです。
クエリが発行されるタイミングについて
Active Recordは、そのデータが本当に必要になったときにはじめてクエリを発行するようになっている(遅延実行)。
以下のようなクエリは発行しない。
class UsersController < ApplicationController
def index
@users = User.where(status: 'active')
end
end
ビューでインスタンス変数を評価するタイミングでクエリが発行される。
<% @users.each do |user| %> # ここでクエリが発行される!
<%= user.name %>
<% end %>
クエリを発行するタイミング | メソッド | |
---|---|---|
即時実行 | find, find_by, take, first, last, exists? | すぐにクエリを発行し、データベースにアクセスし、レコード(Modelのインスタンス or インスタンスの配列)を返す。 |
遅延実行 | 上記以外のメソッド(where,order,selectなど) | ActiveRecord::Relationのオブジェクトを返し、実際にデータが必要になるタイミングまでデータベースにはアクセスしない。 |
console環境では、遅延実行するメソッドでもクエリを発行します。これは戻り値をレシーバとしてinspectメソッドを実行し、得られた文字列を表示する仕様があるため。(自分はconsole環境で本来遅延実行するはずのメソッドを実行しても、クエリが発行されて混乱していたので、腑に落ちました。)
ActiveRecord::Relationのオブジェクトとは?
クエリを生成するための情報を保持し、メソッドチェーンでつなげることができるため、再利用性が高く、便利なものになっている。
一方でActive Recordのオブジェクトはそれなりに大きいので、たくさん作るとメモリを大量に消費するので、クエリの内容や発行回数だけでなく、オブジェクトそのものの生成も抑える必要がある。
クエリ発行タイミングの具体例
データの取得
users = User.where(name: 'John') # ここではクエリは発行されない
users.first # ここでクエリが発行される
配列への置換
users = User.where(name: 'John') # ここではクエリは発行されない
users_array = users.to_a # ここでクエリが発行される
レコードのループ処理
users = User.where(name: 'John') # ここではクエリは発行されない
users.each { |user| puts user.name } # ここでクエリが発行される
レコードの存在チェック
User.exists?(name: 'John') # ここでクエリが発行される
関連データの取得
user = User.first # ここでクエリが発行される
posts = user.posts # もしキャッシュされていなければ、ここで再度クエリが発行される
集計メソッド
User.count # ここでクエリが発行される
User.average(:age) # ここでクエリが発行される
バッチ処理
User.find_each(batch_size: 1000) { |user| ... } # ここでクエリが発行される(バッチサイズごとに)