LoginSignup
22
10

More than 5 years have passed since last update.

Rails における日付範囲で無限大 Float::INFINITY の扱う方法について調べたこと

Last updated at Posted at 2019-06-01

はじめに

Rails で日付の範囲指定をしてレコードを絞り込むことがあると思います。

範囲の両端が決まっているときは簡単なのですが、片方の端が決まっていない場合にどうするのか、無限大を使う方法があるとのことだったのですが、ちょっと調べないとわからなかったので、備忘としてまとめます。

環境情報

  • PostgreSQL 10.5
  • Rails 5.2.2
  • ruby 2.4.2

前提

記事を表す Post モデルに、published_at のような公開日カラムがあるとします。

ある期間に公開された記事を検索する場合

例えば 2019/05/01 ~ 2019/06/01 の間に公開された記事を検索する場合は、以下のように書くことができると思います。

Post.where(published_at: Time.local(2019,5,1)..Time.local(2019,6,1))

SQL は BETWEEN を使う形になっています。

irb(main):019:0> Post.where(published_at: Time.local(2019,5,1)..Time.local(2019,6,1)).to_sql
=> "SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"published_at\" BETWEEN '2019-04-30 15:00:00' AND '2019-05-31 15:00:00'"

ある期間を越えている記事のみ検索する場合

すぐわからなかったのが、published_at >= 2019/05/01 のような、ある期間を越えている記事のみ検索したいという場合についてでした。
結果としては、以下のような形で可能でした。

Post.where(published_at: Time.local(2019,5,1)..Float::INFINITY)

端を指定しない側に、浮動小数点数の無限大を表す Float::INFINITY を指定する方法です。

ruby の Range クラスにおいて、無限遠を指定する際には Float::INFINITY を指定できますが、
Time クラス、Date クラス、DateTime クラスでも使えるようでした。

SQL は以下のように、不等号の形となっています。

irb(main):018:0> Post.where(published_at: Time.local(2019,5,1)..Float::INFINITY).to_sql                              
=> "SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"published_at\" >= '2019-04-30 15:00:00'"

利用にあたってハマったところ

上記で片端のみ指定する形で日付の範囲指定ができるようになったのですが、
利用にあたってハマったところがあったので、そちらもまとめます。

負の無限大で範囲指定できない

上記の例では以下のように、正の無限大 Float::INFINITY を指定し、「ある日付を越えた日付を持つレコード」を表したのですが、
逆に「ある日付より以前の日付を持つレコード」を検索したい場合もあると思います。

そんなとき、以下のように書きたい気持ちになるのですが、これを実行すると ArgumentError: bad value for range というエラーが発生します。


Post.where(published_at: -::Float::INFINITY..Time.local(2019,5,1))

こちらは ruby のバグとして報告されているようで、まだ解決していないようでした。。

そのため以下のように not を使い、あくまで正の無限大を範囲に指定してあげる必要がありました。


Post.where.not(published_at: Time.local(2019,5,1)..Float::INFINITY)

null になっている値を検索できない

上記のように範囲検索を使う形にすると、日付のカラム値が null になっているレコードが検索対象外になってしまいます。

そのため、マイグレーション作成時に null: false としてあげるのが良さそうですが、
ではデフォルト値はどうするんだ?ということで悩みました。

postgresql であれば、文字列として infinity と指定してあげることで、
日付における無限大が表せるようで、rails 側で範囲検索の対象に含めることができるようになりました。

例ですが、マイグレーションはこんな感じになるかと思います。

class CreatePosts < ActiveRecord::Migration[5.2]
  def change
    create_table :posts do |t|
      t.string :title
      t.datetime :published_at, null: false, default: 'infinity'

      t.timestamps
    end
  end
end

以下コミットで、datedatetimetimestamp 型にも対応されているようです。

おわりに

利用にあたっていろいろ気をつけないといけないことが多かったですが、使えるようになると実装の幅が広がるのでは、と思いました。

これから利用する機会があれば使ってみようと思います。

参考記事

22
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
10