📝

ransackで日付検索する時、タイムゾーンを意識して検索する

2024/02/07に公開

概要

問題

Ransack を使用して検索すると、タイムゾーンで日付のズレが発生していた。

前提条件

技術

  • Ruby on Rails
  • Ransack

タイムゾーン設定

タイムゾーン
システム ( Ruby on Rails ) UTC
Ruby on Rails Tokyo
ActiveRecord UTC
システム ( DB ) UTC
DB ( MySQL ) UTC

解決策

目的

のみで検索したい。
例えば 2023 を指定すると、2023年に登録したユーザを検索する。

のみで検索したい。
例えば 12 を指定すると、登録年に関わらず、12月に登録したユーザを検索する。

実装例

Controller のイメージは以下の通り。

class UsersController < ApplicationController
  def index
     # クエリパラメータ例
     # @q = { created_at_year_eq: "2023",  created_atmonth_eq: "12" }
    @user = User.ransack(@q)
  end
end

Model のイメージは以下の通り。

class User < ActiveRecord::Base
  ransacker :created_at_year do
    Arel.sql("YEAR(CONVERT_TZ(created_at, 'UTC', 'Asia/Tokyo'))")
  end

  ransacker :created_at_year, formatter: proc { |v| format('%02d', v) } do
    Arel.sql("MONTH(CONVERT_TZ(created_at, 'UTC', 'Asia/Tokyo'))")
  end
end

発行されるクエリは以下の通り。
MySQL は CONVERT_TZ により、タイムゾーンを変換可能であることから、検索対象のカラムを指定のタイムゾーンに変換し、 YEARMONTH で検索対象の数値を抽出し、検索している。

WHERE
    YEAR(CONVERT_TZ(created_at, 'UTC', 'Asia/Tokyo')) = '2023'
    AND
    MONTH(CONVERT_TZ(created_at, 'UTC', 'Asia/Tokyo')) = '12'

終わりに

今回は、 DB が MySQL の場合の対応となる。

PostgreSQL の場合、タイムゾーン変換の構文が CONVERT_TZ ではないことから、別途調べる必要あり。 Ransack の公式ドキュメントにも実装例はあるため、参照すると良い。

参考情報

Discussion