ActiveRecordのfind_by_sqlで関連を事前にロードする方法

開発 hironemuhironemu

ActiveRecordで複雑なSQLを使いたいときはfind_by_sqlを使って、ゴリゴリSQLを書いていくんですが、こうして検索されたあるモデルの関連は(とくに何もしなければ)その時点では解決されてなかったりします。(ちなみに、弊社のサービス「Hachikin」でもActiveRecordを使っており、複雑なSQLがあるところではこのメソッドを使ってクエリを投げています。)

例えば、ProjectモデルがUserモデルやCompanyモデルを持っている場合、以下のようにprojects[0].userとした時に、再度SQLが発行されてしまいます。

projects = find_by_sql("SELECT * FROM projects")
puts projects[0].user    # この時、Userを取得するためのクエリが発行される
puts projects[0].company # この時も、Companyを取得すためのクエリが発行される

まぁ、1件くらいだったらいいんですが、検索結果一覧に大量のデータを表示したりすると何度もクエリを発行されるのはなかなかちょっとアレです。

find_by_sqlではなく、普通にArelとかで検索する場合、以下のようにするのですが、find_by_sqlでは以下のようには出来ないっぽい。

projects = Project.where(...).includes(:user, :company).all
# この時、以下の3つのクエリが発行される
# SELECT * FROM projects;
# SELECT `users`.* FROM users WHERE `users`.id IN (.....)
# SELECT `companies`.* FROM companies WHERE `companies`.id IN (.....)
puts projects[0].user    # ここではもうクエリは発行されない
puts projects[0].company # ここでもクエリは発行されない

そこで、色々調べてみたところfind_by_sqlの場合以下のようにすることで、includesと同じようなことが出来ることが分かりました。(因みに、ActiveRecord 3.1以上の場合)

projects = find_by_sql("SELECT * FROM projects")
ActiveRecord::Associations::Preloader.new(projects, [:user, :company]).run
projects

これで、includesを使った時と同じように3回だけSQLが実行され、userやcompany等の関連もしっかり保たれた検索結果を取得することができました。

答えはStackOverflowにありましたが、あんまり情報がなかったような。意外とfind_by_sqlを使った時に一緒に関連とか使わないんですかね?

Tags: