@@ -15,13 +15,16 @@ class ParamsScope
15
15
# @option opts :optional [Boolean] whether or not this scope needs to have
16
16
# any parameters set or not
17
17
# @option opts :type [Class] a type meant to govern this scope (deprecated)
18
+ # @option opts :dependent_on [Symbol] if present, this scope should only
19
+ # validate if this param is present in the parent scope
18
20
# @yield the instance context, open for parameter definitions
19
21
def initialize ( opts , &block )
20
- @element = opts [ :element ]
21
- @parent = opts [ :parent ]
22
- @api = opts [ :api ]
23
- @optional = opts [ :optional ] || false
24
- @type = opts [ :type ]
22
+ @element = opts [ :element ]
23
+ @parent = opts [ :parent ]
24
+ @api = opts [ :api ]
25
+ @optional = opts [ :optional ] || false
26
+ @type = opts [ :type ]
27
+ @dependent_on = opts [ :dependent_on ]
25
28
@declared_params = [ ]
26
29
27
30
instance_eval ( &block ) if block_given?
@@ -33,21 +36,45 @@ def initialize(opts, &block)
33
36
# validated
34
37
def should_validate? ( parameters )
35
38
return false if @optional && params ( parameters ) . respond_to? ( :all? ) && params ( parameters ) . all? ( &:blank? )
39
+ return false if @dependent_on && params ( parameters ) . try ( :[] , @dependent_on ) . blank?
36
40
return true if parent . nil?
37
41
parent . should_validate? ( parameters )
38
42
end
39
43
40
44
# @return [String] the proper attribute name, with nesting considered.
41
45
def full_name ( name )
42
- return "#{ @parent . full_name ( @element ) } [#{ name } ]" if @parent
43
- name . to_s
46
+ case
47
+ when nested?
48
+ # Find our containing element's name, and append ours.
49
+ "#{ @parent . full_name ( @element ) } [#{ name } ]"
50
+ when lateral?
51
+ # Find the name of the element as if it was at the
52
+ # same nesting level as our parent.
53
+ @parent . full_name ( name )
54
+ else
55
+ # We must be the root scope, so no prefix needed.
56
+ name . to_s
57
+ end
44
58
end
45
59
46
60
# @return [Boolean] whether or not this scope is the root-level scope
47
61
def root?
48
62
!@parent
49
63
end
50
64
65
+ # A nested scope is contained in one of its parent's elements.
66
+ # @return [Boolean] whether or not this scope is nested
67
+ def nested?
68
+ @parent && @element
69
+ end
70
+
71
+ # A lateral scope is subordinate to its parent, but its keys are at the
72
+ # same level as its parent and thus is not contained within an element.
73
+ # @return [Boolean] whether or not this scope is lateral
74
+ def lateral?
75
+ @parent && !@element
76
+ end
77
+
51
78
# @return [Boolean] whether or not this scope needs to be present, or can
52
79
# be blank
53
80
def required?
@@ -57,7 +84,7 @@ def required?
57
84
protected
58
85
59
86
# Adds a parameter declaration to our list of validations.
60
- # @param attrs [Array] (see Grape::DSL::Parameters#required )
87
+ # @param attrs [Array] (see Grape::DSL::Parameters#requires )
61
88
def push_declared_params ( attrs )
62
89
@declared_params . concat attrs
63
90
end
@@ -98,6 +125,13 @@ def validate_attributes(attrs, opts, &block)
98
125
validates ( attrs , validations )
99
126
end
100
127
128
+ # Returns a new parameter scope, subordinate to the current one and nested
129
+ # under the parameter corresponding to `attrs.first`.
130
+ # @param attrs [Array] the attributes passed to the `requires` or
131
+ # `optional` invocation that opened this scope.
132
+ # @param optional [Boolean] whether the parameter this are nested under
133
+ # is optional or not (and hence, whether this block's params will be).
134
+ # @yield parameter scope
101
135
def new_scope ( attrs , optional = false , &block )
102
136
# if required params are grouped and no type or unsupported type is provided, raise an error
103
137
type = attrs [ 1 ] ? attrs [ 1 ] [ :type ] : nil
@@ -107,12 +141,30 @@ def new_scope(attrs, optional = false, &block)
107
141
end
108
142
109
143
opts = attrs [ 1 ] || { type : Array }
110
- ParamsScope . new ( api : @api , element : attrs . first , parent : self , optional : optional , type : opts [ :type ] , &block )
144
+ self . class . new ( api : @api , element : attrs . first , parent : self , optional : optional , type : opts [ :type ] , &block )
145
+ end
146
+
147
+ # Returns a new parameter scope, not nested under any current-level param
148
+ # but instead at the same level as the current scope.
149
+ # @param options [Hash] options to control how this new scope behaves
150
+ # @option options :dependent_on [Symbol] if given, specifies that this
151
+ # scope should only validate if this parameter from the above scope is
152
+ # present
153
+ # @yield parameter scope
154
+ def new_lateral_scope ( options , &block )
155
+ self . class . new (
156
+ api : @api ,
157
+ element : nil ,
158
+ parent : self ,
159
+ options : @optional ,
160
+ type : Hash ,
161
+ dependent_on : options [ :dependent_on ] ,
162
+ &block )
111
163
end
112
164
113
165
# Pushes declared params to parent or settings
114
166
def configure_declared_params
115
- if @parent
167
+ if nested?
116
168
@parent . push_declared_params [ element => @declared_params ]
117
169
else
118
170
@api . namespace_stackable ( :declared_params , @declared_params )
0 commit comments