render_to_string causes subsequent mail rendering to fail
March 25, 2011
Today, I found a bug in Rails.
On a project, I need to mail PDF files to users, and I would like to use PDFKit for that. The contents of the PDF are generated by rendering a view, but I don’t really want PDFKit to hit the server to get the content. Both because this breaks down in unit tests where the server may not be running, but also because there’s just no reason to go through the entire stack.
So I decided to generate the HTML with render_to_string, then convert it to PDF with PDFkit, and finally mail it to the user.
However, it turned out that this doesn’t work. Here is a minimal example:
class NoteMailer < ActionMailer::Base default :from => "firstname.lastname@example.org" def foo @notes =  render_to_string(:template => "notes/index.html.erb") # self.instance_variable_set(:@lookup_context, nil) # <-- Workaround mail(:to => "email@example.com") end end
It turns out that the call to render_to_string initializes the @lookup_context of the controller (== mailer in this case). The lookup context is the object that is responsible for locating templates to render, so it contains (among other things) a list of template extensions to look for.
The call to mail() renders the actual mail, but the mailer now reuses the lookup context that was created by render_to_string. This prevents it from finding the mail template, which is (in this example) named “foo.text.erb”, because the lookup context doesn’t recognize the “.text.erb” extension.
A workaround is to manually clear the lookup context on the mailer after the call to render_to_string, but obviously this is a really bad solution.