This Week in Rails: 安全な`to_i`変換、カスタム日付フォーマット登録、楽観的ロック修正
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::TimeFormats と ActiveSupport::DateFormats という新モジュールが導入され、カスタムフォーマットを登録するためのregisterメソッドが追加されました(#57345)。
これまでのカスタムフォーマット登録はTime::DATE_FORMATSやDate::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_FORMATS・Date::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_touchとrestore_transaction_record_stateの修正は、ActiveStorageと楽観的ロックを組み合わせているアプリケーションでStaleObjectErrorに悩んでいた開発者にとって直接的な恩恵をもたらします。