Skip to content

Commit d021e22

Browse files
author
Tim Vandecasteele
committed
Allow validation of nested parameters.
Passing the scope to the validators, so they can take the relevant parameters. Using the group syntax which allows for: group :user do group :admin do requires :first_name, :last_name end end
1 parent 9bd4f23 commit d021e22

File tree

3 files changed

+104
-7
lines changed

3 files changed

+104
-7
lines changed

lib/grape/validations.rb

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ module Validations
88
# All validators must inherit from this class.
99
#
1010
class Validator
11-
def initialize(attrs, options, required)
11+
def initialize(attrs, options, required, scope)
1212
@attrs = Array(attrs)
1313
@required = required
14+
@scope = scope
1415

1516
if options.is_a?(Hash) && !options.empty?
1617
raise "unknown options: #{options.keys}"
1718
end
1819
end
1920

2021
def validate!(params)
22+
params = @scope.params(params)
23+
2124
@attrs.each do |attr_name|
2225
if @required || params.has_key?(attr_name)
2326
validate_param!(attr_name, params)
@@ -40,7 +43,7 @@ def self.convert_to_short_name(klass)
4043
##
4144
# Base class for all validators taking only one param.
4245
class SingleOptionValidator < Validator
43-
def initialize(attrs, options, required)
46+
def initialize(attrs, options, required, scope)
4447
@option = options
4548
super
4649
end
@@ -67,7 +70,11 @@ def self.register_validator(short_name, klass)
6770
end
6871

6972
class ParamsScope
70-
def initialize(api, &block)
73+
attr_accessor :element, :parent
74+
75+
def initialize(api, element, parent, &block)
76+
@element = element
77+
@parent = parent
7178
@api = api
7279
instance_eval(&block)
7380
end
@@ -89,7 +96,17 @@ def optional(*attrs)
8996

9097
validates(attrs, validations)
9198
end
92-
99+
100+
def group(element, &block)
101+
scope = ParamsScope.new(@api, element, self, &block)
102+
end
103+
104+
def params(params)
105+
params = @parent.params(params) if @parent
106+
params = params[@element] || {} if @element
107+
params
108+
end
109+
93110
private
94111
def validates(attrs, validations)
95112
doc_attrs = { :required => validations.keys.include?(:presence) }
@@ -130,7 +147,7 @@ def validates(attrs, validations)
130147
def validate(type, options, attrs, doc_attrs)
131148
validator_class = Validations::validators[type.to_s]
132149
if validator_class
133-
@api.settings[:validations] << validator_class.new(attrs, options, doc_attrs[:required])
150+
@api.settings[:validations] << validator_class.new(attrs, options, doc_attrs[:required], self)
134151
else
135152
raise "unknown validator: #{type}"
136153
end
@@ -145,7 +162,7 @@ def reset_validations!
145162
end
146163

147164
def params(&block)
148-
ParamsScope.new(self, &block)
165+
ParamsScope.new(self, nil, nil, &block)
149166
end
150167

151168
def document_attribute(names, opts)

spec/grape/validations/coerce_spec.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ class User
111111
last_response.status.should == 201
112112
last_response.body.should == File.basename(__FILE__).to_s
113113
end
114+
115+
it 'Nests integers' do
116+
subject.params do
117+
group :integers do
118+
requires :int, :coerce => Integer
119+
end
120+
end
121+
subject.get '/int' do params[:integers][:int].class; end
122+
123+
get '/int', { :integers => { :int => "45" } }
124+
last_response.status.should == 200
125+
last_response.body.should == 'Fixnum'
126+
end
114127
end
115128
end
116129
end

spec/grape/validations/presence_spec.rb

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,29 @@ class API < Grape::API
2626
get do
2727
"Hello"
2828
end
29+
30+
params do
31+
group :user do
32+
requires :first_name, :last_name
33+
end
34+
end
35+
get '/nested' do
36+
"Nested"
37+
end
38+
39+
params do
40+
group :admin do
41+
requires :admin_name
42+
group :super do
43+
group :user do
44+
requires :first_name, :last_name
45+
end
46+
end
47+
end
48+
end
49+
get '/nested_triple' do
50+
"Nested triple"
51+
end
2952
end
3053
end
3154
end
@@ -67,5 +90,49 @@ def app
6790
last_response.status.should == 200
6891
last_response.body.should == "Hello"
6992
end
70-
93+
94+
it 'validates nested parameters' do
95+
get('/nested')
96+
last_response.status.should == 400
97+
last_response.body.should == "missing parameter: first_name"
98+
99+
get('/nested', :user => {:first_name => "Billy"})
100+
last_response.status.should == 400
101+
last_response.body.should == "missing parameter: last_name"
102+
103+
get('/nested', :user => {:first_name => "Billy", :last_name => "Bob"})
104+
last_response.status.should == 200
105+
last_response.body.should == "Nested"
106+
end
107+
108+
it 'validates triple nested parameters' do
109+
get('/nested_triple')
110+
last_response.status.should == 400
111+
last_response.body.should == "missing parameter: admin_name"
112+
113+
get('/nested_triple', :user => {:first_name => "Billy"})
114+
last_response.status.should == 400
115+
last_response.body.should == "missing parameter: admin_name"
116+
117+
get('/nested_triple', :admin => {:super => {:first_name => "Billy"}})
118+
last_response.status.should == 400
119+
last_response.body.should == "missing parameter: admin_name"
120+
121+
get('/nested_triple', :super => {:user => {:first_name => "Billy", :last_name => "Bob"}})
122+
last_response.status.should == 400
123+
last_response.body.should == "missing parameter: admin_name"
124+
125+
get('/nested_triple', :admin => {:super => {:user => {:first_name => "Billy"}}})
126+
last_response.status.should == 400
127+
last_response.body.should == "missing parameter: admin_name"
128+
129+
get('/nested_triple', :admin => { :admin_name => 'admin', :super => {:user => {:first_name => "Billy"}}})
130+
last_response.status.should == 400
131+
last_response.body.should == "missing parameter: last_name"
132+
133+
get('/nested_triple', :admin => { :admin_name => 'admin', :super => {:user => {:first_name => "Billy", :last_name => "Bob"}}})
134+
last_response.status.should == 200
135+
last_response.body.should == "Nested triple"
136+
end
137+
71138
end

0 commit comments

Comments
 (0)