diff --git a/CHANGELOG.md b/CHANGELOG.md index eda402379..474659eb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ * [#2819](https://github.com/ruby-grape/grape/pull/2819): Freeze coercers at construction, synchronize `Grape::Util::Cache` lookups, and assign `Route#regexp_capture_index` eagerly - [@ericproulx](https://github.com/ericproulx). * [#2824](https://github.com/ruby-grape/grape/pull/2824): Fix cascaded routes (`X-Cascade: pass`) leaking `route_info` and path captures into the next matched route's `route` and `params` - [@ericproulx](https://github.com/ericproulx). * [#2825](https://github.com/ruby-grape/grape/pull/2825): Stop `present` entity autodetection from picking up a top-level `::Entity` constant - [@ericproulx](https://github.com/ericproulx). +* [#2827](https://github.com/ruby-grape/grape/pull/2827): Make the `cascade` DSL getter return the configured value (`cascade false` read back as `true`) - [@ericproulx](https://github.com/ericproulx). * Your contribution here. ### 3.3.4 (2026-07-25) diff --git a/UPGRADING.md b/UPGRADING.md index 916cfe5ed..ca6031d40 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,10 @@ Upgrading Grape ### Upgrading to >= 4.0.0 +#### The `cascade` getter returns the configured value + +Calling `cascade` with no argument used to report whether cascading had been *configured at all* (`cascade false` still read back as `true`, contradicting the actual runtime behavior, which was correctly disabled). It now returns the configured value itself — `true`/`false` as set, or `true` when never set. Code that used the getter to detect "was `cascade` called" rather than "does this API cascade" must track that separately. + #### `forward_match` is no longer exposed on routes `forward_match` is an internal, construction-time detail that decides how a route matches incoming paths (a prefix match for mounted Rack apps, the compiled pattern otherwise). It is now passed to `Grape::Router::Route` as a keyword argument and derived from the mounted app instead of being carried in the route's options. diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index cd478c369..b732d33c6 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -16,7 +16,7 @@ def mounted(&block) end def cascade(value = nil) - return inheritable_setting.cascade_defined? ? !inheritable_setting.cascade.nil? : true if value.nil? + return inheritable_setting.cascade_defined? ? inheritable_setting.cascade : true if value.nil? inheritable_setting.cascade = value end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index b990ca83e..5e6ffe449 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -4890,12 +4890,33 @@ def uniqe_id_route describe '.cascade' do subject { api.cascade } - let(:api) do - Class.new(Grape::API) do - cascade true + context 'when not set' do + let(:api) { Class.new(described_class) } + + it { is_expected.to be(true) } + end + + context 'when set to true' do + let(:api) do + Class.new(Grape::API) do + cascade true + end end + + it { is_expected.to be(true) } end - it { is_expected.to be(true) } + # Regression: the getter returned whether cascade had been set at all + # (!value.nil?), so `cascade false` still read back as true — contradicting + # the runtime cascading behavior, which was correctly disabled. + context 'when set to false' do + let(:api) do + Class.new(Grape::API) do + cascade false + end + end + + it { is_expected.to be(false) } + end end end