A Host's Route Helpers Work Inside the Engine
Context
isolate_namespace Janela points every route helper evaluated inside
Janela’s controllers at the engine’s own route set. That is the right
default for the engine’s own code. It is the wrong answer for the
host’s, and the host’s runs there constantly, because
Janela::ApplicationController inherits the host’s
ApplicationController on purpose (ADR 004).
Three reports, one cause, and each host found it separately:
- A host’s application layout calling
root_pathin its nav, which made every pane 500. Solved another way in 0.2.1 by not rendering panes in the host’s layout (ADR 011). - A host authenticating per controller, whose
before_actionredirected tonew_session_path. - A host authenticating globally, whose
Authenticationconcern redirected tonew_session_pathfrom inside the engine and raisedActionController::UrlGenerationError. An unauthenticated visitor got a 500 instead of a sign-in page.
Case 3 is the one that shows the shape of the problem. The README had
framed the fix as something to do “if your app authenticates per
controller”, which is not the trigger at all. The trigger is that a
URL is generated by host code while it is inside the engine. A
rescue_from that redirects, an after_action, a flash partial and an
audit callback all qualify. Each host discovers it on its own, in
production, as a 500.
main_app.new_session_path is the documented fix and it works. It
also asks every host to know that Janela is an isolated engine, and to
remember it in code that has nothing to do with Janela. That is the
opposite of what installing this gem is supposed to feel like.
The option of including the host’s url_helpers wholesale was
considered and rejected. Both sets of helpers would then be present,
and a name in both would resolve by module order, so frames_path
inside Janela’s own view could silently become the host’s /frames.
Trading a loud UrlGenerationError for a quietly wrong link is a bad
trade.
Decision
Janela forwards to the host exactly those route helpers the engine does not define, and no others.
The set is computed when routes are loaded, as the host’s helper names
minus the engine’s, and each one is defined to call main_app. It is
rebuilt whenever routes reload, so a name that goes away goes away
here too.
# In a host's ApplicationController, running inside Janela:
redirect_to new_session_path # works, forwarded to the host
frames_path # Janela's, never the host's /frames
main_app.frames_path # the host's, said unambiguously
Two properties matter more than the convenience:
The engine’s own routes can never be shadowed. A name is forwarded only when Janela does not define it, so no host route can change what one of Janela’s own views means. Precedence is decided by construction, not by the order modules happened to be included.
main_app still means what it means. Nothing is taken away. A
host that prefers to be explicit, or that needs a name Janela also
uses, says main_app. and is unambiguous.
This covers named helpers only. url_for(record) and
redirect_to @record resolve polymorphically at call time, so there
is no name to forward and they still need main_app.. That boundary
is documented rather than papered over.
Consequences
- A host’s authentication redirect works inside Janela with no configuration, no reopened class and no knowledge that an engine is involved. That was the whole complaint in three reports.
- Janela is slightly less isolated than a textbook engine, in one direction only: host names in, never Janela names out. ADR 005’s mount independence is untouched, since the engine still generates every one of its own URLs through its own routes.
- A host route helper shares a name with one of Janela’s and the host
loses the bare name inside the engine. Janela’s route names are few
and dull, which makes this rare, and
main_app.remains. - The README’s explanation was wrong about the cause and is corrected here and there.
- Polymorphic routing is a known gap. If it bites someone the answer is another ADR, not a quiet widening of this one.