前提知識
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
参考