前回 Arel が SQL を構築するところを読んだので、次は ActiveRecord がデータベースを操作するときに、どのタイミングで SQL を構築しているのかを見たいと思います。
このサンプルで、User.where(...)の時点でSQLが発行されてしまうと、その後に続く.limit(10)が反映されないはずじゃないか、というのか今回の疑問です。
まず、#whereの場所を探します。
ActiveRecordはざっくりこんなかんじになっています↓
Module#delegate
ref. http://api.rubyonrails.org/classes/Module.html#method-i-delegate
delegate は、active_supportの中でModule#delegateとして定義されています。
上の例では、Hoge#methodの呼び出すと、Hoge#targetが返すオブジェクトのmethodメソッドが呼ばれます。
Concern#included
includedメソッドも同じくactive_supportで定義されており、
そのモジュールがincludeされた時に、ブロックの中身が実行されます。
というわけで、Relation#whereが呼び出されることが分かります。
Relation#where
build_whereの中身を追っていくと、
PredicateBuilder#build_from_hash内の以下のコードが実行されることが分かります。
上のコード内のdefault_tableには
Arel::Tableのインスタンスが入っています。
とういわけで、User.where(:name => "John")を実行したとき、
Relation#build_whereの結果は[table[:name].eq("John")]となります。
これがRelation#where_valuesに格納されます。
以上で、ActiveRecord::Base#whereの呼び出しは終わりです。
まとめると、ActiveRecord::Base#whereを呼び出すと、Relationクラスのインスタンスが返されます。
その中にはtable[column].eq(value)たちが入った配列が格納されています。
Relation#limit
Relation#limitも同じかんじです。
で、いつSQL?
さて、User.where(..).limit(10)の結果、Relationクラスのインスタンスが返ってくることは分かりましたが、
これがいつSQLに変換され、データベースに向けて投げられるんでしょうか?
whereの検索結果が必要になるのはどんな時でしょうか?
大抵の場合、検索結果から個々のUserクラスのインスタンスを取り出すときかと思います。
このとき、検索結果に対しては Array としてアクセスすると思います。
というわけで、Relation#to_aについて見てみました。
scopeとかeager_loadingとかよく分からないことは置いておいて、デバッガで追いかけると、
@klass.find_by_sql(arel, @bind_values)が実行されることが分かります。
ということは、arelの値が問題になりそうです。
こんなかんじになってました。
このbuild_arelで、whereとかlimitに渡された引数たちを、
まとめてArelのオブジェクトに変換していってます。
arel.xxxが連なってる感じ、壮観です。
find_by_sql
この時点ではarelはSQLになっていません。
なので、find_by_sqlの中を追っていくことにします。
追っていくと、
ActiveRecord::ConnectionAdapters::DatabaseStatements#select_all
に行き着きます。
DatabaseManager#to_sqlの中の
visitor.accept(arel.ast) do ... というのが、
Arel::TreeManger#to_sqlと同じことをやっているのが分かります。
というわけで、ここに来て、Arelのオブジェクトが無事SQLに変換されて、 DBMに向けて投げられたことが分かりました。
めでたし。
to_aは誰が呼ぶ?
to_aを呼び出せば、SQLが発行されることは分かりましたが、
誰がどのタイミングでto_aを呼ぶのでしょうか?
そういえば、whereの検索結果に対しては、eachメソッドを呼べます。
eachがどこで定義されているのか調べてみました。
なるほど。
eachとかmapなどが呼ばれたタイミングで、to_aにデリゲートされると。
Relation#inspect
では、たとえば、irbの中で、> p User.where(...)などとしたときはどうなんでしょうか?
このときもto_aが呼ばれるのでしょうか?
http://doc.ruby-lang.org/ja/1.9.2/class/Object.htmlによると↓とのこ とです。
inspect -> String
オブジェクトを人間が読める形式に変換した文字列を返します。 組み込み関数 Kernel.#p は、このメソッドの結果を使用して オブジェクトを表示します。
pはinspectの結果を表示するようです。
Relation#insepctは
となってます。
やっぱり、to_aが呼ばれるようです。
まとめ
Arelのときもそうでしたが、
whereなどのメソッドが呼ばれた時点では単に引数だけを保存しておいて、
必要になった時点でSQLを発行するということでした。
「必要になった時点」の判断の仕方が面白いと思いました。
必要になった時点でto_aを呼ぶわけですが、eachからto_aにdelegateされるとか、
inspectの中でto_aを呼ぶとか。
しかし、whereとかをわざわざActiveRecordでラップする必要があるんですかね…
RelationのなかでArelのオブジェクトを保持しておいて、whereの処理はArelにまかせて、
Relationはto_aとかのまわりだけ面倒みたらだめなのかな…?