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
20 changes: 9 additions & 11 deletions core/lib/workarea/mount_point.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@ module Workarea
module MountPoint
mattr_accessor :cache

# Traverse the app-delegation chain to find the underlying app class.
# In Rails 7, mounted engines are wrapped in one or more
# +ActionDispatch::Routing::Mapper::Constraints+ layers. Plain routes use
# +ActionDispatch::Routing::RouteSet::Dispatcher+, which does not respond to
# +#app+, so we must guard before each step.
# Traverse Rack app-delegation wrappers (Constraints layers added by Rails router)
# stopping as soon as we reach a Class (engine classes are Classes) or something
# that does not respond to :app. A depth ceiling prevents infinite loops.
#
# A depth ceiling prevents infinite loops in degenerate cases (e.g., a
# Rack app whose +#app+ method returns +self+).
#
# @param app [Object] the route app (or a wrapper around it)
# @param depth [Integer] recursion depth guard (stops at 10)
# @return [Object] the innermost app object
# @param app [Object] current app object to inspect
# @param depth [Integer] recursion depth guard
# @return [Object] the innermost non-wrapper app
def self.unwrap_app(app, depth = 0)
return app if depth > 10
# Stop when we reach a Class — Rails engines ARE classes and respond to .app,
# but we should not traverse into them.
return app if app.is_a?(Class)
app.respond_to?(:app) ? unwrap_app(app.app, depth + 1) : app
end

Expand Down
7 changes: 7 additions & 0 deletions core/test/lib/workarea/mount_point_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,12 @@ def test_find_memoizes_result
assert_equal first, second
end
end

def test_unwrap_app_stops_at_class
# Engine classes are Classes — unwrap_app must NOT call .app on them
# (engines respond to .app but we must treat them as the final node)
assert_equal Workarea::Storefront::Engine,
Workarea::MountPoint.unwrap_app(Workarea::Storefront::Engine)
end
end
end
Loading