Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/dsl/routing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 25 additions & 4 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading