Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Permitting arrays within a hash #121

Merged
merged 1 commit into from

8 participants

@simonc

Hi,

After digging the strong_parameters code and trying several filters I can't find a way to permit something like this:

{
  modification: {
    opening_hours: {
      "0" => ["08:00 18:00"],
      "1" => ["08:00 18:00"]
    }
  }
}

Is there a specific filter that would allow this ? I tried the following:

params.require(:modification).permit(:opening_hours)
params.require(:modification).permit(opening_hours: [])
params.require(:modification).permit(opening_hours: {})
params.require(:modification).permit(opening_hours: ['0'])
params.require(:modification).permit(opening_hours: { '0' => [] })

The only thing I get so far is this:

{
  opening_hours: {
    "0" => nil,
    "1" => nil
  }
}

Is there a solution here ?
Thanks :)

@mhenrixon

:+1: this is really frustrating. Right now we have to resource to some really ugly hacks.

@graudeejs

Same problem

@dhh
Owner

params.require(:modification).permit(opening_hours: { '0' => [] }) is the one that should work. It won't work with a variable number of keys, though. To allow your entire bit to work, it should be: params.require(:modification).permit(opening_hours: { '0' => [], '1' => [] })

If that's not working, it's a bug. Please do dig in!

@dhh
Owner

Worth asking, though. Why is the value an array? Looks like there's no need for that?

@simonc

Yeah sorry, not a really good example, the value could also be:

{
  modification: {
    opening_hours: {
      "0" => ["08:00 12:00", "14:00 19:00"],
      "1" => ["08:00 18:00"]
    }
  }
}
@dhh
Owner
@simonc

I checked with this code:

params = ActionController::Parameters.new({
  modification: {
    opening_hours: {
      "0" => ["08:00 12:00", "14:00 19:00"],
      "1" => ["08:00 18:00"]
    }
  }
})

params.require(:modification).permit(opening_hours: { '0' => [], '1' => [] })
#=> {"opening_hours"=>{"0"=>nil, "1"=>nil}}

So I guess it's a bug. I'll try to find a way to fix this and PR.
Thanks for your feedback.

@simonc simonc Allowing arrays to be nested in numeric-key hashes
When params was of the form

    {
      :book => {
        :authors_attributes => {
          :'0' => ['William Shakespeare', '52'],
          :'1' => ['Unattributed Assistant']
        }
      }
    }

And its corresponding filter was

    {
      :book => {
        :authors_attributes => {
          :'0' => [],
          :'1' => []
        }
      }
    }

It resulted in params looking like this

    {
      :book => {
        :authors_attributes => {
          :'0' => nil,
          :'1' => nil
        }
      }
    }

This was due to the way hashes with all numeric keys are treated.
I just added a special case when a corresponding filter exists and is an empty
array.
ad2c982
@simonc

I found a solution. It may not be the prettiest but it works and passes the tests.

@dhh dhh merged commit 5e351d2 into rails:master

1 check failed

Details default The Travis build failed
@arthurnn
Collaborator

:+1:

@dhh
Owner
dhh commented

@simonc, can you create a PR for rails/rails as well so we can get parity? Thanks!

@arthurnn
Collaborator

lol @dhh , I was about to PR myself, as today I was having hard time with this on rails/rails 4 ...

@simonc

@dhh I'll try but I'm on hollidays in California right now and the wifi in hotels is not so great, a simple bundle install is taking forever. I do it as soon as possible.

@simonc simonc deleted the simonc:121-arrays-within-a-hash branch
@fxn
Owner

This solution seems suspicious to me at first sight.

Hashes whose keys are numeric in principle should be transparent. Point is you in general do not want to declare all possible indexes. Wouldn't

params.require(:modification).permit(opening_hours: [])

be the expected declaration? The same way today you declare

params.permit(:book => { :authors_attributes => [ :name ] })

to match

ActionController::Parameters.new({
  :book => {
    :authors_attributes => {
      :'0' => { :name => 'William Shakespeare', :age_of_death => '52' },
      :'1' => { :name => 'Unattributed Assistant' },
      :'2' => { :name => %w(injected names)}
    }
  }
})

?

@simonc

