About this entry
You’re currently reading the article “Rendering Templates with Extensions such as rxml with theme_support: "Template is missing" and "No rhtml, rxml, or delegate template found for ..." Errors.”
- Published:
- December 3rd 02:05 AM
- Updated:
- August 17th 07:53 PM
- Sections:
- Ruby on Rails
Rendering Templates with Extensions such as rxml with theme_support: "Template is missing" and "No rhtml, rxml, or delegate template found for ..." Errors
The rails' plugin theme_support is a great way to allow customized stylesheets, javascripts, images, layouts, and views to your app. Unfortunately, as it is currently built now, it has a short-coming: it doesn't allow for specifying routes with extensions! In this article I will propose a correction which provides the plugin with a performance boost as an added plus.
Liked it? !
Index
- Everybody on the Same Page
- Building a More Robust XML Support
- Bug and Other's Take on it
- My Proposal
- Further Resources
- Coming Soon
Everybody on the Same Page
In order to show you how to correct theme_support in a Ruby on Rails application, I will illustrate the steps by modifying the theme_support to our basic RESTful social_graph application to which we added theme support. Please give both articles a quick glimpse before continuing with this article, if you haven't done so already.
Building a More Robust XML Support
Recently I bumped into a problem with theme_support. It didn't support routes with extensions, such as rxml. Suppose we want to improve on the xml functionality already provided by RESTful implementation of our social_graph. We know that by calling
http://amontano-laptop:3000/acquaintances/1.xml
It's great, but as any scaffolding generation, it is doomed to be replaced since the bare-bones functionality it offers is almost never enough. For instance, I rather have the id display as an attribute of the tag, than as a subtag inside it. So we add the app\views\acquaintances\show.rxml:
xml.acquaintance(:id => @acquaintance.id) do xml.name @acquaintance.name xml.description @acquaintance.description end
How do we call this template from the controller? Our intuition tells us that since it has the same name as the action, we don't have to explicitly specify it, so merely erasing the default .to_xml line should do the trick, right? So our first attempt at the show action within social_graph\app\controllers\acquaintances.rb looks like this:
# GET /acquaintances/1
# GET /acquaintances/1.xml
def show
@acquaintance = Acquaintance.find(params[:id])
respond_to do |format|
format.html # show.rhtml
format.xml # show.rxml
end
end
Bug and Other's Take on it
When called by the browser:
http://amontano-laptop:3000/acquaintances/1.xml
It fails with a "No rhtml, rxml, or delegate template found for ..." message. For this, dorren proposed a fix which worked nicely when everything is bug-free. Our above solution would still not look right because it is adding the xml template within our application layout to it. So we change this to explicit ask it not to use a layout:
# GET /acquaintances/1
# GET /acquaintances/1.xml
def show
@acquaintance = Acquaintance.find(params[:id])
respond_to do |format|
format.html # show.rhtml
format.xml { render :template => 'acquaintances/show.rxml', :layout => false }
end
end
Unfortunately it never is, and his solution led me to another another annoying problem. If the template had a syntax error, the browser displays a misleading "No rhtml, rxml, or delegate template found for ..." message.
Why? Because theme_support's render_file method, meant to override the ActionView::Base method, finds the appropriate theme by going down a list of possibilities sorted by priority and invoking the pick_template_extension method until one does not raise an exception and goes ahead and renders that one. Unfortunately, that is the Action View method used to infer an extension when it is not present within the route, but if it is, ActionView simply takes the extension from the route itself and does not call pick_template_extension to get it. So dorren tries to emend the mistake by following the original ActionView::Base render_file method only invoking pick_template_extension when necessary. His proposal is to replace the render_file method within social_graph\vendor\plugins\theme_support\lib\patches\actionview_ex.rb with:
# Overrides the default Base#render_file to allow theme-specific views
def render_file(template_path, use_full_path = true, local_assigns = {})
search_path = [
"../themes/#{controller.current_theme}/views", # for components
"../../themes/#{controller.current_theme}/views", # for normal views
"../../themes/#{controller.current_theme}", # for layouts
"../../../themes/#{controller.current_theme}/views", # for mailer views
".", # fallback
"..", # Mailer fallback
"../.." # namespaced Mailer fallback
]
if use_full_path
search_path.each do |prefix|
theme_path = prefix +'/'+ template_path
begin
template_path_without_extension, template_extension = path_and_extension(theme_path)
template_extension = pick_template_extension(theme_path).to_s if !template_extension
# Prevent .rhtml (or any other template type) if force_liquid == true
if force_liquid? and
template_extension.to_s != 'liquid' and
prefix != '.'
raise ThemeError.new("Template '#{template_path}' must be a liquid document")
end
local_assigns['active_theme'] = get_current_theme(local_assigns)
return __render_file(theme_path, use_full_path, local_assigns)
rescue ActionView::ActionViewError => err
next
rescue ThemeError => err
# Should it raise an exception, or just call 'next' and revert to
# the default template?
raise err
end
end
raise ActionViewError, "No rhtml, rxml, or delegate template found for #{template_path} in #{@base_path}"
else
__render_file(template_path, use_full_path, local_assigns)
end
end
But then who can raise the exception if that template is not found? Well, he moves the original render_file call to inside the block preceding the rescue. So now when pick_template_extension is not called, render_file is the one that raises the exception when no template is found displaying the No rhtml, rxml, or delegate template found for ... message. Unfortunately, it also raises an exception when there is a syntax error within a template again thinking that it failed because there was no template and still displays the now misleading No rhtml, rxml, or delegate template found for ... message. That is easy to verify. Simply add some line that fails to app\views\acquaintances\show.rxml like:
@foo.bar xml.acquaintance(:id => @acquaintance.id) do xml.name @acquaintance.name xml.description @acquaintance.description end
And trying
http://amontano-laptop:3000/acquaintances/1.xml
will again display the annoying non-informative No rhtml, rxml, or delegate template found for ... message.
My Proposal
Now here is my proposal. Change the render_file method within social_graph\vendor\plugins\theme_support\lib\patches\actionview_ex.rb to:
# Overrides the default Base#render_file to allow theme-specific views
def render_file(template_path, use_full_path = true, local_assigns = {})
search_path = [
"../themes/#{controller.current_theme}/views", # for components
"../../themes/#{controller.current_theme}/views", # for normal views
"../../themes/#{controller.current_theme}", # for layouts
"../../../themes/#{controller.current_theme}/views", # for mailer views
".", # fallback
".." # Mailer fallback
]
if use_full_path
template_path_without_extension, template_extension = path_and_extension(template_path)
template_extension = pick_template_extension(template_path).to_s if !template_extension
local_assigns['active_theme'] = get_current_theme(local_assigns)
search_path.each do |prefix|
begin
# template_extension = pick_template_extension(theme_path)
if File.exists?(full_template_path("#{prefix}/#{template_path_without_extension}", template_extension))
# Prevent .rhtml (or any other template type) if force_liquid == true
raise ThemeError.new("Template '#{template_path}' must be a liquid document") if force_liquid? && template_extension.to_s != 'liquid' && prefix != '.'
return __render_file("#{prefix}/#{template_path}", use_full_path, local_assigns)
end
#rescue ActionView::ActionViewError => err
# next
rescue ThemeError => err
# Should it raise an exception, or just call 'next' and revert to
# the default template?
raise err
end
end
raise ActionViewError, "No rhtml, rxml, or delegate template found for #{template_path}"
else
__render_file(template_path, use_full_path, local_assigns)
end
end
No again try
http://amontano-laptop:3000/acquaintances/1.xml
and you will see a more informative Showing app/views/acquaintances/show.rxml where line #1 raised... message. So fix app\views\acquaintances\show.rxml back to:
xml.acquaintance(:id => @acquaintance.id) do xml.name @acquaintance.name xml.description @acquaintance.description end
And it will work fine! My strategy: instead of relying on render_file or pick_template_extension to succeed or fail in order to know if a template is appropriate, I choose a much more direct approach: call pick_template_extension only when needed and use it with full_template_path to check if the template exists and then render it without rescue.
Further Resources
- From Matt McCray, the creator of theme_support see Full Theme Support in Rails, Revisited and his archives on theme_support.
- more problem and hacks in theme_support plugin by dorren.
Coming Soon
- How to implement partials in rxml.
Stay tuned!

2 comments
Jump to comment form | comments rss [?]