There are times when you need to render a view template outside of the context of a controller.
Yes, most of the time, if you think "I want to render this in my mode" you're Doing It Wrong. So, before you do this, think very carefully whether this rendering really belongs outside of a controller. Most of the time, it doesn't.
Here's an example from Woobius. I've been working on a Project Archive functionality. For the project archives to be useful, they should not only include files from the project, but also issue sheets, audit logs, and other meta-information. The result of the archival process is a zip file containing all the files in the project, as well as all that meta-information - ideally in an easily accessible format like, say, HTML.
Archiving a project that might contain several gigabytes of architectural drawings is obviously not something that should be happening in a controller. It's a fire, forget, and receive a DVD in the post a few days later kind of process. Rendering HTML files in a Rails application with anything other than ERB (or HAML, etc if you swing that way) would be silly. So, this is a perfectly legitimate case where we need to render ERB templates outside of the context of a controller.
A long time ago, I worked on an application where I had to rush to figure out how to do this, so I ended up spawning a test-controller from within the console context and using that to render the page. That had a number of issues which I won't get into in this article, but the main one was probably the fact that it was really ugly and nasty - a hack of the wrong kind.
That was then, and this is now, and I thought I'd figure out the correct way of doing this. Perhaps my Google-fu is lacking, but I didn't find all that much help on this topic, until I stumbled on
this page. In short, it gives examples of how to render an ERB in a controller-free context. I thought it'd be quite simple to use lifo's code, but it turned out to have a big problem.
You see, when I render views, I like to use my carefully constructed helpers, not juggle plain ERB and HTML. So I included my helpers:
require 'erb'
require 'ostruct'
def render_template(template, params)
vars = OpenStruct.new params
class << vars
include CustomHelper, ApplicationHelper, ActionView::Helpers::TextHelper
end
template = File.read("#{TEMPLATE_ROOT}/#{template_path}.html.erb")
puts ERB.new(template).result(vars.send(:binding))
end
However, getting this to work involved all sorts of 'fun' because of the output_buffer is not provided to the template, by ERB, and so the concat TextHelper method doesn't work. Without concat, most interesting helpers fall on their nose. There follows a lengthy session of working my way through ERB's internals to find out how to expose that output_buffer. It turns out ERB is pure evil stuff (though I'm sure it all made sense at the time). It renders the template into a string, which it then evals. You can set the output_buffer variable name, but try as I might I couldn't get the template to have access to it.
Luckily, though, while I was desperately tracing through the Rails code and doing a lot of global searches, I found a file, compiled_templates_test.rb, which involved rendering some templates. And, lo and behold, in the tests, a completely different rendering method was used:
assert_equal "Hello world!", render(:file => "test/hello_world.erb")
def render(*args)
view_paths = @explicit_view_paths || ActionController::Base.view_paths
ActionView::Base.new(view_paths, {}).render(*args)
end
Aha!
It turns out, this is by far the easier way to render templates, in a rails application, outside of a controller. So here's the final code, for anyone to reuse as they see fit. Hopefully it will save some other hapless soul a couple of hours of poking around:
def render_erb(template_path, params)
view = ActionView::Base.new(ActionController::Base.view_paths, {})
class << view
include EmailHelper, ApplicationHelper
end
view.render(:file => "#{template_path}.html.erb", :locals => params)
end