It depends of the use cases I guess. In mine it's a limited set of indexes and they should be considered as any string indexes just as "name".
I guess allowing undefined numerical keys should be another subject but maybe I missing something :)

@dhh
Owner
@fxn
Owner

Excellent David, I'll have a stab at it.

@superp

It stay have a bug at 0.2.1 version

# controller
params.require(:specialist).permit(:skills_attributes => [:id, :price, :subcategory_ids => {} ])

# incoming params
"specialist" => {"skills_attributes" => {"0"=>{"_destroy"=>"false", "subcategory_ids"=>{"22"=>"1", "23"=>"0", "24"=>"1"}

# in model
subcategory_ids # {"22"=>nil, "23"=>nil, "24"=>nil}
@simonc

@superp I don't see how your example is supposed to work :/
skill_attributes is a hash and you're trying to permit it as an array.

@simonc

@superp sorry I read it wrong. But still, I'm not sure it's supposed to work. I may be wrong though.

@laffy

Was this ever pulled into rails?

Not that I know of. There is a Pull Request though. No activity for some time :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 26, 2013
  1. @simonc

    Allowing arrays to be nested in numeric-key hashes

    simonc authored
    When params was of the form
    
        {
          :book => {
            :authors_attributes => {
              :'0' => ['William Shakespeare', '52'],
              :'1' => ['Unattributed Assistant']
            }
          }
        }
    
    And its corresponding filter was
    
        {
          :book => {
            :authors_attributes => {
              :'0' => [],
              :'1' => []
            }
          }
        }
    
    It resulted in params looking like this
    
        {
          :book => {
            :authors_attributes => {
              :'0' => nil,
              :'1' => nil
            }
          }
        }
    
    This was due to the way hashes with all numeric keys are treated.
    I just added a special case when a corresponding filter exists and is an empty
    array.
This page is out of date. Refresh to see the latest.
View
12 lib/action_controller/parameters.rb
@@ -168,9 +168,9 @@ def permitted_scalar_filter(params, key)
end
end
- def array_of_permitted_scalars_filter(params, key)
- if has_key?(key) && array_of_permitted_scalars?(self[key])
- params[key] = self[key]
+ def array_of_permitted_scalars_filter(params, key, hash = self)
+ if hash.has_key?(key) && array_of_permitted_scalars?(hash[key])
+ params[key] = hash[key]
end
end
@@ -186,10 +186,12 @@ def hash_filter(params, filter)
array_of_permitted_scalars_filter(params, key)
else
# Declaration {:user => :name} or {:user => [:name, :age, {:adress => ...}]}.
- params[key] = each_element(value) do |element|
+ params[key] = each_element(value) do |element, index|
if element.is_a?(Hash)
element = self.class.new(element) unless element.respond_to?(:permit)
element.permit(*Array.wrap(filter[key]))
+ elsif filter[key].is_a?(Hash) && filter[key][index] == []
+ array_of_permitted_scalars_filter(params, index, value)
end
end
end
@@ -202,7 +204,7 @@ def each_element(value)
# fields_for on an array of records uses numeric hash keys.
elsif value.is_a?(Hash) && value.keys.all? { |k| k =~ /\A-?\d+\z/ }
hash = value.class.new
- value.each { |k,v| hash[k] = yield v }
+ value.each { |k,v| hash[k] = yield(v, k) }
hash
else
yield value
View
18 test/parameters_permit_test.rb
@@ -291,4 +291,22 @@ def assert_filtered_out(params, key)
assert_filtered_out permitted[:book][:authors_attributes]['-1'], :age_of_death
end
+
+ test "fields_for_style_nested_params with nested arrays" do
+ params = ActionController::Parameters.new({
+ :book => {
+ :authors_attributes => {
+ :'0' => ['William Shakespeare', '52'],
+ :'1' => ['Unattributed Assistant']
+ }
+ }
+ })
+ permitted = params.permit :book => { :authors_attributes => { :'0' => [], :'1' => [] } }
+
+ assert_not_nil permitted[:book][:authors_attributes]['0']
+ assert_not_nil permitted[:book][:authors_attributes]['1']
+ assert_nil permitted[:book][:authors_attributes]['2']
+ assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][0]
+ assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['1'][0]
+ end
end
Something went wrong with that request. Please try again.