Skip to content

Commit 86648fa

Browse files
authored
Optimize hash alloc (#2512)
* Remove double splat operators when not needed including specs Grape::Validations::Validators::Base last arguments is now just a simple param Grape::Exceptions::Validation and Grape::Exceptions::ValidationErrors does not end with **args. Grape::Request has a named param build_params_with: nil instead of **options Remove @namespace_description in routing.rb * Revert compile_many_routes.rb * Optimize Head Route * Use dup instead of new for Head Route * Use opts = {} for validator instead of just opts rubocop * Optimize add_head_not_allowed_methods_and_options_methods Renamed to add_head_and_options_methods * Remove requirements and path from greedy route (not used) allow header is joined * Remove {} for opts since its internal. * Fix rubocop * Remove frozen allow_header. Now Dynamic * Update CHANGELOG.md
1 parent 0477baf commit 86648fa

38 files changed

+214
-278
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* [#2500](https://github.com/ruby-grape/grape/pull/2500): Remove deprecated `file` method - [@ericproulx](https://github.com/ericproulx).
77
* [#2501](https://github.com/ruby-grape/grape/pull/2501): Remove deprecated `except` and `proc` options in values validator - [@ericproulx](https://github.com/ericproulx).
88
* [#2502](https://github.com/ruby-grape/grape/pull/2502): Remove deprecation `options` in `desc` - [@ericproulx](https://github.com/ericproulx).
9+
* [#2512](https://github.com/ruby-grape/grape/pull/2512): Optimize hash alloc - [@ericproulx](https://github.com/ericproulx).
910
* Your contribution here.
1011

1112
#### Fixes

lib/grape/api.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def const_missing(*args)
9191
# For instance, a description could be done using: `desc configuration[:description]` if it may vary
9292
# depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
9393
# too much, you may actually want to provide a new API rather than remount it.
94-
def mount_instance(**opts)
94+
def mount_instance(opts = {})
9595
instance = Class.new(@base_parent)
9696
instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
9797
instance.base = self

lib/grape/api/instance.rb

+22-58
Original file line numberDiff line numberDiff line change
@@ -194,88 +194,52 @@ def cascade?
194194
# will return an HTTP 405 response for any HTTP method that the resource
195195
# cannot handle.
196196
def add_head_not_allowed_methods_and_options_methods
197-
versioned_route_configs = collect_route_config_per_pattern
198197
# The paths we collected are prepared (cf. Path#prepare), so they
199198
# contain already versioning information when using path versioning.
199+
all_routes = self.class.endpoints.map(&:routes).flatten
200+
200201
# Disable versioning so adding a route won't prepend versioning
201202
# informations again.
202-
without_root_prefix do
203-
without_versioning do
204-
versioned_route_configs.each do |config|
205-
next if config[:options][:matching_wildchar]
206-
207-
allowed_methods = config[:methods].dup
208-
209-
allowed_methods |= [Rack::HEAD] if !self.class.namespace_inheritable(:do_not_route_head) && allowed_methods.include?(Rack::GET)
210-
211-
allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Rack::OPTIONS] | allowed_methods)
212-
213-
config[:endpoint].options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Rack::OPTIONS)
214-
215-
attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
216-
generate_not_allowed_method(config[:pattern], **attributes)
217-
end
218-
end
219-
end
203+
without_root_prefix_and_versioning { collect_route_config_per_pattern(all_routes) }
220204
end
221205

222-
def collect_route_config_per_pattern
223-
all_routes = self.class.endpoints.map(&:routes).flatten
206+
def collect_route_config_per_pattern(all_routes)
224207
routes_by_regexp = all_routes.group_by(&:pattern_regexp)
225208

226209
# Build the configuration based on the first endpoint and the collection of methods supported.
227-
routes_by_regexp.values.map do |routes|
228-
last_route = routes.last # Most of the configuration is taken from the last endpoint
229-
matching_wildchar = routes.any? { |route| route.request_method == '*' }
230-
{
231-
options: { matching_wildchar: matching_wildchar },
232-
pattern: last_route.pattern,
233-
requirements: last_route.requirements,
234-
path: last_route.origin,
235-
endpoint: last_route.app,
236-
methods: matching_wildchar ? Grape::Http::Headers::SUPPORTED_METHODS : routes.map(&:request_method)
237-
}
238-
end
239-
end
210+
routes_by_regexp.each_value do |routes|
211+
last_route = routes.last # Most of the configuration is taken from the last endpoint
212+
next if routes.any? { |route| route.request_method == '*' }
240213

241-
# Generate a route that returns an HTTP 405 response for a user defined
242-
# path on methods not specified
243-
def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
244-
supported_methods =
245-
if self.class.namespace_inheritable(:do_not_route_options)
246-
Grape::Http::Headers::SUPPORTED_METHODS
247-
else
248-
Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
249-
end
250-
not_allowed_methods = supported_methods - allowed_methods
251-
@router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
214+
allowed_methods = routes.map(&:request_method)
215+
allowed_methods |= [Rack::HEAD] if !self.class.namespace_inheritable(:do_not_route_head) && allowed_methods.include?(Rack::GET)
216+
217+
allow_header = self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Rack::OPTIONS] | allowed_methods
218+
last_route.app.options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Rack::OPTIONS)
219+
220+
@router.associate_routes(last_route.pattern, {
221+
endpoint: last_route.app,
222+
allow_header: allow_header
223+
})
224+
end
252225
end
253226

