diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c17f18d3..daf25c654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * [#1252](https://github.com/ruby-grape/grape/pull/1252): Allow default to be a subset or equal to allowed values without raising IncompatibleOptionValues - [@jeradphelps](https://github.com/jeradphelps). * [#1255](https://github.com/ruby-grape/grape/pull/1255): Allow param type definition in `route_param` - [@namusyaka](https://github.com/namusyaka). * [#1257](https://github.com/ruby-grape/grape/pull/1257): Allow Proc, Symbol or String in `rescue_from with: ...` - [@namusyaka](https://github.com/namusyaka). +* [#1261](https://github.com/ruby-grape/grape/pull/1261): support array with index - [@itoufo](https://github.com/itoufo). * Your contribution here. #### Fixes diff --git a/README.md b/README.md index aade1a835..649f44d7a 100644 --- a/README.md +++ b/README.md @@ -912,6 +912,31 @@ params do end ``` +If type is Array, It support following kinds of parameter. +#### Array +```sh +#without index +curl -X POST http://localhost:9292/api/v1/preference.json -d \ + &preferences[][key]=foo \ + &preferences[][value]=100 \ + &preferences[][key]=bar \ + &preferences[][value]=200 + +#with index +curl -X POST http://localhost:9292/api/v1/preference.json -d \ + &preferences[0][key]=foo \ + &preferences[1][key]=bar \ + &preferences[0][value]=100 \ + &preferences[1][value]=200 +``` + +#### Hash +```sh +curl -X POST http://localhost:9292/api/v1/preference.json -d \ + &preferences[key]=foo \ + &preferences[value]=100 \ +```` + ### Dependent Parameters Suppose some of your parameters are only relevant if another parameter is given; diff --git a/lib/grape.rb b/lib/grape.rb index 78dc98c15..6f1beda19 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -129,6 +129,7 @@ module Util autoload :InheritableSetting autoload :StrictHashConfiguration autoload :FileResponse + autoload :HashParameter end module DSL diff --git a/lib/grape/dsl/parameters.rb b/lib/grape/dsl/parameters.rb index adbad5d2b..e7087de2b 100644 --- a/lib/grape/dsl/parameters.rb +++ b/lib/grape/dsl/parameters.rb @@ -8,6 +8,8 @@ module DSL module Parameters extend ActiveSupport::Concern + include Grape::Util::HashParameter + # Include reusable params rules among current. # You can define reusable params with helpers method. # @@ -188,6 +190,7 @@ def declared_param?(param) # @api private def params(params) params = @parent.params(params) if @parent + if @element if params.is_a?(Array) params = params.flat_map { |el| el[@element] || {} } @@ -197,6 +200,9 @@ def params(params) params = {} end end + + params = params.values if params.is_a?(Hash) && deem_hash_array?(params) + params end end diff --git a/lib/grape/util/hash_parameter.rb b/lib/grape/util/hash_parameter.rb new file mode 100644 index 000000000..5951cd8d0 --- /dev/null +++ b/lib/grape/util/hash_parameter.rb @@ -0,0 +1,20 @@ +require 'active_support/concern' +module Grape + module Util + module HashParameter + extend ActiveSupport::Concern + + def deem_hash_array?(hash) + return false unless hash.is_a?(Hash) && hash.keys.any? { |key| integer_string?(key) } + true + end + + def integer_string?(str) + Integer(str) + true + rescue ArgumentError, TypeError + false + end + end + end +end diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index eae075613..3ab89c735 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -3,6 +3,8 @@ module Validations class Base attr_reader :attrs + include Grape::Util::HashParameter + # Creates a new Validator from options specified # by a +requires+ or +optional+ directive during # parameter definition. diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index 624480728..dcc0e1f5a 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -36,8 +36,10 @@ def valid_type?(val) # Allow nil, to ignore when a parameter is absent return true if val.nil? - - converter.value_coerced? val + unless !type.is_a?(Types::VariantCollectionCoercer) && type == Array && val.is_a?(Hash) && deem_hash_array?(val) + return converter.value_coerced? val + end + true end def coerce_value(val) diff --git a/spec/grape/validations/validators/default_spec.rb b/spec/grape/validations/validators/default_spec.rb index 72ceacc5b..0806a5b29 100644 --- a/spec/grape/validations/validators/default_spec.rb +++ b/spec/grape/validations/validators/default_spec.rb @@ -99,6 +99,12 @@ def app expect(last_response.body).to eq({ array: [{ name: 'name', with_default: 'default' }, { name: 'name2', with_default: 'bar2' }] }.to_json) end + it 'sets default values for grouped arrays with index' do + get('/array?array[0][name]=name&array[1][name]=name2&array[0][with_default]=bar1') + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({ array: { 0 => { name: 'name', with_default: 'bar1' }, 1 => { name: 'name2', with_default: 'default' } } }.to_json) + end + context 'optional group with defaults' do subject do Class.new(Grape::API) do