課題
Railsでページネーション機能を作るときにはkaminari を使うのが定番ですね。Active Recordのクエリメソッドに対してメソッドチェーン形式でpageやperを追加するだけで手軽にページネーションができます。
ただ、find_by_sql
を利用してActive Recordのオブジェクトを作成したときには、戻り値が配列になってしまうのでpageやperなどを後に追加することができません*1。どうしたらよいのでしょうか。
問題のあるやり方
ググるとKaminari.paginate_array
を使った次のようなやり方がいくつか引っかかります。
Kaminari.paginate_array(array_from_find_by_sql).page(params[:page]).per(10)
Kaminari.paginate_array
は読んで字のごとく、配列をkaminariで扱えるようにするためのメソッドです。これでとりあえず要件としては満たせますが、レコード件数が多くなってくるとパフォーマンスに問題が出てきます。このやり方だと、find_by_sqlが検索対象のActive Recordオブジェクトを毎回すべて生成する必要があるからです。例えば10000件のレコードが対象となるクエリだとしたら、毎回10000個のActive Recordオブジェクトが生成されるわけです、これはだいぶつらいですね…><。
とりあえずの解決方法
kaminariで生成したモデルオブジェクトは、ビューでpaginate
メソッドへの引数になり、ページネーション用のhtmlになります。引数は必ずしもkaminariから作成されたクラスである必要はなく、必要なメソッドが定義されていれば問題ありません。なので必要なメソッドを返すラッパーを用意すればよい、と考えてしばらく↓のようなクラスを作り運用していました(もちろんfind_by_sqlの引数としてlimitやoffsetを使ったクエリを組み立てる必要がありますし、total_countは別のクエリで取得しておく必要があります)。
真の解決方法
しかし、そもそもKaminari.paginate_array
に次のような形で引数を渡すと、無駄なActive Recordのオブジェクトを生成する必要なく、上記のWrapperForKaminariと同等のメソッドが提供されたオブジェクトが返る、ということに気づきました。
Kaminari.paginate_array(array_from_find_by_sql, limit: 10, offset: 0, total_count: 100)
WrapperForKaminariをgemにしようかな。と思っていたのですが不要でしたね。kaminariべんり。
*1:仮に追加できても、find_by_sqlで発行されるクエリをいじれないので無意味ですね…