Skip to content

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

Merged
kiskoza merged 2 commits into
caxlsx:masterfrom
giantmonkey:fix-prefixes-with-prepended-modules
Jun 18, 2026
Merged

Fix MissingTemplate on Rails >= 8.0 with prepended controller modules#202
kiskoza merged 2 commits into
caxlsx:masterfrom
giantmonkey: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

@kiskoza

kiskoza commented Jun 17, 2026

Copy link
Copy Markdown
Member

Hey! Thanks for the PR. I enabled the CI run and it seems like it's failing on some unrelated changes - i18n got a new release today that might not be compatible with what we have in the codebase, see ruby-i18n/i18n#735 others experiencing the same. I'll see what I can do on that front and get back to this PR as soon as I can

Alexander and others added 2 commits June 18, 2026 09:33
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.
The file was missing the _spec.rb suffix, so it never matched RSpec's
default pattern and was silently skipped by the suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kiskoza
kiskoza force-pushed the fix-prefixes-with-prepended-modules branch from 86189d7 to d4830fb Compare June 18, 2026 07:33
@kiskoza
kiskoza merged commit 9b83e80 into caxlsx:master Jun 18, 2026
33 checks passed
@giant-saimiri

Copy link
Copy Markdown
Author

@kiskoza Thanks for your quick merge!
Any idea when we can get a new release / tag?

@kiskoza

kiskoza commented Jun 18, 2026

Copy link
Copy Markdown
Member

Hey @giant-saimiri, I made the release for you. Happy coding and thanks again for the contribution!

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.

2 participants