🔎

Ransack で unixtimestamp を使った検索を行う

2024/09/27に公開

こんにちは、okumudです。

Ransack は、Ruby on Rails アプリケーションでレコードの検索機能を直感的に実装できる強力な Gem です。ActiveRecord と深く統合されているため、複雑なSQLクエリを手動で書くことなく、ユーザーが柔軟な検索条件を設定できます。
今回の記事では、特に日時を扱う場合の検索条件を、 unixtimestamp 形式(1727404488.666990 のような値)で指定する方法を紹介します。

https://github.com/activerecord-hackery/ransack

Custom predicates を使う方法

Custom predicates (カスタム述語) は、Ransack 標準で用意されていない検索述語を定義することができます。
https://activerecord-hackery.github.io/ransack/going-further/custom-predicates/

add_predicate で カスタマイズした Search Matchers を追加できます。
複数のフィールドで同様の加工が必要になる場合は、こちらがおすすめです。

# config/initializers/ransack.rb
Ransack.configure do |config|
  # ... 他の設定は省略

  # *_unixtime_gteq で指定した値(unixtimestamp 形式)以上の値の検索条件
  config.add_predicate 'unixtime_gteq', # 検索条件の名前(必須)
                       # Arel述語(必須)
                       arel_predicate: 'gteq',
                       # 入力値のフォーマット(default: なし)
                       # NOTE: このブロックからモデルは参照できません
                       formatter: ->(v) { Time.zone.at(v) },
                       # 入力値の型(default: DBの列定義を使用)
                       type: :float

  # *_unixtime_lteq で指定した値(unixtimestamp 形式)以下の値の検索条件
  config.add_predicate 'unixtime_lteq',
                       arel_predicate: 'lteq',
                       formatter: ->(v) { Time.zone.at(v) },
                       type: :float
end

arel_predicate (Arel述語)はあらかじめ定義されている値を使用してください。

次のように unixtimestamp が datetime 形式に変換されて検索条件として使用されます。

Task.ransack(updated_at_unixtime_gteq: 1727404488.666990).result
# SELECT `tasks`.* FROM `tasks`
#   WHERE `tasks`.`updated_at` >= '2024-09-26 10:26:30.666990'

Ransackers を使う方法

Ransackers は、モデル上に定義して 仮想の属性(カラム)を検索できるようになります。
https://activerecord-hackery.github.io/ransack/going-further/ransackers/#search-on-field

特定のフィールドに対しての加工や、複数のカラムを組み合わせてカスタム検索を行いたい場合に使えます。

# app/models/task.rb
class Task < ApplicationRecord
  # updated_at フィールドに対する属性を定義する
  ransacker :updated_at,
            type: :float,
            # formatter で検索値を datetime に変換する
            formatter: ->(v) { Time.zone.at(v) } do |_parent|
    # 検索対象列を Arel で返す
    Arel.sql('updated_at')
  end
end

次のように unixtimestamp が datetime 形式に変換されて検索条件として使用されます。

Task.ransack(updated_at_gteq: 1727404488.666990).result
# SELECT `tasks`.* FROM `tasks`
#   WHERE `tasks`.`updated_at` >= '2024-09-26 10:26:30.666990'

Scope を使う方法

モデルの Scope や クラスメソッド を Ransack で使うことができます。
https://activerecord-hackery.github.io/ransack/going-further/other-notes/#using-scopesclass-methods
条件の加工や複雑なことを行う場合に使用することができます。

class Task < ApplicationRecord
  scope :updated_at_unixtime_gteq,
        ->(v) { where('updated_at >= ?', Time.zone.at(v)) }

  # ... 他の設定は省略

  # Ransack を使用した検索時に指定可能な scope を設定
  def self.ransackable_scopes(_auth_object = nil)
    %w[updated_at_unixtime_gteq]
  end
end

次のように unixtimestamp が datetime 形式に変換されて検索条件として使用されます。

Task.ransack(updated_at_gteq: 1727404488.666990).result
# SELECT `tasks`.* FROM `tasks`
#   WHERE (updated_at >= '2024-09-26 10:26:30.666990')

ただし、OR 条件や、 AND 条件を制御できる groupings もありますが、 scope を使った場合は、無視されるようです😢

Task.ransack(
  combinator: 'and',
  groupings: [ {updated_at_unixtime_gteq: 1727404488.666990} ]
).result
# SELECT `tasks`.* FROM `tasks`

まとめ

Ransack の検索条件を指定する方法を紹介しました。

  • Custom predicates を使う方法
    • 複数フィールドに対して共通の検索条件を適用したい場合に最適です
  • Ransackers を使う方法
    • 特定のフィールドや、計算結果に基づいて検索条件を設定したい場合に適しています
  • Scope を使う方法
    • より複雑な検索ロジックに適していますが、groupings の検索方法に対応していない点には注意が必要です

他に良い方法があれば教えていただけると幸いです。

SocialPLUS Tech Blog

Discussion