Skip to content

Commit fb8dfac

Browse files
committed
Fix #801: Only evaluate values proc lazily.
1 parent 847f65c commit fb8dfac

File tree

7 files changed

+61
-40
lines changed

7 files changed

+61
-40
lines changed

.rubocop_todo.yml

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# This configuration was generated by `rubocop --auto-gen-config`
2-
# on 2014-12-14 14:50:02 -0500 using RuboCop version 0.28.0.
2+
# on 2014-12-16 11:52:50 -0500 using RuboCop version 0.28.0.
33
# The point is for the user to remove these configuration records
44
# one by one as the offenses are removed from the code base.
55
# Note that changes in the inspected code, or installation of new
@@ -10,14 +10,14 @@
1010
Lint/UnusedBlockArgument:
1111
Enabled: false
1212

13-
# Offense count: 25
13+
# Offense count: 26
1414
# Cop supports --auto-correct.
1515
Lint/UnusedMethodArgument:
1616
Enabled: false
1717

1818
# Offense count: 35
1919
Metrics/AbcSize:
20-
Max: 51
20+
Max: 50
2121

2222
# Offense count: 1
2323
Metrics/BlockNesting:
@@ -30,9 +30,9 @@ Metrics/ClassLength:
3030

3131
# Offense count: 15
3232
Metrics/CyclomaticComplexity:
33-
Max: 21
33+
Max: 19
3434

