ActiveAdminで秒単位の時間(期間)検索

秒単位の時間範囲検索をしたかった。

日にち単位、時間単位、10分単位 とかなら「ActiveAdmin datepicker」とかで検索すると
それ用の gem が出てくるが、
秒単位がなかった。

イメージとしてはテキストボックスで

start = 2016-03-22 09:00:00
end = 2016-03-22 09:00:00

的な感じで指定したい。
もはや秒単位検索というか、単なるカスタム検索に近い。

これを実現するのが ransack という gem 。
ransack 自体は検索フォームを簡単に作れるgemっぽくて、
複雑な検索条件を扱えるのが特徴っぽい。

今回の実装方針としては、
ransack の持っている「複雑な検索条件を扱える機能」を
ActiveAdmin から利用するようにする方法。
結構ググったけど、これしか方法がなかった。

まずは ransack を bundle install する。

gem 'ransack'

次に ActiveAdmin に検索フォームを準備する。

filter :created_date_gteq, label: "start", as: :string
filter :created_date_lteq, label: "end", as: :string  

created_date_gteq の _gteq と
created_date_lteq の _lteq がポイントになる。

この _gteq, _lteq は Arel という複雑なSQLを生成するライブラリのメソッドになっている。
https://github.com/rails/arel

Arel は以下のように eq というメソッドを利用することで SQL の users.name = 'amy' を生成する。

users.where(users[:name].eq('amy'))
# => SELECT * FROM users WHERE users.name = 'amy'

created_date_gteq の _gteq と
created_date_lteq の _lteq も eq と同じように
Arel でSQLを生成するときに利用するメソッドの記法になる。
SQL でいうと「 created_at <= _gteq AND _lteq <= created_at」のような範囲検索を表現している。

なので、
「filter :created_date_gteq, label: "start", as: :string」で入力された時刻は開始時刻となり、
「filter :created_date_lteq, label: "end", as: :string」で入力された時刻は終了時刻となる。

では、その入力された時刻はどこで処理されるのか?

入力値はモデルで処理される。


モデルに ransacker というメソッドを用意する。

private
 ransacker :created_date do |parent|
   Arel.sql("created_at")
 end

モデルの「ransacker :created_date」の created_date は
filter の 「:created_date_gteq」 の created_date と合わせる必要がある。

Arel.sql("created_at") で created_at を検索対象と認識する。
created_at はDBのカラム名を指定する。

これで検索が可能なはず。

ちなみに、以下のように入力値を変換することも可能。
formatter で入力値(変数のvが入力値になる)をフックして、変換することが可能になる。

private
 ransacker :created_date, formatter: -> v { Time.parse(v).to_i } do |parent|
   # Time.parse() で yyyy-mm-dd hh:ii:ss を unixtime に変換する
   Arel.sql("created_at")
 end

この ransack と Arel を組み合わせた検索を使いこなすと複雑な検索も可能になる。