秒単位の時間範囲検索をしたかった。
日にち単位、時間単位、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 を組み合わせた検索を使いこなすと複雑な検索も可能になる。