This Week in Rails: 安全な`to_i`変換、カスタム日付フォーマット登録、楽観的ロック修正

rails/website

2026年5月23日付けの「This Week in Rails」では、DoS対策としての文字列変換制限、グローバル汚染を避けるフォーマット登録API、楽観的ロックまわりの重要なバグ修正を含む5件の変更が取り上げられています。

背景

今週号は、セキュリティ・APIの一貫性・並行処理の堅牢性という3つの異なる文脈での改善が集まりました。それぞれ独立した変更ですが、いずれも「既存の動作に潜む問題を修正しながら後方互換性を維持する」というアプローチを共通して取っています。

技術的な変更

to_i変換の文字列サイズ上限によるDoS対策

Active Modelのオート整数変換(to_i)に、入力文字列のバイト長制限が導入されました(#57368)。

非常に長い文字列に対してto_iを呼び出すと処理時間が線形増加し、DoSベクターになり得ます。今回の修正では、変換前にカラムの _limit(ストレージサイズ)を参照し、_limit * 4バイトに入力を切り詰めます。デフォルトの4バイト整数なら16バイト、8バイトのbigintなら32バイトが上限となります。

この上限は符号や区切り文字を含む十分な余裕を持った設計で、Post.where(id: params[:id])123-hello-worldのようなスラグ形式のIDが渡されても問題なく動作します。乗数を付けずに_limitバイトそのままにするとスラグが切り捨てられてしまうため、* 4の係数がトレードオフとして選ばれています。

グローバルハッシュを汚染しないto_fsフォーマット登録API

ActiveSupport::TimeFormatsActiveSupport::DateFormats という新モジュールが導入され、カスタムフォーマットを登録するためのregisterメソッドが追加されました(#57345)。

これまでのカスタムフォーマット登録はTime::DATE_FORMATSDate::DATE_FORMATSというグローバルハッシュに直接代入する方式でした。新APIでは以下のように記述します:

ActiveSupport::TimeFormats.register(:month_and_year, "%B %Y")
ActiveSupport::DateFormats.register(:short_ordinal, ->(date) { date.strftime("%B #{date.day.ordinalize}") })

Time.now.to_fs(:month_and_year)  # => "February 2024"
Date.today.to_fs(:short_ordinal) # => "February 21st"

旧来のTime::DATE_FORMATSDate::DATE_FORMATSへの直接代入は後方互換性のために引き続き動作しますが、deprecated扱いとなり次バージョンで削除される予定です。

ActionController::Parametersへのdeep_transform_values追加

ActionController::Parameters にHashが持つdeep_transform_valuesメソッドと同名のメソッドが追加されました(#57340)。バング版(deep_transform_values!)も実装され、permitted?の状態もネストした結果に引き継がれます。

params = ActionController::Parameters.new(
  user: {
    email: "  ALICE@EXAMPLE.COM  ",
    profile: { bio: "  Hello world  " }
  }
)

params.deep_transform_values { |v| v.is_a?(String) ? v.strip.downcase : v }

これによりネストしたパラメータを再帰的に正規化する処理が、パラメータオブジェクトの機能として一元化されます。

Blobアナリシス時のlock_versionバンプ抑制

ActiveRecord::Locking::Optimistic に新しいブロックヘルパー preserve_lock_version_on_touch が追加されました(#57284)。このブロック内で発生するタッチ操作ではlock_versionのインクリメントと対応するWHERE lock_version = X制約が抑制されます。updated_atは通常通り更新されるため、キャッシュキーの無効化は維持されます。

ActiveStorage::Blob#touch_attachments がこのヘルパーでラップされました。Blobのアナリシス(MIME type検出、画像サイズ取得など)は親レコードのフィールドを実際には変更しないにもかかわらず、タッチのたびにlock_versionが上がっていたため、並行アップロード時のStaleObjectErrorの頻繁な発生源となっていました。

ネストしたセーブポイントロールバック後のlock_versionリセット

restore_transaction_record_state がネストしたセーブポイントのロールバックごとに処理を短絡していたため、_update_rowがセーブポイント内でインクリメントしたlock_versionが残り続けるバグが修正されました(#57400)。

この状態でその後のsaveが実行されるとStaleObjectErrorが発生していました。修正ではロッキングを認識する分岐が追加され、lock_versionをスナップショットの original_value から再構築します。これにより、セーブポイントのロールバック後のWHERE句とダーティトラッキングが、セーブポイント内のインクリメントが存在しなかったかのように正しく動作します。OptimisticLockingRollbackTestに内側ロールバックと外側コミットの2つのリグレッションテストが追加されています。

設計判断

今週の変更群に共通するのは、既存APIの意味を保ちながら問題を修正するという設計方針です。to_i変換の制限では単純な打ち切りではなく乗数付きの係数を採用してスラグ互換性を維持し、TimeFormats/DateFormatsでは旧定数をdeprecatedとしながら残すことで段階的な移行を可能にしています。preserve_lock_version_on_touchはブロックスコープを使うことで、ロック抑制の範囲を局所化しています。

とりわけ楽観的ロックまわりは、#57284#57400の2件が同週に入っており、並行アップロードやネストトランザクションにおけるStaleObjectErrorの再現性が高まっていた状況を反映しています。

まとめ

今週のRailsは、セキュリティ(to_iDoS対策)、API品質(フォーマット登録の非グローバル化)、並行処理の堅牢性(楽観的ロックの2件の修正)という幅広い文脈をカバーした週でした。特にpreserve_lock_version_on_touchrestore_transaction_record_stateの修正は、ActiveStorageと楽観的ロックを組み合わせているアプリケーションでStaleObjectErrorに悩んでいた開発者にとって直接的な恩恵をもたらします。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
187cb687

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→背景→技術詳細(各論)→設計判断→まとめ(結論)という模範的な3部構成が記事全体に適用されています。各要素が明確に分離され、読者の理解を助けます。

カスタムMarkdown構文 ✓ PASS

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

PR番号のリンク記法([#123](URL))、シンタックスハイライト付きコードブロックなど、すべてのカスタムMarkdown構文が正しく使用されています。

対象読者への適合性 ✓ PASS

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

専門用語を前提として使用しており、過度な説明を省いているため、専門知識を持つエンジニアという対象読者に完全に適合しています。

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

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

各セクションが総論→各論の構成になっており、各段落もトピックセンテンスで始まるなど、パラグラフ・ライティングの原則がよく守られています。非常に読みやすいです。

Diff内容との照合 ✓ PASS

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

記事内のコード例や説明は、参照元であるPRのDiff(TWIR原文)の内容を正確に反映しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Active Record, Active Support, StaleObjectErrorなど、Railsに関する技術用語が文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

`_limit * 4`の具体的な計算例や、`preserve_lock_version_on_touch`の動作原理など、技術的な説明はすべて正確で論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張は、参照元のPR情報(TWIR原文)によって裏付けられています。「背景」や「設計判断」セクションは原文の情報を分析・再構成したものであり、ハルシネーション(捏造)ではありません。

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

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

PR番号、定数(`* 4`)、日付(2026年5月23日)など、記事に含まれるすべての数値・固有名詞は正確です。

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

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

記事のタイトルはPRの内容(複数のRailsへのコントリビューション)を的確に要約しており、読者の興味を引く適切なものです。

外部知識の正確性 ✓ PASS

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

「次バージョンで削除予定」といった情報はPRの原文(Diff)に記載されている内容であり、PR情報に基づかない外部知識の持ち込みはありません。

時間表現の正確性 ✓ PASS

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

「次バージョンで削除される予定です」といった将来に関する記述は、原文の「slated for removal in the next version」と一致しており、時間表現は正確です。