diff options
Diffstat (limited to 'actionpack/lib/action_controller/metal/strong_parameters.rb')
-rw-r--r-- | actionpack/lib/action_controller/metal/strong_parameters.rb | 55 |
1 files changed, 42 insertions, 13 deletions
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 23d70c9ea2..48a916f2b1 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' require 'action_dispatch/http/upload' require 'stringio' +require 'set' module ActionController # Raised when a required parameter is missing. @@ -17,7 +18,7 @@ module ActionController def initialize(param) # :nodoc: @param = param - super("param not found: #{param}") + super("param is missing or the value is empty: #{param}") end end @@ -125,6 +126,13 @@ module ActionController @permitted = self.class.permit_all_parameters end + # Attribute that keeps track of converted arrays, if any, to avoid double + # looping in the common use case permit + mass-assignment. Defined in a + # method to instantiate it only if needed. + def converted_arrays + @converted_arrays ||= Set.new + end + # Returns +true+ if the parameter is permitted, +false+ otherwise. # # params = ActionController::Parameters.new @@ -149,8 +157,10 @@ module ActionController # Person.new(params) # => #<Person id: nil, name: "Francesco"> def permit! each_pair do |key, value| - convert_hashes_to_parameters(key, value) - self[key].permit! if self[key].respond_to? :permit! + value = convert_hashes_to_parameters(key, value) + Array.wrap(value).each do |_| + _.permit! if _.respond_to? :permit! + end end @permitted = true @@ -201,6 +211,7 @@ module ActionController # You may declare that the parameter should be an array of permitted scalars # by mapping it to an empty array: # + # params = ActionController::Parameters.new(tags: ['rails', 'parameters']) # params.permit(tags: []) # # You can also use +permit+ on nested parameters, like: @@ -230,7 +241,7 @@ module ActionController # params = ActionController::Parameters.new({ # person: { # contact: { - # email: 'none@test.com' + # email: 'none@test.com', # phone: '555-1234' # } # } @@ -283,7 +294,7 @@ module ActionController # params.fetch(:none, 'Francesco') # => "Francesco" # params.fetch(:none) { 'Francesco' } # => "Francesco" def fetch(key, *args) - convert_hashes_to_parameters(key, super) + convert_hashes_to_parameters(key, super, false) rescue KeyError raise ActionController::ParameterMissing.new(key) end @@ -297,7 +308,7 @@ module ActionController # params.slice(:d) # => {} def slice(*keys) self.class.new(super).tap do |new_instance| - new_instance.instance_variable_set :@permitted, @permitted + new_instance.permitted = @permitted end end @@ -311,24 +322,38 @@ module ActionController # copy_params.permitted? # => true def dup super.tap do |duplicate| - duplicate.instance_variable_set :@permitted, @permitted + duplicate.permitted = @permitted end end + protected + def permitted=(new_permitted) + @permitted = new_permitted + end + private - def convert_hashes_to_parameters(key, value) - if value.is_a?(Parameters) || !value.is_a?(Hash) + def convert_hashes_to_parameters(key, value, assign_if_converted=true) + converted = convert_value_to_parameters(value) + self[key] = converted if assign_if_converted && !converted.equal?(value) + converted + end + + def convert_value_to_parameters(value) + if value.is_a?(Array) && !converted_arrays.member?(value) + converted = value.map { |_| convert_value_to_parameters(_) } + converted_arrays << converted + converted + elsif value.is_a?(Parameters) || !value.is_a?(Hash) value else - # Convert to Parameters on first access - self[key] = self.class.new(value) + self.class.new(value) end end def each_element(object) if object.is_a?(Array) object.map { |el| yield el }.compact - elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ } + elsif fields_for_style?(object) hash = object.class.new object.each { |k,v| hash[k] = yield v } hash @@ -337,6 +362,10 @@ module ActionController end end + def fields_for_style?(object) + object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) } + end + def unpermitted_parameters!(params) unpermitted_keys = unpermitted_keys(params) if unpermitted_keys.any? @@ -415,7 +444,7 @@ module ActionController # Slicing filters out non-declared keys. slice(*filter.keys).each do |key, value| - return unless value + next unless value if filter[key] == EMPTY_ARRAY # Declaration { comment_ids: [] }. |