Skip to content

Commit 192a2a2

Browse files
authored
Fixes redundant dependency check and adds a vrp benchmark (#2096)
1 parent fe6a4a4 commit 192a2a2

File tree

5 files changed

+275
-1
lines changed

5 files changed

+275
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* [#2092](https://github.com/ruby-grape/grape/pull/2092): Correct an example params in Include Missing doc - [@huyvohcmc](https://github.com/huyvohcmc).
1313
* [#2091](https://github.com/ruby-grape/grape/pull/2091): Fix ruby 2.7 keyword deprecations - [@dim](https://github.com/dim).
1414
* [#2097](https://github.com/ruby-grape/grape/pull/2097): Skip to set default value unless `meets_dependency?` - [@wanabe](https://github.com/wanabe).
15+
* [#2096](https://github.com/ruby-grape/grape/pull/2096): Fix redundant dependency check - [@braktar](https://github.com/braktar).
1516

1617
### 1.4.0 (2020/07/10)
1718

benchmark/large_model.rb

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
# frozen_string_literal: true
2+
3+
# gem 'grape', '=1.0.1'
4+
5+
require 'grape'
6+
require 'ruby-prof'
7+
require 'hashie'
8+
9+
class API < Grape::API
10+
# include Grape::Extensions::Hash::ParamBuilder
11+
# include Grape::Extensions::Hashie::Mash::ParamBuilder
12+
13+
rescue_from do |e|
14+
warn "\n\n#{e.class} (#{e.message}):\n " + e.backtrace.join("\n ") + "\n\n"
15+
end
16+
17+
prefix :api
18+
version 'v1', using: :path
19+
content_type :json, 'application/json; charset=UTF-8'
20+
default_format :json
21+
22+
def self.vrp_request_timewindow(this)
23+
this.optional(:id, types: String)
24+
this.optional(:start, types: [String, Float, Integer])
25+
this.optional(:end, types: [String, Float, Integer])
26+
this.optional(:day_index, type: Integer, values: 0..6)
27+
this.at_least_one_of :start, :end, :day_index
28+
end
29+
30+
def self.vrp_request_indice_range(this)
31+
this.optional(:start, type: Integer)
32+
this.optional(:end, type: Integer)
33+
end
34+
35+
def self.vrp_request_point(this)
36+
this.requires(:id, type: String, allow_blank: false)
37+
this.optional(:location, type: Hash, allow_blank: false) do
38+
requires(:lat, type: Float, allow_blank: false)
39+
requires(:lon, type: Float, allow_blank: false)
40+
end
41+
end
42+
43+
def self.vrp_request_unit(this)
44+
this.requires(:id, type: String, allow_blank: false)
45+
this.optional(:label, type: String)
46+
this.optional(:counting, type: Boolean)
47+
end
48+
49+
def self.vrp_request_activity(this)
50+
this.optional(:duration, types: [String, Float, Integer])
51+
this.optional(:additional_value, type: Integer)
52+
this.optional(:setup_duration, types: [String, Float, Integer])
53+
this.optional(:late_multiplier, type: Float)
54+
this.optional(:timewindow_start_day_shift_number, documentation: { hidden: true }, type: Integer)
55+
this.requires(:point_id, type: String, allow_blank: false)
56+
this.optional(:timewindows, type: Array) do
57+
API.vrp_request_timewindow(self)
58+
end
59+
end
60+
61+
def self.vrp_request_quantity(this)
62+
this.optional(:id, type: String)
63+
this.requires(:unit_id, type: String, allow_blank: false)
64+
this.optional(:value, type: Float)
65+
end
66+
67+
def self.vrp_request_capacity(this)
68+
this.optional(:id, type: String)
69+
this.requires(:unit_id, type: String, allow_blank: false)
70+
this.requires(:limit, type: Float, allow_blank: false)
71+
this.optional(:initial, type: Float)
72+
this.optional(:overload_multiplier, type: Float)
73+
end
74+
75+
def self.vrp_request_vehicle(this)
76+
this.requires(:id, type: String, allow_blank: false)
77+
this.optional(:cost_fixed, type: Float)
78+
this.optional(:cost_distance_multiplier, type: Float)
79+
this.optional(:cost_time_multiplier, type: Float)
80+
81+
this.optional :router_dimension, type: String, values: %w[time distance]
82+
this.optional(:skills, type: Array[Array[String]])
83+
84+
this.optional(:unavailable_work_day_indices, type: Array[Integer])
85+
86+
this.optional(:free_approach, type: Boolean)
87+
this.optional(:free_return, type: Boolean)
88+
89+
this.optional(:start_point_id, type: String)
90+
this.optional(:end_point_id, type: String)
91+
this.optional(:capacities, type: Array) do
92+
API.vrp_request_capacity(self)
93+
end
94+
95+
this.optional(:sequence_timewindows, type: Array) do
96+
API.vrp_request_timewindow(self)
97+
end
98+
end
99+
100+
def self.vrp_request_service(this)
101+
this.requires(:id, type: String, allow_blank: false)
102+
this.optional(:priority, type: Integer, values: 0..8)
103+
this.optional(:exclusion_cost, type: Integer)
104+
105+
this.optional(:visits_number, type: Integer, coerce_with: ->(val) { val.to_i.positive? && val.to_i }, default: 1, allow_blank: false)
106+
107+
this.optional(:unavailable_visit_indices, type: Array[Integer])
108+
this.optional(:unavailable_visit_day_indices, type: Array[Integer])
109+
110+
this.optional(:minimum_lapse, type: Float)
111+
this.optional(:maximum_lapse, type: Float)
112+
113+
this.optional(:sticky_vehicle_ids, type: Array[String])
114+
this.optional(:skills, type: Array[String])
115+
116+
this.optional(:type, type: Symbol)
117+
this.optional(:activity, type: Hash) do
118+
API.vrp_request_activity(self)
119+
end
120+
this.optional(:quantities, type: Array) do
121+
API.vrp_request_quantity(self)
122+
end
123+
end
124+
125+
def self.vrp_request_configuration(this)
126+
this.optional(:preprocessing, type: Hash) do
127+
API.vrp_request_preprocessing(self)
128+
end
129+
this.optional(:resolution, type: Hash) do
130+
API.vrp_request_resolution(self)
131+
end
132+
this.optional(:restitution, type: Hash) do
133+
API.vrp_request_restitution(self)
134+
end
135+
this.optional(:schedule, type: Hash) do
136+
API.vrp_request_schedule(self)
137+
end
138+
end
139+
140+
def self.vrp_request_partition(this)
141+
this.requires(:method, type: String, values: %w[hierarchical_tree balanced_kmeans])
142+
this.optional(:metric, type: Symbol)
143+
this.optional(:entity, type: Symbol, values: %i[vehicle work_day], coerce_with: ->(value) { value.to_sym })
144+
this.optional(:threshold, type: Integer)
145+
end
146+
147+
def self.vrp_request_preprocessing(this)
148+
this.optional(:max_split_size, type: Integer)
149+
this.optional(:partition_method, type: String, documentation: { hidden: true })
150+
this.optional(:partition_metric, type: Symbol, documentation: { hidden: true })
151+
this.optional(:kmeans_centroids, type: Array[Integer])
152+
this.optional(:cluster_threshold, type: Float)
153+
this.optional(:force_cluster, type: Boolean)
154+
this.optional(:prefer_short_segment, type: Boolean)
155+
this.optional(:neighbourhood_size, type: Integer)
156+
this.optional(:partitions, type: Array) do
157+
API.vrp_request_partition(self)
158+
end
159+
this.optional(:first_solution_strategy, type: Array[String])
160+
end
161+
162+
def self.vrp_request_resolution(this)
163+
this.optional(:duration, type: Integer, allow_blank: false)
164+
this.optional(:iterations, type: Integer, allow_blank: false)
165+
this.optional(:iterations_without_improvment, type: Integer, allow_blank: false)
166+
this.optional(:stable_iterations, type: Integer, allow_blank: false)
167+
this.optional(:stable_coefficient, type: Float, allow_blank: false)
168+
this.optional(:initial_time_out, type: Integer, allow_blank: false, documentation: { hidden: true })
169+
this.optional(:minimum_duration, type: Integer, allow_blank: false)
170+
this.optional(:time_out_multiplier, type: Integer)
171+
this.optional(:vehicle_limit, type: Integer)
172+
this.optional(:solver_parameter, type: Integer, documentation: { hidden: true })
173+
this.optional(:solver, type: Boolean, default: true)
174+
this.optional(:same_point_day, type: Boolean)
175+
this.optional(:allow_partial_assignment, type: Boolean, default: true)
176+
this.optional(:split_number, type: Integer)
177+
this.optional(:evaluate_only, type: Boolean)
178+
this.optional(:several_solutions, type: Integer, allow_blank: false, default: 1)
179+
this.optional(:batch_heuristic, type: Boolean, default: false)
180+
this.optional(:variation_ratio, type: Integer)
181+
this.optional(:repetition, type: Integer, documentation: { hidden: true })
182+
this.at_least_one_of :duration, :iterations, :iterations_without_improvment, :stable_iterations, :stable_coefficient, :initial_time_out, :minimum_duration
183+
this.mutually_exclusive :initial_time_out, :minimum_duration
184+
end
185+
186+
def self.vrp_request_restitution(this)
187+
this.optional(:geometry, type: Boolean)
188+
this.optional(:geometry_polyline, type: Boolean)
189+
this.optional(:intermediate_solutions, type: Boolean)
190+
this.optional(:csv, type: Boolean)
191+
this.optional(:allow_empty_result, type: Boolean)
192+
end
193+
194+
def self.vrp_request_schedule(this)
195+
this.optional(:range_indices, type: Hash) do
196+
API.vrp_request_indice_range(self)
197+
end
198+
this.optional(:unavailable_indices, type: Array[Integer])
199+
end
200+
201+
params do
202+
optional(:vrp, type: Hash, documentation: { param_type: 'body' }) do
203+
optional(:name, type: String)
204+
205+
optional(:points, type: Array) do
206+
API.vrp_request_point(self)
207+
end
208+
209+
optional(:units, type: Array) do
210+
API.vrp_request_unit(self)
211+
end
212+
213+
requires(:vehicles, type: Array) do
214+
API.vrp_request_vehicle(self)
215+
end
216+
217+
optional(:services, type: Array, allow_blank: false) do
218+
API.vrp_request_service(self)
219+
end
220+
221+
optional(:configuration, type: Hash) do
222+
API.vrp_request_configuration(self)
223+
end
224+
end
225+
end
226+
post '/' do
227+
'hello'
228+
end
229+
end
230+
puts Grape::VERSION
231+
232+
options = {
233+
method: 'POST',
234+
params: JSON.parse(File.read('benchmark/resource/vrp_example.json'))
235+
}
236+
237+
env = Rack::MockRequest.env_for('/api/v1', options)
238+
239+
start = Time.now
240+
result = RubyProf.profile do
241+
API.call env
242+
end
243+
puts Time.now - start
244+
printer = RubyProf::FlatPrinter.new(result)
245+
File.open('test_prof.out', 'w+') { |f| printer.print(f, {}) }

benchmark/resource/vrp_example.json

+1
Large diffs are not rendered by default.

lib/grape/validations/params_scope.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@ def should_validate?(parameters)
5454
end
5555

5656
def meets_dependency?(params, request_params)
57+
return true unless @dependent_on
58+
5759
if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
5860
return false
5961
end
6062

61-
return true unless @dependent_on
6263
return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
6364
return false unless params.respond_to?(:with_indifferent_access)
6465
params = params.with_indifferent_access

spec/grape/validations/params_scope_spec.rb

+26
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,32 @@ def initialize(value)
633633
expect(last_response.status).to eq(200)
634634
end
635635

636+
it 'detect unmet nested dependency' do
637+
subject.params do
638+
requires :a, type: String, allow_blank: false, values: %w[x y z]
639+
given a: ->(val) { val == 'z' } do
640+
requires :inner3, type: Array, allow_blank: false do
641+
requires :bar, type: String, allow_blank: false
642+
given bar: ->(val) { val == 'b' } do
643+
requires :baz, type: Array do
644+
optional :baz_category, type: String
645+
end
646+
end
647+
given bar: ->(val) { val == 'c' } do
648+
requires :baz, type: Array do
649+
requires :baz_category, type: String
650+
end
651+
end
652+
end
653+
end
654+
end
655+
subject.get('/nested-dependency') { declared(params).to_json }
656+
657+
get '/nested-dependency', a: 'z', inner3: [{ bar: 'c', baz: [{ unrelated: 'nope' }] }]
658+
expect(last_response.status).to eq(400)
659+
expect(last_response.body).to eq 'inner3[0][baz][0][baz_category] is missing'
660+
end
661+
636662
it 'includes the parameter within #declared(params)' do
637663
get '/test', a: true, b: true
638664

0 commit comments

Comments
 (0)