Rails 3.0 の ActiveRecord::Base.find_all の挙動について

月別アーカイブを作成したい時などに DBMS 独自の時刻関数を使う事は多いと思いますが、今回そのあたりに関連して ActiveRecord の挙動でハマったのでメモ。

例えば次のような SQLPostgreSQLpsql コマンドから実行したとします(データベースに costs テーブルが存在し、日付型の occured_on という値を持っているものとします)。

SELECT DISTINCT to_char(occured_on, 'YYYYMM') FROM costs;

これはきちんと動作し、月別の値が YYYYMM の形で出力されます。

次に、上の SQLrails console で ActiveRecord を通して実行してみます。

Cost.select("DISTINCT to_char(occured_on, 'YYYYMM')")

これも動作はするのですが、返される Cost のオブジェクトに値がセットされていません。仕方がないのでソースを追いかけると、どうやらデータベースのテーブルに定義された値以外は ActiveRecord ではロードされない様子。それならば、と思って実行したのが次のコード。

Cost.select("DISTINCT to_char(occured_on, 'YYYYMM') AS occured_on")

これで値が返されます…が、返される値は Ruby の Date 型で、中身は西暦20年という有様。さらにソースを読むと、返される値の型もデータベースに対応して自動設定される事が判明しました。最終的に描いたコードがこちら。

Cost.select("DISTINCT to_char(occured_on, 'YYYY-MM-01') AS occured_on")

日を強制的に1日にしてしまえば、月毎に分ける事ができます。まとめると、『ActiveRecord 3.0 系ではデータベースに定義されたカラムの値が、定義された型(に、対応した Ruby の型)で取得され、他の値は無視される』と言ったところでしょうか。

追記

試しにデータの件数を10万件ほどに増やして上の SQL を実行すると、結構な時間がかかってしまいます。インデックスを張ったりもしてみたのですが、日付に対してはいまいち有効ではないようでした。上のコードをそのまま使うのであれば、データ件数の増加と実行時間には常に気をつけた方がいいでしょう。