💡

Rails6.0 | 6.1 でのActiveSupport::Durationの挙動の違い

2022/08/22に公開2

この記事を読むと、助かるかもしれないエンジニア

  • 現在、Rails6.0 → 6.1 へアップデートしようと準備している方
  • 直近で、Rails6.1 へアップデートを完了できている方

この記事を書くまでの経緯

私は、Rails で動く Web アプリを開発しています。先日、Rails6.1 へアップデートをする際に、バージョンを挙げたことで ActiveSupport::Duration の挙動で少しハマりました。
Rails で動く Web アプリを開発・運用している会社さんは、影響ありそうだなと思い、この記事を書くことにしました。

ActiveSupport::Duration とは

日付の事前と時刻の事前設定をそれぞれ使用して、正確な日付と時刻の測定値を提供します。主に Numeric のメソッドをサポートしています。

https://github.com/rails/rails/blob/6-0-stable/activesupport/lib/active_support/duration.rb#L8-L13

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

IWAMOTO KouichiIWAMOTO Kouichi

6.0では@partsのデフォルト値が0に設定されていた[1]のが、6.1ではそれが行われなくなったようですね。

なので、:daysだけ個別に設定するよりは以下のようにdefaultを0にする方が6.0と同じ挙動になって扱いやすいかもしれません。

diff_time_parts.default = 0
脚注
  1. https://github.com/rails/rails/blob/91cf62e7b43c33ae6263adf3d7563da9b68ff21d/activesupport/lib/active_support/duration.rb#L210 ↩︎

sontixyousontixyou

おおー!.defaultで設定できるのですね!
そこまでコードを読めていませんでした。

コードのリンクも添えていただいて、ありがとございます!