254227
# Allows definition of endpoints that ignore the versioning configuration
255228
# used by the rest of your API.
256-
def without_versioning(&_block)
229+
def without_root_prefix_and_versioning
257230
old_version = self.class.namespace_inheritable(:version)
258231
old_version_options = self.class.namespace_inheritable(:version_options)
232+
old_root_prefix = self.class.namespace_inheritable(:root_prefix)
259233

260234
self.class.namespace_inheritable_to_nil(:version)
261235
self.class.namespace_inheritable_to_nil(:version_options)
236+
self.class.namespace_inheritable_to_nil(:root_prefix)
262237

263238
yield
264239

265240
self.class.namespace_inheritable(:version, old_version)
266241
self.class.namespace_inheritable(:version_options, old_version_options)
267-
end
268-
269-
# Allows definition of endpoints that ignore the root prefix used by the
270-
# rest of your API.
271-
def without_root_prefix(&_block)
272-
old_prefix = self.class.namespace_inheritable(:root_prefix)
273-
274-
self.class.namespace_inheritable_to_nil(:root_prefix)
275-
276-
yield
277-
278-
self.class.namespace_inheritable(:root_prefix, old_prefix)
242+
self.class.namespace_inheritable(:root_prefix, old_root_prefix)
279243
end
280244
end
281245
end

lib/grape/dsl/inside_route.rb

