Fix MissingTemplate on Rails >= 8.0 with prepended controller modules - #202
Merged
kiskoza merged 2 commits intoJun 18, 2026
Merged
Conversation
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 |
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
force-pushed
the
fix-prefixes-with-prepended-modules
branch
from
June 18, 2026 07:33
86189d7 to
d4830fb
Compare
Author
|
@kiskoza Thanks for your quick merge! |
Member
|
Hey @giant-saimiri, I made the release for you. Happy coding and thanks again for the contribution! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix
MissingTemplateon Rails >= 8.0 when a module is prepended to the controllerSymptom
On Rails 8.0+,
render xlsx: "show"raises:— 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:
Rails 8.0 reordered render option normalization. Through Rails 7.2,
_normalize_renderran before custom renderers, sooptions[:template]was already defaulted to the action name andoptions[:prefixes]to the controller's_prefixesby the time this gem's:xlsxrenderer block executed — the gem's ownif options[:template].nil?defaulting was dead code. Rails 8.0 moved that normalization intorender_to_body(_normalize_optionsbecame_process_render_template_optionsinActionView::Rendering), which runs after the renderer. So on 8.0 the gem's defaulting branch is exercised for the first time.The gem's prefix computation breaks on prepended modules.
A prepended module is the first entry in
ancestorsand does not respond tocontroller_path, sotake_whilestops immediately and returns[]. With empty prefixes, the bare template name is searched at the view-paths root →MissingTemplate.Fix
Use the controller's
_prefixesinstead. It is built from the superclass chain (local_prefixes + superclass._prefixes, seeAbstractController::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 barerender :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