Skip to content

Commit 3a34ca2

Browse files
304dblock
authored andcommitted
Add group attributes for parameter definitions (#1507)
1 parent 2d34f29 commit 3a34ca2

File tree

6 files changed

+235
-3
lines changed

6 files changed

+235
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
Next Release
22
============
33

4-
* Your contribution here.
54
* [#1503](https://github.com/ruby-grape/grape/pull/1503): Allow to use regexp validator with arrays - [@akoltun](https://github.com/akoltun).
5+
* [#1507](https://github.com/ruby-grape/grape/pull/1507): Add group attributes for parameter definitions - [@304](https://github.com/304).
6+
* Your contribution here.
67

78
0.18.0 (10/7/2016)
89
==================

README.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
- [Multiple Allowed Types](#multiple-allowed-types)
3939
- [Validation of Nested Parameters](#validation-of-nested-parameters)
4040
- [Dependent Parameters](#dependent-parameters)
41+
- [Group Options](#group-options)
4142
- [Built-in Validators](#built-in-validators)
4243
- [Namespace Validation and Coercion](#namespace-validation-and-coercion)
4344
- [Custom Validators](#custom-validators)
@@ -1001,6 +1002,35 @@ params do
10011002
end
10021003
```
10031004

1005+
1006+
### Group Options
1007+
1008+
Parameters options can be grouped. It can be useful if you want to extract
1009+
common validation or types for several parameters. The example below presents a
1010+
typical case when parameters share common options.
1011+
1012+
```ruby
1013+
params do
1014+
requires :first_name, type: String, regexp: /w+/, desc: 'First name'
1015+
requires :middle_name, type: String, regexp: /w+/, desc: 'Middle name'
1016+
requires :last_name, type: String, regexp: /w+/, desc: 'Last name'
1017+
end
1018+
```
1019+
1020+
Grape allows you to present the same logic through the `with` method in your
1021+
parameters block, like so:
1022+
1023+
```ruby
1024+
params do
1025+
with(type: String, regexp: /w+/) do
1026+
requires :first_name, desc: 'First name'
1027+
requires :middle_name, desc: 'Middle name'
1028+
requires :last_name, desc: 'Last name'
1029+
end
1030+
end
1031+
```
1032+
1033+
10041034
### Built-in Validators
10051035

10061036
#### `allow_blank`
@@ -1076,8 +1106,8 @@ end
10761106
```
10771107

10781108
Values and except can be combined to define a range of accepted values while not allowing
1079-
certain values within the set. Custom error messages can be defined for both when the parameter
1080-
passed falls within the ```except``` list or when it falls entirely outside the ```value``` list.
1109+
certain values within the set. Custom error messages can be defined for both when the parameter
1110+
passed falls within the ```except``` list or when it falls entirely outside the ```value``` list.
10811111

10821112
```ruby
10831113
params do

lib/grape/dsl/parameters.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def requires(*attrs, &block)
9999

100100
opts = attrs.extract_options!.clone
101101
opts[:presence] = { value: true, message: opts[:message] }
102+
opts = @group.merge(opts) if @group
102103

103104
if opts[:using]
104105
require_required_and_optional_fields(attrs.first, opts)
@@ -118,6 +119,7 @@ def optional(*attrs, &block)
118119

119120
opts = attrs.extract_options!.clone
120121
type = opts[:type]
122+
opts = @group.merge(opts) if @group
121123

122124
# check type for optional parameter group
123125
if attrs && block_given?
@@ -134,6 +136,13 @@ def optional(*attrs, &block)
134136
end
135137
end
136138

139+
# Define common settings for one or more parameters
140+
# @param (see #requires)
141+
# @option (see #requires)
142+
def with(*attrs, &block)
143+
new_group_scope(attrs.clone, &block)
144+
end
145+
137146
# Disallow the given parameters to be present in the same request.
138147
# @param attrs [*Symbol] parameters to validate
139148
def mutually_exclusive(*attrs)

lib/grape/validations/params_scope.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class ParamsScope
1616
# @option opts :optional [Boolean] whether or not this scope needs to have
1717
# any parameters set or not
1818
# @option opts :type [Class] a type meant to govern this scope (deprecated)
19+
# @option opts :type [Hash] group options for this scope
1920
# @option opts :dependent_on [Symbol] if present, this scope should only
2021
# validate if this param is present in the parent scope
2122
# @yield the instance context, open for parameter definitions
@@ -25,6 +26,7 @@ def initialize(opts, &block)
2526
@api = opts[:api]
2627
@optional = opts[:optional] || false
2728
@type = opts[:type]
29+
@group = opts[:group] || {}
2830
@dependent_on = opts[:dependent_on]
2931
@declared_params = []
3032

@@ -189,6 +191,19 @@ def new_lateral_scope(options, &block)
189191
&block)
190192
end
191193

194+
# Returns a new parameter scope, subordinate to the current one and nested
195+
# under the parameter corresponding to `attrs.first`.
196+
# @param attrs [Array] the attributes passed to the `requires` or
197+
# `optional` invocation that opened this scope.
198+
# @yield parameter scope
199+
def new_group_scope(attrs, &block)
200+
self.class.new(
201+
api: @api,
202+
parent: self,
203+
group: attrs.first,
204+
&block)
205+
end
206+
192207
# Pushes declared params to parent or settings
193208
def configure_declared_params
194209
if nested?

spec/grape/dsl/parameters_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ def validates_reader
3131
@validates
3232
end
3333

34+
def new_group_scope(args)
35+
@group = args.clone.first
36+
yield
37+
end
38+
3439
def extract_message_option(attrs)
3540
return nil unless attrs.is_a?(Array)
3641
opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
@@ -92,6 +97,15 @@ def extract_message_option(attrs)
9297
end
9398
end
9499

100+
describe '#with' do
101+
it 'creates a scope with group attributes' do
102+
subject.with(type: Integer) { subject.optional :id, desc: 'Identity.' }
103+
104+
expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.' }])
105+
expect(subject.push_declared_params_reader).to eq([[:id]])
106+
end
107+
end
108+
95109
describe '#mutually_exclusive' do
96110
it 'adds an mutally exclusive parameter validation' do
97111
subject.mutually_exclusive :media, :audio

spec/grape/validations/params_scope_spec.rb

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,4 +624,167 @@ def initialize(value)
624624
end
625625
end
626626
end
627+
628+
context 'when params have group attributes' do
629+
context 'with validations' do
630+
before do
631+
subject.params do
632+
with(allow_blank: false) do
633+
requires :id
634+
optional :name
635+
optional :address, allow_blank: true
636+
end
637+
end
638+
subject.get('test')
639+
end
640+
641+
context 'when data is invalid' do
642+
before do
643+
get 'test', id: '', name: ''
644+
end
645+
646+
it 'returns a validation error' do
647+
expect(last_response.status).to eq(400)
648+
end
649+
650+
it 'applies group validations for every parameter' do
651+
expect(last_response.body).to eq('id is empty, name is empty')
652+
end
653+
end
654+
655+
context 'when parameter has the same validator as a group' do
656+
before do
657+
get 'test', id: 'id', address: ''
658+
end
659+
660+
it 'returns a successful response' do
661+
expect(last_response.status).to eq(200)
662+
end
663+
664+
it 'prioritizes parameter validation over group validation' do
665+
expect(last_response.body).to_not include('address is empty')
666+
end
667+
end
668+
end
669+
670+
context 'with types' do
671+
before do
672+
subject.params do
673+
with(type: Date) do
674+
requires :created_at
675+
end
676+
end
677+
subject.get('test') { params[:created_at] }
678+
end
679+
680+
context 'when invalid date provided' do
681+
before do
682+
get 'test', created_at: 'not_a_date'
683+
end
684+
685+
it 'responds with HTTP error' do
686+
expect(last_response.status).to eq(400)
687+
end
688+
689+
it 'returns a validation error' do
690+
expect(last_response.body).to eq('created_at is invalid')
691+
end
692+
end
693+
694+
context 'when created_at receives a valid date' do
695+
before do
696+
get 'test', created_at: '2016-01-01'
697+
end
698+
699+
it 'returns a successful response' do
700+
expect(last_response.status).to eq(200)
701+
end
702+
703+
it 'returns a date' do
704+
expect(last_response.body).to eq('2016-01-01')
705+
end
706+
end
707+
end
708+
709+
context 'with several group attributes' do
710+
before do
711+
subject.params do
712+
with(values: [1]) do
713+
requires :id, type: Integer
714+
end
715+
716+
with(allow_blank: false) do
717+
optional :address, type: String
718+
end
719+
720+
requires :name
721+
end
722+
subject.get('test')
723+
end
724+
725+
context 'when data is invalid' do
726+
before do
727+
get 'test', id: 2, address: ''
728+
end
729+
730+
it 'responds with HTTP error' do
731+
expect(last_response.status).to eq(400)
732+
end
733+
734+
it 'returns a validation error' do
735+
expect(last_response.body).to eq('id does not have a valid value, address is empty, name is missing')
736+
end
737+
end
738+
739+
context 'when correct data is provided' do
740+
before do
741+
get 'test', id: 1, address: 'Some street', name: 'John'
742+
end
743+
744+
it 'returns a successful response' do
745+
expect(last_response.status).to eq(200)
746+
end
747+
end
748+
end
749+
750+
context 'with nested groups' do
751+
before do
752+
subject.params do
753+
with(type: Integer) do
754+
requires :id
755+
756+
with(type: Date) do
757+
requires :created_at
758+
optional :updated_at
759+
end
760+
end
761+
end
762+
subject.get('test')
763+
end
764+
765+
context 'when data is invalid' do
766+
before do
767+
get 'test', id: 'wrong', created_at: 'not_a_date', updated_at: '2016-01-01'
768+
end
769+
770+
it 'responds with HTTP error' do
771+
expect(last_response.status).to eq(400)
772+
end
773+
774+
it 'returns a validation error' do
775+
expect(last_response.body).to eq('id is invalid, created_at is invalid')
776+
end
777+
end
778+
779+
context 'when correct data is provided' do
780+
before do
781+
get 'test', id: 1, created_at: '2016-01-01'
782+
end
783+
784+
it 'returns a successful response' do
785+
expect(last_response.status).to eq(200)
786+
end
787+
end
788+
end
789+
end
627790
end

0 commit comments

Comments
 (0)