Skip to content
Closed
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 @@ -55,6 +55,7 @@
* [#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).
* [#2829](https://github.com/ruby-grape/grape/pull/2829): Fix a cascading route handing over only to the last route registered for the path, making a middle version (3+ mounted versions with a catch-all) answer 406 - [@ericproulx](https://github.com/ericproulx).
* [#2826](https://github.com/ruby-grape/grape/pull/2826): Fix `api.version` not being set for the root route of a path-versioned API (`GET /v1`) - [@ericproulx](https://github.com/ericproulx).
* [#2848](https://github.com/ruby-grape/grape/pull/2848): Recompile the router when a route is declared after the API has been compiled, so it is served instead of listed by `routes` and answering 404 - [@ericproulx](https://github.com/ericproulx).
* Your contribution here.

### 3.3.5 (2026-07-30)
Expand Down
5 changes: 5 additions & 0 deletions lib/grape/dsl/routing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ def route(methods, paths = ['/'], requirements: nil, anchor: true, **route_optio
)
endpoints << new_endpoint unless endpoints.include?(new_endpoint)

# The router is built once, when the API is first compiled, so an
# endpoint added after that would be listed by #routes and still 404.
# `helpers` and `mount` already invalidate for the same reason.
change!

inheritable_setting.route_end
reset_validations!
end
Expand Down
42 changes: 42 additions & 0 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4120,6 +4120,48 @@ def my_method
end
end

# The router is built once, when the API is first compiled. A route declared
# after that was listed by .routes and still answered 404, so the API's own
# metadata disagreed with what it served.
describe 'a route declared after the API has been compiled' do
before do
subject.format :json
subject.get('/first') { 'first' }
end

it 'is served after a request has already been made' do
get '/first'
subject.get('/second') { 'second' }

get '/second'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('second'.to_json)
end

it 'is served after recognize_path has compiled the router' do
subject.recognize_path('/first')
subject.get('/second') { 'second' }

get '/second'
expect(last_response.status).to eq(200)
end

it 'is recognised by recognize_path' do
get '/first'
subject.get('/second') { 'second' }

expect(subject.recognize_path('/second')).not_to be_nil
end

it 'leaves the routes declared before it serving' do
get '/first'
subject.get('/second') { 'second' }

get '/first'
expect(last_response.body).to eq('first'.to_json)
end
end

describe '.endpoint' do
before do
subject.format :json
Expand Down
4 changes: 4 additions & 0 deletions spec/grape/dsl/routing_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
class << self
attr_reader :instance, :base
attr_accessor :configuration

# Part of the contract Grape::DSL::Routing expects of its host, the
# same way #mount already relies on it.
def change!; end
end
end
end
Expand Down
Loading