diff --git a/CHANGELOG.md b/CHANGELOG.md index 577bb2633..3a876973e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * [#2684](https://github.com/ruby-grape/grape/pull/2684): Readability refactors: case/when, guard clauses, small cleanups - [@ericproulx](https://github.com/ericproulx). * [#2687](https://github.com/ruby-grape/grape/pull/2687): Skip backtrace capture on internal validation exceptions - [@ericproulx](https://github.com/ericproulx). * [#2688](https://github.com/ruby-grape/grape/pull/2688): Consolidate user-registered rescue handler lookup into `Middleware::Error#registered_rescue_handler` backed by a shared `rescue_handler_from` primitive - [@ericproulx](https://github.com/ericproulx). +* [#2689](https://github.com/ruby-grape/grape/pull/2689): Avoid empty-hash merges on request hot paths - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes diff --git a/UPGRADING.md b/UPGRADING.md index dbe32e6d5..c078a175b 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,10 @@ Upgrading Grape ### Upgrading to >= 3.3 +#### `Grape::Request#grape_routing_args` has been removed + +`grape_routing_args` was previously public to support third-party `params_builder` extensions, which have since been removed. With no remaining callers, the method has been removed. If you were calling it externally, read `env[Grape::Env::GRAPE_ROUTING_ARGS]` directly. + #### `endpoint_run_filters.grape` notification no longer fired for empty filter lists `ActiveSupport::Notifications` subscribers listening to `endpoint_run_filters.grape` will no longer receive an event when the filter list for a given phase (`:before`, `:before_validation`, `:after_validation`, `:after`, `:finally`) is empty. Previously every phase emitted an event on every request regardless of whether any filters were registered. If you relied on these events to infer per-phase timing, subscribe to `endpoint_run.grape` (which always fires once per request) or register a no-op filter to keep the phase instrumented. diff --git a/lib/grape/dsl/entity.rb b/lib/grape/dsl/entity.rb index fec11c1a9..320efa9dc 100644 --- a/lib/grape/dsl/entity.rb +++ b/lib/grape/dsl/entity.rb @@ -32,7 +32,7 @@ def present(*args, root: nil, with: nil, **options) representation = { root => representation } if root if key - representation = (body || {}).merge(key => representation) + representation = body&.merge(key => representation) || { key => representation } elsif entity_class.present? && body raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge) diff --git a/lib/grape/request.rb b/lib/grape/request.rb index d0f4be1e2..bdd155cf5 100644 --- a/lib/grape/request.rb +++ b/lib/grape/request.rb @@ -156,16 +156,12 @@ def cookies @cookies ||= Grape::Cookies.new(-> { rack_cookies }) end - # needs to be public until extensions param_builder are removed - def grape_routing_args - # preserve version from query string parameters - env[Grape::Env::GRAPE_ROUTING_ARGS]&.except(:version, :route_info) || {} - end - private def make_params - @params_builder.call(rack_params).deep_merge!(grape_routing_args) + params = @params_builder.call(rack_params) + routing_args = env[Grape::Env::GRAPE_ROUTING_ARGS]&.except(:version, :route_info)&.presence + routing_args ? params.deep_merge!(routing_args) : params rescue *Grape::RACK_ERRORS raise Grape::Exceptions::RequestError end diff --git a/lib/grape/router.rb b/lib/grape/router.rb index 988b5f1d0..b6d31a8d2 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -137,8 +137,7 @@ def transaction(input, method, env) def process_route(route, input, env, include_allow_header: false) args = env[Grape::Env::GRAPE_ROUTING_ARGS] || { route_info: route } route_params = route.params(input) - routing_args = args.merge(route_params || {}) - env[Grape::Env::GRAPE_ROUTING_ARGS] = routing_args + env[Grape::Env::GRAPE_ROUTING_ARGS] = route_params.blank? ? args : args.merge(route_params) env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header if include_allow_header route.call(env) end