Fix inconsistent results of params.deep_transform_keys (#50361)

* Fix inconsistent results of params.deep_transform_keys

* fix: specs

* fix: implements own deep_transform methods to ActionController::Parameters

Co-authored-by: Rafael Mendonça França <rafael@rubyonrails.org>
This commit is contained in:
Iago Pimenta 2024-02-21 14:55:30 -03:00 committed by GitHub
parent 278d6574cf
commit 32587c3bdd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 61 additions and 2 deletions

View File

@ -796,7 +796,7 @@ module ActionController
# and from all nested hashes and arrays. The values are unchanged.
def deep_transform_keys(&block)
new_instance_with_inherited_permitted_status(
@parameters.deep_transform_keys(&block)
_deep_transform_keys_in_object(@parameters, &block).to_unsafe_h
)
end
@ -804,7 +804,7 @@ module ActionController
# This includes the keys from the root hash and from all nested hashes and
# arrays. The values are unchanged.
def deep_transform_keys!(&block)
@parameters.deep_transform_keys!(&block)
@parameters = _deep_transform_keys_in_object(@parameters, &block).to_unsafe_h
self
end
@ -1042,6 +1042,46 @@ module ActionController
end
end
def _deep_transform_keys_in_object(object, &block)
case object
when Hash
object.each_with_object(self.class.new) do |(key, value), result|
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
end
when Parameters
if object.permitted?
object.to_h.deep_transform_keys(&block)
else
object.to_unsafe_h.deep_transform_keys(&block)
end
when Array
object.map { |e| _deep_transform_keys_in_object(e, &block) }
else
object
end
end
def _deep_transform_keys_in_object!(object, &block)
case object
when Hash
object.keys.each do |key|
value = object.delete(key)
object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
end
object
when Parameters
if object.permitted?
object.to_h.deep_transform_keys!(&block)
else
object.to_unsafe_h.deep_transform_keys!(&block)
end
when Array
object.map! { |e| _deep_transform_keys_in_object!(e, &block) }
else
object
end
end
def specify_numeric_keys?(filter)
if filter.respond_to?(:keys)
filter.keys.any? { |key| /\A-?\d+\z/.match?(key) }

View File

@ -124,6 +124,25 @@ class ParametersMutatorsTest < ActiveSupport::TestCase
assert_predicate @params.deep_transform_keys! { |k| k }, :permitted?
end
test "deep_transform_keys! transforms nested keys" do
@params.permit!
@params.deep_transform_keys!(&:upcase)
expected_hash = { "PERSON" => { "AGE" => "32", "NAME" => { "FIRST" => "David", "LAST" => "Heinemeier Hansson" }, "ADDRESSES" => [{ "CITY" => "Chicago", "STATE" => "Illinois" }] } }
assert_equal @params.to_hash, expected_hash
end
test "deep_transform_keys transforms nested keys" do
original_hash = @params.to_unsafe_h
@params.permit!
new_params = @params.deep_transform_keys(&:upcase)
assert_equal @params.to_hash, original_hash
expected_hash = { "PERSON" => { "AGE" => "32", "NAME" => { "FIRST" => "David", "LAST" => "Heinemeier Hansson" }, "ADDRESSES" => [{ "CITY" => "Chicago", "STATE" => "Illinois" }] } }
assert_equal new_params.to_hash, expected_hash
end
test "deep_transform_keys! retains unpermitted status" do
assert_not_predicate @params.deep_transform_keys! { |k| k }, :permitted?
end