前提知識
ActiveRecordの検索メソッドは、DBアクセスの有無
という点で大きく分けると以下の2種類がある。
- DBアクセスを行うメソッド
-
ActiveRecord::FinderMethods
のメソッド-
find, find_by, take, first, last
など - すぐにクエリを発行し、データベースにアクセスし、レコード(Modelのインスタンス or インスタンスの配列)を返す。
-
-
- DBアクセスを行わず
ActiveRecord::Relation
のオブジェクトを返すメソッド-
all, where, limit, eager_load, preload, joins
など
-
問題
上記の通りall
やwhere
はそれ単体ではDBへのアクセスは発生しない。
そこで実際にrails console
でwhere
を実行してみる。
pry(main)> User.where(id: 1)
User Load (3.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1
=> #<User:0x000055e9e1dd7e00
id: 1,
last_name: "田中",
first_name: "太郎",
created_at: Wed, 09 Feb 2022 17:57:33 JST +09:00,
updated_at: Wed, 09 Feb 2022 17:57:33 JST +09:00 >
むむ、DBへのアクセスは発生しないはずだけど、明らかにDBからデータを取得してますね。(田中太郎さんのデータを取得している)
ログをみてもやはりSQLが発行してDBアクセスが発生していることがわかります。
User Load (3.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1
一見すると矛盾しているように見えますね。
なぜDBにアクセスが発生しないはずのwhere
でDBアクセスが発生しているのか?
ActiveRecord::Relation を返す他のメソッド( all, preload など)も上記と同じ挙動になります。
原因
上記の問題は、railsコンソールの仕様が原因となっています。
railsコンソールにおいて、User.where(id: 1)
は以下のような流れで処理されます。
- コンソールで
User.where(id: 1)
を実行 -
ActiveRecord::Relation
オブジェクトが帰ってくる - コンソールはオブジェクトを画面に表示するため
#inspect
を呼ぶ -
ActiveRecord::Relation
オブジェクトはDBにアクセスしにいく
つまりrails console
では、オブジェクトを画面に表示するために式にinspect
が実行されているんですね。
その時に、ActiveRecord::Relation
が評価されDBアクセスが発生してしまいます。
まとめ
-
rails console
では、画面表示のためにinspect
が実行されており、それが原因でwhere
やall
でもDBアクセスが発生している。 - それを知らないと、
all
やwhere
はDBアクセスしないという情報と、rails console
での実行結果が矛盾しているように見え混乱を招く。 - なので、内部的に
inspect
を実行するという作りになっていることを知っておこう!
追記
行末にセミコロンをつけると実際の挙動になるようです。
User.where(id: 1); # DBアクセスなし
User.find(1); # DBアクセスあり
User Load (1.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
参考