あきろぐ

いろいろめもするよ🐈🐈🐈

Railsで時間をJSON形式の文字列に変換するときに気をつけたいこと

前提

Railscreated_atを含む文字列をJSONに変換したものを受け取った後、そのJSON文字列からcreated_atを取り出し、DBにデータを取りに行くような実装がされていたとします。 その際、想定通りのデータがDBから取得できずに困ったので、その調査&原因をまとめます。

# articleテーブルから1レコードを取得する
article = Article.last
  Article Load (6.0ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" DESC LIMIT $1  [["LIMIT", 1]]
=>
#<Article:0x000000010c6eae08
...

# articleのレコードを使ってjsonデータを生成する
data = { id: article.id, created_at: article.created_at }
=> {:id=>1040597104, :created_at=>Sun, 05 Mar 2023 21:34:37.707825000 JST +09:00}
json = JSON.generate(data)
=> "{\"id\":1040597104,\"created_at\":\"2023-03-05 21:34:37 +0900\"}"

問題

上記のようにJSON化されたデータを受け取り、パースした上でDBにデータを取りに行くと、articlesに含まれていてほしいレコードが存在していませんでした。

data = JSON.parse(json)
=> {"id"=>1040597104, "created_at"=>"2023-03-05 21:34:37 +0900"}

articles = Article.where('id < ? and created_at < ?', data["id"], data["created_at"])
  Article Load (1.8ms)  SELECT "articles".* FROM "articles" WHERE (id < 1040597104 and created_at < '2023-03-05 21:34:37 +0900')
=>
[#<Article:0x000000010c1d16d8
...

原因

created_atを含む文字列をJSON.generateするとミリ秒が切り捨てられるため、ほぼ同時刻にDBに保存されたデータは対象外となり取得できていませんでした。

解決策

created_atiso8601形式のStringに変換した後、JSON化すればミリ秒を保持することができます。

data = { 'id': article.id, 'created_at': article.created_at.iso8601(6) }
=> {:id=>1040597104, :created_at=>"2023-03-05T21:34:37.707825+09:00"}

# ミリ秒まで保持されている
json = JSON.generate(data)
=> "{\"id\":1040597104,\"created_at\":\"2023-03-05T21:34:37.707825+09:00\"}"

パースする時は、in_time_zoneメソッドでActiveSupport::TimeWithZoneクラスのオブジェクトに変換します。

data = JSON.parse(json)
=> {"id"=>1040597104, "created_at"=>"2023-03-05T21:34:37.707825+09:00"}

data['created_at'].in_time_zone
=> Sun, 05 Mar 2023 21:34:37.707825000 JST +09:00

参考

api.rubyonrails.org

docs.ruby-lang.org