前回 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
とかのまわりだけ面倒みたらだめなのかな…?