+7-8
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ def self.post_filter_methods(type)
2626
# has completed
2727
module PostBeforeFilter
2828
def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
29-
options = options.reverse_merge(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
30-
declared_params ||= optioned_declared_params(**options)
29+
options.reverse_merge!(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
30+
declared_params ||= optioned_declared_params(options[:include_parent_namespaces])
3131

3232
res = if passed_params.is_a?(Array)
3333
declared_array(passed_params, options, declared_params, params_nested_path)
@@ -120,8 +120,8 @@ def optioned_param_key(declared_param, options)
120120
options[:stringify] ? declared_param.to_s : declared_param.to_sym
121121
end
122122

123-
def optioned_declared_params(**options)
124-
declared_params = if options[:include_parent_namespaces]
123+
def optioned_declared_params(include_parent_namespaces)
124+
declared_params = if include_parent_namespaces
125125
# Declared params including parent namespaces
126126
route_setting(:declared_params)
127127
else
@@ -199,10 +199,9 @@ def rack_response(message, status = 200, headers = { Rack::CONTENT_TYPE => conte
199199
# Redirect to a new url.
200200
#
201201
# @param url [String] The url to be redirect.
202-
# @param options [Hash] The options used when redirect.
203-
# :permanent, default false.
204-
# :body, default a short message including the URL.
205-
def redirect(url, permanent: false, body: nil, **_options)
202+
# @param permanent [Boolean] default false.
203+
# @param body default a short message including the URL.
204+
def redirect(url, permanent: false, body: nil)
206205
body_message = body
207206
if permanent
208207
status 301

lib/grape/dsl/parameters.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def requires(*attrs, &block)
136136
require_required_and_optional_fields(attrs.first, opts)
137137
else
138138
validate_attributes(attrs, opts, &block)
139-
block ? new_scope(orig_attrs, &block) : push_declared_params(attrs, **opts.slice(:as))
139+
block ? new_scope(orig_attrs, &block) : push_declared_params(attrs, opts.slice(:as))
140140
end
141141
end
142142

@@ -162,7 +162,7 @@ def optional(*attrs, &block)
162162
else
163163
validate_attributes(attrs, opts, &block)
164164

165-
block ? new_scope(orig_attrs, true, &block) : push_declared_params(attrs, **opts.slice(:as))
165+
block ? new_scope(orig_attrs, true, &block) : push_declared_params(attrs, opts.slice(:as))
166166
end
167167
end
168168

lib/grape/dsl/routing.rb

+5-12
Original file line numberDiff line numberDiff line change
@@ -175,19 +175,12 @@ def route(methods, paths = ['/'], route_options = {}, &block)
175175
# end
176176
# end
177177
def namespace(space = nil, options = {}, &block)
178-
@namespace_description = nil unless instance_variable_defined?(:@namespace_description) && @namespace_description
179-
180-
if space || block
181-
within_namespace do
182-
previous_namespace_description = @namespace_description
183-
@namespace_description = (@namespace_description || {}).deep_merge(namespace_setting(:description) || {})
184-
nest(block) do
185-
namespace_stackable(:namespace, Namespace.new(space, **options)) if space
186-
end
187-
@namespace_description = previous_namespace_description
178+
return Namespace.joined_space_path(namespace_stackable(:namespace)) unless space || block
179+
180+
within_namespace do
181+
nest(block) do
182+
namespace_stackable(:namespace, Namespace.new(space, options)) if space
188183
end
189-
else
190-
Namespace.joined_space_path(namespace_stackable(:namespace))
191184
end
192185
end
193186

lib/grape/endpoint.rb

+70-73
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,16 @@ def reset_routes!
145145
end
146146

147147
def mount_in(router)
148-
if endpoints
149-
endpoints.each { |e| e.mount_in(router) }
150-
else
151-
reset_routes!
152-
routes.each do |route|
153-
methods = [route.request_method]
154-
methods << Rack::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
155-
methods.each do |method|
156-
route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h) unless route.request_method == method
157-
router.append(route.apply(self))
158-
end
148+
return endpoints.each { |e| e.mount_in(router) } if endpoints
149+
150+
reset_routes!
151+
routes.each do |route|
152+
router.append(route.apply(self))
153+
next unless !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
154+
155+
route.dup.then do |head_route|
156+
head_route.convert_to_head_request!
157+
router.append(head_route.apply(self))
159158
end
160159
end
161160
end
@@ -164,8 +163,9 @@ def to_routes
164163
route_options = prepare_default_route_attributes
165164
map_routes do |method, path|
166165
path = prepare_path(path)
167-
params = merge_route_options(**route_options.merge(suffix: path.suffix))
168-
route = Router::Route.new(method, path.path, **params)
166+
route_options[:suffix] = path.suffix
167+
params = options[:route_options].merge(route_options)
168+
route = Grape::Router::Route.new(method, path.path, params)
169169
route.apply(self)
170170
end.flatten
171171
end
@@ -196,10 +196,6 @@ def prepare_version
196196
version.length == 1 ? version.first : version
197197
end
198198

199-
def merge_route_options(**default)
200-
options[:route_options].clone.merge!(**default)
201-
end
202-
203199
def map_routes
204200
options[:method].map { |method| options[:path].map { |path| yield method, path } }
205201
end
@@ -259,9 +255,10 @@ def run
259255
run_filters befores, :before
260256

261257
if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
262-
raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options?
258+
allow_header_value = allowed_methods.join(', ')
259+
raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allow_header_value)) unless options?
263260

264-
header Grape::Http::Headers::ALLOW, allowed_methods
261+
header Grape::Http::Headers::ALLOW, allow_header_value
265262
response_object = ''
266263
status 204
267264
else
@@ -287,59 +284,6 @@ def run
287284
end
288285
end
289286

290-
def build_stack(helpers)
291-
stack = Grape::Middleware::Stack.new
292-
293-
content_types = namespace_stackable_with_hash(:content_types)
294-
format = namespace_inheritable(:format)
295-
296-
stack.use Rack::Head
297-
stack.use Class.new(Grape::Middleware::Error),
298-
helpers: helpers,
299-
format: format,
300-
content_types: content_types,
301-
default_status: namespace_inheritable(:default_error_status),
302-
rescue_all: namespace_inheritable(:rescue_all),
303-
rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
304-
default_error_formatter: namespace_inheritable(:default_error_formatter),
305-
error_formatters: namespace_stackable_with_hash(:error_formatters),
306-
rescue_options: namespace_stackable_with_hash(:rescue_options),
307-
rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers),
308-
base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers),
309-
all_rescue_handler: namespace_inheritable(:all_rescue_handler),
310-
grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
311-
312-
stack.concat namespace_stackable(:middleware)
313-
314-
if namespace_inheritable(:version).present?
315-
stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
316-
versions: namespace_inheritable(:version).flatten,
317-
version_options: namespace_inheritable(:version_options),
318-
prefix: namespace_inheritable(:root_prefix),
319-
mount_path: namespace_stackable(:mount_path).first
320-
end
321-
322-
stack.use Grape::Middleware::Formatter,
323-
format: format,
324-
default_format: namespace_inheritable(:default_format) || :txt,
325-
content_types: content_types,
326-
formatters: namespace_stackable_with_hash(:formatters),
327-
parsers: namespace_stackable_with_hash(:parsers)
328-
329-
builder = stack.build
330-
builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
331-
builder.to_app
332-
end
333-
334-
def build_helpers
335-
helpers = namespace_stackable(:helpers)
336-
return if helpers.empty?
337-
338-
Module.new { helpers.each { |mod_to_include| include mod_to_include } }
339-
end
340-
341-
private :build_stack, :build_helpers
342-
343287
def execute
344288
@block&.call(self)
345289
end
@@ -411,13 +355,66 @@ def validations
411355
return enum_for(:validations) unless block_given?
412356

