Time#advance と DateTime#advance が呼び出し元ハッシュを変更しないように修正

rails/rails

Time#advanceDateTime#advance が受け取ったオプションハッシュをコピーして処理するように変更し、呼び出し元のハッシュが変化せず、凍結ハッシュでも例外を発生させずに利用できるようになりました。

背景

この PR が対象とした問題は、Time#advanceDateTime#advance が受け取った options ハッシュをその場で書き換えていたことです。weeksdays に、dayshours に変換する際にハッシュへ新しいキーが追加され、呼び出し元の変数が予期せず変更されました。さらに、呼び出し元が凍結されたハッシュを渡すと FrozenError が発生し、例外が投げられました。

一方 Date#advance はハッシュを読み取るだけで内容を変更せず、同一 API で挙動が揃っていないことが開発者に混乱を与えていました。この不整合が原因で、オプションハッシュを再利用したコードや定数ハッシュを引数にしたコードが壊れるケースがありました。この状況を是正することが本修正の目的です。

技術的な変更

Time#advanceDateTime#advance の実装冒頭に options = options.dup を追加し、以降の処理をコピー上で行うようにしました。これによりハッシュのキー変換は呼び出し元に影響せず、凍結ハッシュでも変更可能なミュータブルなコピーが利用されます。変更は activesupport/lib/active_support/core_ext/time/calculations.rbactivesupport/lib/active_support/core_ext/date_time/calculations.rb のみで、ロジック自体は変わっていません。

変更前 (Time):

def advance(options)
  unless options[:weeks].nil?
    options[:weeks], partial_weeks = options[:weeks].divmod(1)
    options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
    # ...
  end
end

変更後 (Time):

def advance(options)
+  options = options.dup
  unless options[:weeks].nil?
    options[:weeks], partial_weeks = options[:weeks].divmod(1)
    options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
    # ...
  end
end

同様に DateTime#advance でも先頭に options = options.dup が挿入されています。これにより、キー操作はコピーに対して行われ、元のハッシュはそのまま残ります。

同時に activesupport/test/core_ext/time_ext_test.rbactivesupport/test/core_ext/date_time_ext_test.rb に2つずつテストが追加され、ハッシュが変化しないことと凍結ハッシュでも例外が出ないことを検証しています。すべての既存テストは緑のまま通過し、回帰がなく安全にリリースできます。

設計判断

メソッドを副作用なしにする設計は、Ruby の慣例に合致し API の予測可能性を高めます。options.dup という最小限の変更で済むため、コードベースへの侵入度が低く、パフォーマンスへの影響も無視できる程度です。また、Date#advance と同様の振る舞いに統一することで、ドキュメントと実装の整合性が取れました。

既存の呼び出し側が options ハッシュを再利用していた場合でも、コピーを操作するだけなので動作は従来と同一で、後方互換性が保たれます。したがって、アプリケーションコードの修正は不要です。

まとめ

この変更により Time#advanceDateTime#advance が呼び出し元ハッシュを汚染せず、凍結ハッシュでも安全に利用できるようになりました。API の一貫性と安全性が向上したことで、開発者はオプションハッシュを自由に再利用でき、予期せぬ副作用を回避できます。機能的な差分はなく、既存コードへの影響はありません。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
0143294b

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更、設計判断(任意)、まとめが明確に配置され、各セクションが総論→各論→結論の流れになっている。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックは `ruby:ファイルパス` 形式で正しく記述され、PRリンクは `[#57672](URL)` 形式で正しい。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

専門的な Ruby/Rails エンジニア向けに記述され、不要な初心者向け説明はない。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論の段落構成で、トピックセンテンスが先頭にあり、1段落1トピック、段落長も適切で空行で区切られている。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは提供された Diff と一致し、追加行 `options = options.dup` が正確に示されている。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている技術用語は PR と一致し、誤用はない。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的説明は Diff と PR の記述に基づき正確で、因果関係も妥当。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の主張はすべて PR の説明・Diff に裏付けられており、数値的根拠や外部情報の捏造はない。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号 #57672 などの数値は正確。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR の内容と意味的に一致している。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

外部知識は含まれておらず、PR情報のみで記述。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなし。