`ActionController::Parameters`に`deep_transform_values`を追加
ActionController::Parametersにdeep_transform_valuesとdeep_transform_values!が追加され、Strong Parametersの枠組みを維持したまま全ネスト値の一括変換が可能になりました。これにより、ホワイトスペース除去や文字列正規化のためだけにto_unsafe_hを呼び出す必要がなくなります。
背景
これまで、ネストされたパラメータの全値を変換するにはto_unsafe_hを通じてプレーンなHashに変換するしかありませんでした。この方法ではpermitted/unpermittedの区別が失われ、Strong Parametersのガードレールを意図せず迂回してしまいます。
具体的には、バリデーション前にすべての文字列からホワイトスペースを除去する処理が典型例です。これは日常的な正規化タスクであるにもかかわらず、既存の手段では以下のように書くしかありませんでした:
params.to_unsafe_h.deep_transform_values { |v| v.is_a?(String) ? v.strip : v }
ActionController::Parametersは既にdeep_transform_keysとdeep_transform_keys!を持ち、Active SupportもHashにdeep_transform_valuesを提供していました。今回の追加はこの非対称性を解消し、キー変換とバリュー変換の一貫したインターフェースを実現します。
技術的な変更
strong_parameters.rbにdeep_transform_valuesとdeep_transform_values!の2メソッドが追加され、内部処理を担うプライベートメソッド_deep_transform_values_in_objectと_deep_transform_values_in_object!が実装されました。
非破壊版のdeep_transform_valuesは、new_instance_with_inherited_permitted_statusを通じて変換結果を新しいParametersインスタンスに包み直します。これは既存のdeep_transform_keysと同じパターンです:
def deep_transform_values(&block)
new_instance_with_inherited_permitted_status(
_deep_transform_values_in_object(@parameters, &block)
)
end
def deep_transform_values!(&block)
@parameters = _deep_transform_values_in_object!(@parameters, &block)
self
end
ブロックはリーフ値(末端の値)にのみ渡され、Hash・Array・Parametersのような中間ノードはトラバーサルの対象となるだけでブロックには渡されません。また、ファイル先頭ではactive_support/core_ext/hash/deep_transform_valuesのrequireが追加され、Active Supportの実装との連携が明示されています。
メソッドの動作を示すと、以下のようになります:
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 }
# => #<ActionController::Parameters {"user"=>#<ActionController::Parameters {"email"=>"alice@example.com", "profile"=>#<ActionController::Parameters {"bio"=>"hello world"} permitted: false>} permitted: false>} permitted: false>
# permit!後のインスタンスでもpermitted?が引き継がれる
params.permit!
params.deep_transform_values { |v| v }.permitted? # => true
テストはaccessors_test.rbとmutators_test.rbに分けて追加されており、permitted状態の継承・リーフ値のみへの適用・非破壊性(deep_transform_valuesが元のインスタンスを変更しないこと)がそれぞれ検証されています。
設計判断
permitted?ステータスの継承が明示的に設計されています。変換後のインスタンスもpermit / expectを通過させないとマスアサインメントには使えないという制約が保たれており、Strong Parametersの安全モデルが損なわれません。
既存のdeep_transform_keysの実装パターンをそのまま踏襲しています。new_instance_with_inherited_permitted_statusの再利用、case文によるオブジェクト種別の分岐、!版がselfを返す設計など、コードベースの一貫性が優先されています。新たな設計上の概念は導入されておらず、既存のパターンへの準拠が選ばれています。
まとめ
本PRは、deep_transform_keysとの対称性を埋めることで、Strong Parametersを正しく使いながら値の正規化処理を記述できる手段を追加しました。to_unsafe_hという安全でない迂回路を排除し、日常的な前処理をStrong Parametersの保護下で完結させられるようになったことが、この変更の本質的な意義です。