413357
route_setting(:saved_validations)&.each do |saved_validation|
414-
yield Grape::Validations::ValidatorFactory.create_validator(**saved_validation)
358+
yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)
415359
end
416360
end
417361

418362
def options?
419363
options[:options_route_enabled] &&
420364
env[Rack::REQUEST_METHOD] == Rack::OPTIONS
421365
end
366+
367+
private
368+
369+
def build_stack(helpers)
370+
stack = Grape::Middleware::Stack.new
371+
372+
content_types = namespace_stackable_with_hash(:content_types)
373+
format = namespace_inheritable(:format)
374+
375+
stack.use Rack::Head
376+
stack.use Class.new(Grape::Middleware::Error),
377+
helpers: helpers,
378+
format: format,
379+
content_types: content_types,
380+
default_status: namespace_inheritable(:default_error_status),
381+
rescue_all: namespace_inheritable(:rescue_all),
382+
rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
383+
default_error_formatter: namespace_inheritable(:default_error_formatter),
384+
error_formatters: namespace_stackable_with_hash(:error_formatters),
385+
rescue_options: namespace_stackable_with_hash(:rescue_options),
386+
rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers),
387+
base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers),
388+
all_rescue_handler: namespace_inheritable(:all_rescue_handler),
389+
grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
390+
391+
stack.concat namespace_stackable(:middleware)
392+
393+
if namespace_inheritable(:version).present?
394+
stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
395+
versions: namespace_inheritable(:version).flatten,
396+
version_options: namespace_inheritable(:version_options),
397+
prefix: namespace_inheritable(:root_prefix),
398+
mount_path: namespace_stackable(:mount_path).first
399+
end
400+
401+
stack.use Grape::Middleware::Formatter,
402+
format: format,
403+
default_format: namespace_inheritable(:default_format) || :txt,
404+
content_types: content_types,
405+
formatters: namespace_stackable_with_hash(:formatters),
406+
parsers: namespace_stackable_with_hash(:parsers)
407+
408+
builder = stack.build
409+
builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
410+
builder.to_app
411+
end
412+
413+
def build_helpers
414+
helpers = namespace_stackable(:helpers)
415+
return if helpers.empty?
416+
417+
Module.new { helpers.each { |mod_to_include| include mod_to_include } }
418+
end
422419
end
423420
end

0 commit comments

Comments
 (0)