Skip to content

Fix MissingTemplate on Rails >= 8.0 with prepended controller modules - #201

Closed
giant-saimiri wants to merge 1 commit into
caxlsx:masterfrom
giant-saimiri:fix-prefixes-with-prepended-modules
Closed

Fix MissingTemplate on Rails >= 8.0 with prepended controller modules#201
giant-saimiri wants to merge 1 commit into
caxlsx:masterfrom
giant-saimiri:fix-prefixes-with-prepended-modules

Conversation

@giant-saimiri

Copy link
Copy Markdown

Fix MissingTemplate on Rails >= 8.0 when a module is prepended to the controller

Symptom

On Rails 8.0+, render xlsx: "show" raises:

ActionView::MissingTemplate (Missing template /show with {locale: [:en], formats: [:xlsx], ...})

— note the bare /show: the template is looked up at the view-paths root with no prefixes, instead of under the controller's view directory. This happens for any controller that has a module prepended to it (memo_wise, instrumentation/APM gems, etc.). On Rails <= 7.2 the same code works fine.

Root cause

Two things combine:

  1. Rails 8.0 reordered render option normalization. Through Rails 7.2, _normalize_render ran before custom renderers, so options[:template] was already defaulted to the action name and options[:prefixes] to the controller's _prefixes by the time this gem's :xlsx renderer block executed — the gem's own if options[:template].nil? defaulting was dead code. Rails 8.0 moved that normalization into render_to_body (_normalize_options became _process_render_template_options in ActionView::Rendering), which runs after the renderer. So on 8.0 the gem's defaulting branch is exercised for the first time.

  2. The gem's prefix computation breaks on prepended modules.

    options[:prefixes] ||= self.class.ancestors
                               .take_while { |a| a.respond_to?(:controller_path) }
                               .map(&:controller_path)

    A prepended module is the first entry in ancestors and does not respond to controller_path, so take_while stops immediately and returns []. With empty prefixes, the bare template name is searched at the view-paths root → MissingTemplate.

Fix

Use the controller's _prefixes instead. It is built from the superclass chain (local_prefixes + superclass._prefixes, see AbstractController::ViewPaths), so it is immune to prepended modules — and it is exactly the value Rails itself injected before 8.0 and still uses for a bare render :action, so behavior is identical for vanilla controllers.

Test

Adds Examples::PrependedModuleController (prepends an empty module to mirror what memo_wise & co. do) with a request spec. Without the fix it fails on Rails 8.0/8.1 with the exact error above; it passes on <= 7.2 either way, since there the normalization-order made the branch dead code. Suite verified green locally on Rails 7.1, 7.2, 8.0 and 8.1.

🤖 Generated with Claude Code

Rails 8.0 moved render option normalization out of _normalize_render
(which ran before custom renderers) into render_to_body (which runs
after). Until then the renderer's own template/prefixes defaulting was
dead code: Rails had already set options[:template] to the action name
and options[:prefixes] to the controller's _prefixes by the time the
:xlsx renderer block executed.

On Rails 8.0 that defaulting is exercised for the first time, and
computing prefixes via

  self.class.ancestors.take_while { |a| a.respond_to?(:controller_path) }

returns [] whenever a module is prepended to the controller (memo_wise,
instrumentation gems, ...): the prepended module is the first ancestor
and does not respond to controller_path, so take_while stops at once.
The bare template name is then looked up at the view-paths root and
raises ActionView::MissingTemplate (e.g. "Missing template /show").

Use the controller's _prefixes instead - it is built from the
superclass chain (local_prefixes + superclass._prefixes), immune to
prepended modules, and exactly the value Rails itself injected before
8.0 and still uses for a bare render :action.
@giant-saimiri

Copy link
Copy Markdown
Author

Superseded by #202 — same change, moved to our organization's fork. Sorry for the noise!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant