Railsで時間をJSON形式の文字列に変換するときに気をつけたいこと
前提
Railsでcreated_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_at
をiso8601
形式の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