この記事について
ActiveRecord::Relationの理解をちゃんとできておらず、debugにハマったことがあったので備忘として残すもの
※網羅的に解説しているというよりは、見落としていた所について調べたことをまとめるものです。
参考書籍
ActiveRecord::Relationとは
RailsのO/RマッパーであるActiveRecordにおいて定義されているクラスの一つです。
表題で「whereが〜」と書いていますが、そもそもwhereはActiveRecord::Relationで定義されているメソッドであり、実行結果としてAcitveRecord::Relationのインスタンスオブジェクトが返ってきます。
今回私がハマった理由として、以下の特性を理解していなかったことが挙げられます(パーフェクトRuby on Railsより引用しています)
ActiveRecord::Relationは内部でどの様なSQLを発行するか、という情報だけを保持します。そのため、実際にそのSQLの実行結果が必要になるまでは、データベースに対するアクセスは発生しません。
「SQLが即時実行されない」というこの特徴を忘れていました。
実際にハマった事象
副業でメンター業をやっており、受講生さんからの質問に対して同期で対応しています。高難易度の質問は滅多にこないものの、瞬発力を求められるのでたまに沼る時があり、今回もそのケースでした。
あいまい検索の実装対応を行なっており、以下の様なコードを書いた所、view側でエラーが発生しました。
# ユーザー名の部分検索を実施
search_results = User.where("name LIKE ?" "%#{search_word}%")
結論から言うとwhere内記述に「,」が漏れているためsyntax errorが発生していた、という簡単な話なのですが、焦っていて沼りました。AcitveRecord::Relation以前に「焦る前にエラー文読みなさいね」というオーソドックスな反省はあります。
が、この時冷静でない私は以下の様に間違った道を進んで行きました。
間違えた道を選ぶ過程
まず根本的な理解不足により、「viewでエラーが起きてるので検索ロジックに問題はないのでは?」という思い込みがありました(ここが今回の核ですね)
その上で上記のエラーに対し、pryで止めてdebugを行なっていました。
pry(#<SearchesController>)> User.where("name LIKE ?" "%sample%")
User Load (1.3ms) SELECT "users".* FROM "users" WHERE (name LIKE ?%sample%)
↳ app/controllers/searches_controller.rb:16:in `search'
User Load (0.7ms) SELECT "users".* FROM "users" WHERE (name LIKE ?%sample%) /* loading for inspect */ LIMIT ? [["LIMIT", 11]]
↳ app/controllers/searches_controller.rb:16:in `search'
=> #<User::ActiveRecord_Relation:0x8d7c>
この結果を見て、「エラーを吐かずに何か結果が返ってきてるけど、配列の形になっていないからview側でエラーになっているのでは」という誤解を持った結果、中々正しい答えに辿り着かなかった、というのが今回起きたことです。
また、pryで正しく実行した結果、返り値として取得されたデータが表示されることも自分が誤解を進めた一因だったかなと思います
User.where("name LIKE ?", "%sample%")
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE (name LIKE '%sample%')
↳ app/controllers/searches_controller.rb:16:in `search'
=> [# 取得されたデータが表示される]
もちろん便利なのでこうあってほしいのですが、ちゃんと理解していない状態で日々この表示を見ていたため「コードの読み込み時に常にSQLが実行されている」と誤解していたかなあと思います。
解決後に調べたこと
先に述べた通り、実際はsyntaxエラーなのでそこを修正すれば正しく動作したのですが、誤解ゆえに「なんでviewでエラーが出たのか?」と思い調べ直した所、AcitveRecord::Relationの存在を思い出し、「呼び出されるまでSQLは実行されない」がため、というのを理解しなおした形になります。
ちなみにActiveRecord::Relationにはloaded?
が定義されており、「実際にSQL呼び出しが行われたか」がここでわかる形になっているのですが、それぞれ以下の結果になりました
# pry上で間違ったコードを実行
wrong = User.where("name LIKE ?" "%sample%")
wrong.loaded? #=> nil つまりまだ実行されていない
# pry上で正しいコードを実行
right = User.where("name LIKE ?" "%sample%")
right.loaded? #=> true 実行されている
# 正しいコードを書いた上で、結果のインスタンス変数に対しloaded?を実施
# 以下の様なコードを書いてターミナルを確認
@search_results = User.where("name LIKE ?" "%sample%")
@search_results.loaded? #=> nil まだ実行されていない
これは調べたわけではないので推測ですが、pry上で正しくコードを書いた場合はその場で実行し、実行結果を表示してくれてるのかな?という感じの挙動ですね。
ちなみにrecordsに結果の格納も定義されているので、wrongの時にpry上でrecordsを実行したら、当初viewに表示されていたものと同じエラーをpry上で引き起こすことができました。
まとめ
エラーメッセージはどんなに焦ってても丁寧に読みましょう。
パーフェクトRuby on Rails読み直します。
リアタイできなかったのですが、今年のKaigi on RailsでまさにAcitveRecord::Relationを詳細にまとめていただいている方がいたのでアーカイブを心待ちにしています。
理解不足によりハマりましたが、「エラーが解決したからいいや」ではなく「納得いかないことを納得いくまで調べ理解すること」は楽しいし一番身になるなと思いました。