Help us understand the problem. What is going on with this article?

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

はじめに

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 型にも対応されているようです。

https://github.com/rails/rails/commit/d75063ce3ece4d1d0d06a9088cefb7713eb3cb8d

おわりに

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

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

参考記事

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away