Skip to content

Commit

Permalink
Added support for Rails style nested attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Navarrete and Jonas Nicklas committed Apr 19, 2012
1 parent c36497c commit 9e965b1
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 17 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,14 @@ Using Rails' skip_before_filter to make a controller insecure again
end

## TODO
## Nested attributes

* Mark a parameter that expects an array as its value:
* param_accessible :user => ["group_ids[]"]
* would accept [10,12] or {"0" => 10, "1" => 12}
* param_accessible :user => {"groups[]" => [:name]}
* would accept [{:name => "Admins"}, {:name => "Users"}]
Arrays of attributes like the ones used with Rails' nested forms feature can be specified using
the following syntax:

class DemoController < ApplicationController
param_accessible :"foo[]" => [:bar, :baz]
end

## Contributing

Expand Down
29 changes: 27 additions & 2 deletions lib/param_accessible/rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ def accessible_params_for controller, dest

def accessible_hash_for params, attributes, dest
attributes.each do |key, value|
if value.is_a?(Hash)
if key.to_s =~ /\[\]$/
accessible_array_for key, params, value, dest
elsif value.is_a?(Hash)
attrs = dest[key]
if attrs.nil?
attrs = {}
Expand Down Expand Up @@ -75,7 +77,30 @@ def accessible_params_for_regex regex, params, dest

dest
end


def accessible_array_for key, params, value, dest
key = key.to_s.chomp('[]')

if params and params[key].is_a? Hash
params[key].each do |index, nested_params|
dest[key] ||= {}
attrs = dest[key][index] = {}
accessible_hash_for nested_params, value, attrs if value
end
elsif params and params[key].is_a? Array
params[key].each do |nested_params|
if nested_params.is_a? Hash
dest[key] ||= []
attrs = {}
accessible_hash_for nested_params, value, attrs if value
dest[key].push(attrs)
else
dest[key] = nil
end
end
end
end

# When specifying params to protect, we allow a combination of arrays and hashes much like how
# ActiveRecord::Base#find's :include options works. This method normalizes that into just nested hashes,
# stringifying the keys and setting all values to nil. This format is easier/faster to work with when
Expand Down
5 changes: 2 additions & 3 deletions lib/param_accessible/rules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ def detect_inaccessible_hash hash, accessible, errors, prefix = nil
detect_inaccessible_hash value, nested, errors, prefix_for(prefix, key)

elsif value.is_a?(Array)
nested = accessible[key] || {}
value.each do |v|
value.each_with_index do |v, i|
if v.is_a?(Hash)
detect_inaccessible_hash v, nested, errors, prefix_for(prefix, key)
detect_inaccessible_hash v, accessible[key][i], errors, prefix_for(prefix_for(prefix, key), "")
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions spec/app_root/app/controllers/nested_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class NestedController < ApplicationController
param_accessible :a => [{:"b[]" => [:d, :e, :q]}, :c]
param_accessible :"o[]", :p
end
88 changes: 88 additions & 0 deletions spec/lib/nested_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe NestedController do
include RSpec::Rails::ControllerExampleGroup

it "should allow arrays given as rails style pseudo hashes" do
post :create, :a => { :b => {"0" => {"d"=>"foo", "e"=>"bar"}, "1" => {"d"=>"foo", "e"=>"bar"}}}
response.code.should == "200"
end

it "should allow arrays given as arrays hashes" do
post :create, :a => { :b => [{"d"=>"foo", "e"=>"bar"}, {"d"=>"foo", "e"=>"bar"}]}
response.code.should == "200"
end

it "should allow arrays given as arrays hashes" do
post :create, :o => ["foo", "bar"]
response.code.should == "200"
end

it "should allow arrays given as rails style hash arrays when only arrays of strings are allowed" do
post :create, :o => { "0" => "foo", "1" => "bar" }
response.code.should == "200"
end

it "should not allow options which are not in the list" do
begin
post :create, :a => { :b => {"0" => {"x"=>"foo" }}}
raise "should fail"
rescue ParamAccessible::Error => e
e.inaccessible_params.should == %w(a[b][0][x])
end
end

it "should not allow strings for an array" do
begin
post :create, :a => { :b => "foo" }
raise "should fail"
rescue ParamAccessible::Error => e
e.inaccessible_params.should == %w(a[b])
end
end

it "should not allow arrays with invalid values" do
begin
post :create, :a => { :b => [{"x"=>"foo"}]}
raise "should fail"
rescue ParamAccessible::Error => e
e.inaccessible_params.should == %w(a[b][][x])
end
end

it "should not allow arrays with invalid values at other index" do
begin
post :create, :a => { :b => [{"d" => "foo"}, {"x"=>"foo"}]}
raise "should fail"
rescue ParamAccessible::Error => e
e.inaccessible_params.should == %w(a[b][][x])
end
end

it "should not allow hashes when only arrays of strings are allowed" do
begin
post :create, :o => [{ :foo => "bar" }]
raise "should fail"
rescue ParamAccessible::Error => e
e.inaccessible_params.should == %w(o[][foo])
end
end

it "should not allow string when only arrays are allowed" do
begin
post :create, :o => "foo"
raise "should fail"
rescue ParamAccessible::Error => e
e.inaccessible_params.should == %w(o)
end
end

it "should not allow arrays given as rails style hash arrays containing hashes when only arrays of strings are allowed" do
begin
post :create, :o => { "0" => { "x" => "foo" } }
raise "should fail"
rescue ParamAccessible::Error => e
e.inaccessible_params.should == %w(o[0][x])
end
end
end
7 changes: 1 addition & 6 deletions spec/lib/simple_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,7 @@
e.inaccessible_params.should == %w(bar[unknown])
end
end

it "should handle nested arrays" do
post :create, :bar => [:baz => 'hi']
response.code.should == '200'
end


it "should be fine when the ONLY value is a value but not an options hash" do
SimpleController.param_accessible :bar => []
end
Expand Down

0 comments on commit 9e965b1

Please sign in to comment.