Rails6.0 | 6.1 でのActiveSupport::Durationの挙動の違い
この記事を読むと、助かるかもしれないエンジニア
- 現在、Rails6.0 → 6.1 へアップデートしようと準備している方
- 直近で、Rails6.1 へアップデートを完了できている方
この記事を書くまでの経緯
私は、Rails で動く Web アプリを開発しています。先日、Rails6.1 へアップデートをする際に、バージョンを挙げたことで ActiveSupport::Duration の挙動で少しハマりました。
Rails で動く Web アプリを開発・運用している会社さんは、影響ありそうだなと思い、この記事を書くことにしました。
ActiveSupport::Duration とは
日付の事前と時刻の事前設定をそれぞれ使用して、正確な日付と時刻の測定値を提供します。主に Numeric のメソッドをサポートしています。
ActiveSupport::Duration の戻り値の違い
今回、Rails6.0 と 6.1 でのActiveSupport::Duration の戻り値の違いを紹介するために、用意した最小コードはこれです。
diff_time = Time.currnet.since(3.hours) - Time.current
ActiveSupport::Duration.build(diff_time).parts
Rails6.0 での実行結果
# rails c
# Loading development environment (Rails 6.0.5)
pry(main)> diff_time = Time.currnet.since(3.hours) - Time.current
=> 10799.999942775
pry(main)> diff_time_parts = ActiveSupport::Duration.build(diff_time).parts
=> {:hours=>2, :minutes=>59, :seconds=>59.99994277500082}
[3] pry(main)> diff_time_parts[:days]
=> 0
Rails6.0 では、diff_time_parts[:days]
と実行すると、0が出力されます。
Rails6.1 での実行結果
# rails c
# Loading development environment (Rails 6.1.6)
pry(main)> diff_time = Time.currnet.since(3.hours) - Time.current
=> 10799.999942775
pry(main)> diff_time_parts = ActiveSupport::Duration.build(diff_time).parts
=> {:hours=>2, :minutes=>59, :seconds=>59.99994277500082}
[3] pry(main)> diff_time_parts[:days]
=> nil
Rails6.1 では、diff_time_parts[:days]
と実行すると、nilが出力されます。
こんなコードの時、エラーが出てしまう
電車の発車時刻から現在の時刻までの残り時間(分単位)を出したいとする。
Rails6.1 で、以下のコードを実行すると、diff_time_parts[:days]
が nil であるため、
diff_time_parts[:days] * 24
を計算しようとすると、そこでエラーが起きてしまう。
departure_time = Time.currnet.since(3.hours)
remain_minutes_before_departure = calcurate_remain_minutes(departure_time)
def calcurate_remain_minutes(departure_time)
diff_time = departure_time - Time.current
diff_time_parts = ActiveSupport::Duration.build(diff_time).parts
# diff_timeが1日未満の場合、daysキーの値はnilとなり、エラーが起きる。
diff_time_parts[:days] * 24 + diff_times_parts[:hours] * 60 + diff_time_parts[:minutes]
end
このようなコードを書いて、残り時間を表示しているWebアプリはありそうです。
Rails6.1 へアップデートする前に、このようなコードがあるか確認した方がいいかと思います。
エラー回避方法
キーを持っていない場合は0を代入してあげれば、一応エラー回避することは可能です。
departure_time = Time.currnet.since(3.hours)
remain_minutes_before_departure = calcurate_remain_minutes(departure_time)
def calcurate_remain_minutes(departure_time)
diff_time = departure_time - Time.current
diff_time_parts = ActiveSupport::Duration.build(diff_time).parts
# diff_timeが1日未満の場合、daysキーの値はnilとなり、エラーが起きる。
+ diff_time_parts[:days] = 0 unless diff_time_parts.keys?(:days)
diff_time_parts[:days] * 24 + diff_times_parts[:hours] * 60 + diff_time_parts[:minutes]
end
参考資料
ActiveSupport::Duration Rails6.0
ActiveSupport::Duration Rails6.1
Discussion
6.0では@partsのデフォルト値が0に設定されていた[1]のが、6.1ではそれが行われなくなったようですね。
なので、
:days
だけ個別に設定するよりは以下のようにdefaultを0にする方が6.0と同じ挙動になって扱いやすいかもしれません。https://github.com/rails/rails/blob/91cf62e7b43c33ae6263adf3d7563da9b68ff21d/activesupport/lib/active_support/duration.rb#L210 ↩︎
おおー!.defaultで設定できるのですね!
そこまでコードを読めていませんでした。
コードのリンクも添えていただいて、ありがとございます!