Skip to content

Commit d8d2415

Browse files
rnubeljdurand
authored andcommitted
Bugfix: Correctly handle given in Array params (ruby-grape#1625)
* Bugfix: Correctly handle `given` in Array params Array parameters are handled as a parameter that opens a scope with `type: Array`; `given` opens up a new scope, but was always setting the type to Hash. This patch fixes that, as well as correctly labeling the error messages associated with array fields. * Code review fix * Add CHANGELOG entry
1 parent bb0de00 commit d8d2415

File tree

4 files changed

+60
-13
lines changed

4 files changed

+60
-13
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
### 1.0.0 (Next)
2+
3+
#### Features
4+
5+
* [#1594](https://github.com/ruby-grape/grape/pull/1594): Replace `Hashie::Mash` parameters with `ActiveSupport::HashWithIndifferentAccess` - [@james2m](https://github.com/james2m), [@dblock](https://github.com/dblock).
6+
* [#1622](https://github.com/ruby-grape/grape/pull/1622): Add `except_values` validator to replace `except` option of `values` validator - [@jlfaber](https://github.com/jlfaber).
7+
* [#1635](https://github.com/ruby-grape/grape/pull/1635): Instrument validators with ActiveSupport::Notifications - [@ktimothy](https://github.com/ktimothy).
8+
* Your contribution here.
9+
10+
#### Fixes
11+
12+
* [#1615](https://github.com/ruby-grape/grape/pull/1615): Fix default and type validator when values is a Hash with no value attribute - [@jlfaber](https://github.com/jlfaber).
13+
* [#1625](https://github.com/ruby-grape/grape/pull/1625): Handle `given` correctly when nested in Array params - [@rnubel](https://github.com/rnubel), [@avellable](https://github.com/avellable).
14+
* Your contribution here.
15+
116
### 0.19.2 (4/12/2017)
217

318
#### Features

lib/grape/validations/params_scope.rb

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,37 +42,45 @@ def should_validate?(parameters)
4242
return false if @optional && (params(parameters).blank? ||
4343
any_element_blank?(parameters))
4444

45+
return true if parent.nil?
46+
parent.should_validate?(parameters)
47+
end
48+
49+
def meets_dependency?(params)
50+
return true unless @dependent_on
51+
52+
params = params.with_indifferent_access
53+
4554
@dependent_on.each do |dependency|
4655
if dependency.is_a?(Hash)
4756
dependency_key = dependency.keys[0]
4857
proc = dependency.values[0]
49-
return false unless proc.call(params(parameters).try(:[], dependency_key))
50-
elsif params(parameters).try(:[], dependency).blank?
58+
return false unless proc.call(params.try(:[], dependency_key))
59+
elsif params.respond_to?(:key?) && params.try(:[], dependency).blank?
5160
return false
5261
end
53-
end if @dependent_on
62+
end
5463

55-
return true if parent.nil?
56-
parent.should_validate?(parameters)
64+
true
5765
end
5866

5967
# @return [String] the proper attribute name, with nesting considered.
60-
def full_name(name)
68+
def full_name(name, index: nil)
6169
if nested?
6270
# Find our containing element's name, and append ours.
63-
"#{@parent.full_name(@element)}#{array_index}[#{name}]"
71+
[@parent.full_name(@element), [@index || index, name].map(&method(:brackets))].compact.join
6472
elsif lateral?
65-
# Find the name of the element as if it was at the
66-
# same nesting level as our parent.
67-
@parent.full_name(name)
73+
# Find the name of the element as if it was at the same nesting level
74+
# as our parent. We need to forward our index upward to achieve this.
75+
@parent.full_name(name, index: @index)
6876
else
6977
# We must be the root scope, so no prefix needed.
7078
name.to_s
7179
end
7280
end
7381

74-
def array_index
75-
"[#{@index}]" if @index.present?
82+
def brackets(val)
83+
"[#{val}]" if val
7684
end
7785

7886
# @return [Boolean] whether or not this scope is the root-level scope
@@ -187,7 +195,7 @@ def new_lateral_scope(options, &block)
187195
element: nil,
188196
parent: self,
189197
options: @optional,
190-
type: Hash,
198+
type: type == Array ? Array : Hash,
191199
dependent_on: options[:dependent_on],
192200
&block
193201
)

lib/grape/validations/validators/base.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def validate!(params)
4141
array_errors = []
4242
attributes.each do |resource_params, attr_name|
4343
next unless @required || (resource_params.respond_to?(:key?) && resource_params.key?(attr_name))
44+
next unless @scope.meets_dependency?(resource_params)
4445

4546
begin
4647
validate_param!(attr_name, resource_params)

spec/grape/validations/params_scope_spec.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,29 @@ def initialize(value)
473473
end
474474
end
475475

476+
context 'when validations are dependent on a parameter within an array param' do
477+
before do
478+
subject.params do
479+
requires :foos, type: Array do
480+
optional :foo_type, :baz_type
481+
given :foo_type do
482+
requires :bar
483+
end
484+
end
485+
end
486+
subject.post('/test') { declared(params).to_json }
487+
end
488+
489+
it 'applies the constraint within each value' do
490+
post '/test',
491+
{ foos: [{ foo_type: 'a' }, { baz_type: 'c' }] }.to_json,
492+
'CONTENT_TYPE' => 'application/json'
493+
494+
expect(last_response.status).to eq(400)
495+
expect(last_response.body).to eq('foos[0][bar] is missing')
496+
end
497+
end
498+
476499
context 'when validations are dependent on a parameter with specific value' do
477500
# build test cases from all combinations of declarations and options
478501
a_decls = %i(optional requires)

0 commit comments

Comments
 (0)