Skip to content
Open
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 @@ -12,6 +12,7 @@
* [#2782](https://github.com/ruby-grape/grape/pull/2782): Remove the dead `format` keyword from `Grape::Router::Pattern` - [@ericproulx](https://github.com/ericproulx).
* [#2783](https://github.com/ruby-grape/grape/pull/2783): Read a route's `version`, `anchor` and `requirements` from its pattern instead of storing them again on the route - [@ericproulx](https://github.com/ericproulx).
* [#2784](https://github.com/ruby-grape/grape/pull/2784): Move HEAD route creation into `Grape::Router::Route#to_head` - [@ericproulx](https://github.com/ericproulx).
* [#2788](https://github.com/ruby-grape/grape/pull/2788): Compare the base API instead of `to_s` when refreshing a mounted app - [@ericproulx](https://github.com/ericproulx).
* Your contribution here.

#### Fixes
Expand Down
2 changes: 2 additions & 0 deletions lib/grape/api/instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class << self

def_delegators :@base, :to_s

attr_reader :base

def base=(grape_api)
@base = grape_api
grape_api.instances << self
Expand Down
14 changes: 13 additions & 1 deletion lib/grape/dsl/routing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def mount(mounts, *opts)
refresh_already_mounted = opts.any? ? opts.first[:refresh_already_mounted] : false
if refresh_already_mounted && !endpoints.empty?
endpoints.delete_if do |endpoint|
endpoint.mounted_app.to_s == app.to_s
same_mounted_app?(endpoint.mounted_app, app)
end
end

Expand Down Expand Up @@ -274,6 +274,18 @@ def refresh_mounted_api(mounts, *opts)
mount(mounts, *opts)
end

# Two mounts refer to the same app when they share the same base Grape
# API. +mount+ turns every mounted Grape API into a throwaway
# +mount_instance+ (a fresh +Class.new+ per mount), so object identity
# never holds across mounts; comparing the base is the real signal.
# Plain Rack apps have no base and are mounted as-is, so they fall back
# to object identity.
def same_mounted_app?(mounted, app)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have app in scope here? In which case it could be called mounted_in?(mounted_app)?

return mounted.base.equal?(app.base) if mounted.respond_to?(:base) && app.respond_to?(:base)

mounted.equal?(app)
end

# Execute first the provided block, then each of the
# block passed in. Allows for simple 'before' setups
# of settings stack pushes.
Expand Down
12 changes: 12 additions & 0 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3610,6 +3610,18 @@ def self.call(object, _env)
expect { subject.mount app => '/thing' }
.to change { subject.endpoints.count }.by(1)
end

it 'does not confuse two distinct apps that share a name' do
other = Class.new(described_class) { get('/y') { 'y' } }
# Force a #to_s collision to prove the refresh keys off the base API
# identity rather than the string representation.
allow(app).to receive(:to_s).and_return('SameName')
allow(other).to receive(:to_s).and_return('SameName')

subject.mount app => '/thing'
expect { subject.mount({ other => '/thing' }, refresh_already_mounted: true) }
.to change { subject.endpoints.count }.by(1)
end
end

context 'mounting an API' do
Expand Down
Loading