35-
# Offense count: 546
35+
# Offense count: 545
3636
# Configuration parameters: AllowURI, URISchemes.
3737
Metrics/LineLength:
3838
Max: 198

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* [#813](https://github.com/intridea/grape/pull/813): Routing methods dsl refactored to get rid of explicit `paths` parameter - [@AlexYankee](https://github.com/AlexYankee).
1919
* [#826](https://github.com/intridea/grape/pull/826): Find `coerce_type` for `Array` when not specified - [@manovotn](https://github.com/manovotn).
2020
* [#645](https://github.com/intridea/grape/issues/645): Invoking `body false` will return `204 No Content` - [@dblock](https://github.com/dblock).
21-
* [#801](https://github.com/intridea/grape/issues/801): Evaluate permitted parameter `values` lazily on each request when declared as a proc - [@dblock](https://github.com/dblock).
21+
* [#801](https://github.com/intridea/grape/issues/801): Only evaluate permitted parameter `values` and `default` lazily on each request when declared as a proc - [@dblock](https://github.com/dblock).
2222
* [#679](https://github.com/intridea/grape/issues/679): Fixed `OPTIONS` method returning 404 when combined with `prefix`- [@dblock](https://github.com/dblock).
2323
* [#679](https://github.com/intridea/grape/issues/679): Fixed unsupported methods returning 404 instead of 405 when combined with `prefix`- [@dblock](https://github.com/dblock).
2424
* Your contribution here.

README.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ Parameters can be restricted to a specific set of values with the `:values` opti
738738

739739
Default values are eagerly evaluated. Above `:non_random_number` will evaluate to the same
740740
number for each call to the endpoint of this `params` block. To have the default evaluate
741-
at calltime use a lambda, like `:random_number` above.
741+
lazily with each request use a lambda, like `:random_number` above.
742742

743743
```ruby
744744
params do
@@ -747,8 +747,9 @@ params do
747747
end
748748
```
749749

750-
The `:values` option can also be supplied with a `Proc` to be evalutated at runtime for each request. For example, given a status
751-
model you may want to restrict by hashtags that you have previously defined in the `HashTag` model.
750+
The `:values` option can also be supplied with a `Proc`, evaluated lazily with each request.
751+
For example, given a status model you may want to restrict by hashtags that you have
752+
previously defined in the `HashTag` model.
752753

753754
```ruby
754755
params do

UPGRADING.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -191,19 +191,19 @@ See the [the updated API Formats documentation](https://github.com/intridea/grap
191191

192192
#### Changes to Evaluation of Permitted Parameter Values
193193

194-
Permitted parameter values are now evaluated lazily for each request when declared as a proc. The following code would produce the same allowed value in 0.9.0 for each request and a new value since 0.10.0.
194+
Permitted and default parameter values are now only evaluated lazily for each request when declared as a proc. The following code would raise an error at startup time.
195195

196196
```ruby
197197
params do
198-
optional :v, values: -> { [SecureRandom.uuid] }
198+
optional :v, values: -> { [:x, :y] }, default: -> { :z } }
199199
end
200200
```
201201

202202
Remove the proc to get the previous behavior.
203203

204204
```ruby
205205
params do
206-
optional :v, values: [SecureRandom.uuid]
206+
optional :v, values: [:x, :y], default: :z }
207207
end
208208
```
209209

lib/grape/validations/params_scope.rb

+19-10
Original file line numberDiff line numberDiff line change
@@ -104,22 +104,16 @@ def validates(attrs, validations)
104104
default = validations[:default]
105105
doc_attrs[:default] = default if default
106106

107-
default = default.call if default.is_a?(Proc)
108-
109107
values = validations[:values]
110108
doc_attrs[:values] = values if values
111109

112-
evaluated_values = values.is_a?(Proc) ? values.call : values
113-
114-
coerce_type = evaluated_values.first.class if evaluated_values && coerce_type == Array && !evaluated_values.empty?
110+
coerce_type = guess_coerce_type(coerce_type, values)
115111

116-
# default value should be present in values array, if both exist
117-
if default && evaluated_values && !evaluated_values.include?(default)
118-
fail Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, evaluated_values)
119-
end
112+
# default value should be present in values array, if both exist and are not procs
113+
check_incompatible_option_values(values, default)
120114

121115
# type should be compatible with values array, if both exist
122-
validate_value_coercion(coerce_type, evaluated_values) if coerce_type && evaluated_values
116+
validate_value_coercion(coerce_type, values)
123117

124118
doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
125119

@@ -145,6 +139,19 @@ def validates(attrs, validations)
145139
end
146140
end
147141

142+
def guess_coerce_type(coerce_type, values)
143+
return coerce_type if !values || values.is_a?(Proc)
144+
return values.first.class if coerce_type == Array && !values.empty?
145+
coerce_type
146+
end
147+
148+
def check_incompatible_option_values(values, default)
149+
return unless values && default
150+
return if values.is_a?(Proc) || default.is_a?(Proc)
151+
return if values.include?(default)
152+
fail Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
153+
end
154+
148155
def validate(type, options, attrs, doc_attrs)
149156
validator_class = Validations.validators[type.to_s]
150157

@@ -157,6 +164,8 @@ def validate(type, options, attrs, doc_attrs)
157164
end
158165

159166
def validate_value_coercion(coerce_type, values)
167+
return unless coerce_type && values
168+
return if values.is_a?(Proc)
160169
coerce_type = coerce_type.first if coerce_type.kind_of?(Array)
161170
if values.any? { |v| !v.kind_of?(coerce_type) }
162171
fail Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)

spec/grape/validations/params_scope_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def app
5050

5151
it 'raises exception when values are of different type' do
5252
expect do
53-
subject.params { requires :numbers, type: Array, values: -> { [1, 'definitely not a number', 3] } }
53+
subject.params { requires :numbers, type: Array, values: [1, 'definitely not a number', 3] }
5454
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
5555
end
5656
end

spec/grape/validations/validators/values_spec.rb

+28-17
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,6 @@ class API < Grape::API
7676
end
7777
end
7878
get '/optional_with_required_values'
79-
80-
params do
81-
optional :type, type: String, values: -> { [SecureRandom.uuid] }
82-
end
83-
get '/random_values'
8479
end
8580
end
8681
end
@@ -161,14 +156,7 @@ def app
161156
it 'raises IncompatibleOptionValues on an invalid default value from proc' do
162157
subject = Class.new(Grape::API)
163158
expect do
164-
subject.params { optional :type, values: ['valid-type1', 'valid-type2', 'valid-type3'], default: -> { ValuesModel.values.sample + '_invalid' } }
165-
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
166-
end
167-
168-
it 'raises IncompatibleOptionValues on an invalid default value from proc validating against values in a proc' do
169-
subject = Class.new(Grape::API)
170-
expect do
171-
subject.params { optional :type, values: -> { ValuesModel.values }, default: -> { ValuesModel.values.sample + '_invalid' } }
159+
subject.params { optional :type, values: ['valid-type1', 'valid-type2', 'valid-type3'], default: ValuesModel.values.sample + '_invalid' }
172160
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
173161
end
174162

@@ -205,9 +193,32 @@ def app
205193
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
206194
end
207195

208-
it 'evaluates values dynamically with each request' do
209-
allow(SecureRandom).to receive(:uuid).and_return('foo')
210-
get '/random_values', type: 'foo'
211-
expect(last_response.status).to eq 200
196+
context 'with a lambda values' do
197+
subject do
198+
Class.new(Grape::API) do
199+
params do
200+
optional :type, type: String, values: -> { [SecureRandom.uuid] }, default: -> { SecureRandom.uuid }
201+
end
202+
get '/random_values'
203+
end
204+
end
205+
206+
def app
207+
subject
208+
end
209+
210+
before do
211+
expect(SecureRandom).to receive(:uuid).and_return('foo').once
212+
end
213+
214+
it 'only evaluates values dynamically with each request' do
215+
get '/random_values', type: 'foo'
216+
expect(last_response.status).to eq 200
217+
end
218+
219+
it 'chooses default' do
220+
get '/random_values'
221+
expect(last_response.status).to eq 200
222+
end
212223
end
213224
end

0 commit comments

Comments
 (0)