PDFKit in Rails 3.1 on Heroku
So PDFkit is totally awesome, but there are a few gotchas to be aware of if you are on a single threaded server (`rails server`) or a read-only filesystem (Heroku).
The first thing to note is that with the introduction of the asset pipeline, assets (images, stylesheets, javascripts) are no longer served up directly but instead are first processed by your rails app and then served. This means that in a single threaded process (`rails server` or Heroku with a single dyno) PDFs generated with an image or external stylesheet will stall and timeout. This is because while the thread is being used to process the PDF, wkhtmltopdf sends another request to fetch the external data and we find ourselves in a deadlock as the two processes wait for each other to complete.
For stylesheets, the simplest solution I found was to have your styles placed inline and perhaps have them conditionally placed with this helpful bit of code:
def request_from_pdfkit? # when generating a PDF, PDFKit::Middleware will set this flag request.env["Rack-Middleware-PDFKit"] == "true" end
You could also try serving a stylesheet directly from the public directory (bypassing the asset pipeline, but I have not tested this.
With images it is a little more tricky. We still cannot use the asset pipeline for reasons already discussed. If we give a full URL such as “http://example.com/image.png” the server hangs and times out, and if we provide a relative path to an image in the public directory PDFKit doesn’t seem to find it. So what to do?!
It turns out that when wkhtmltopdf encounters an image tag with a relative path, it actually looks for the image at the full UNIX path. So, we create a new image_tag helper that will give the full UNIX path to the file in question:
def pdf_image_tag(filename, options = {})
path = Rails.root.join("app/assets/images/#{filename}")
options[:src] = path.to_s
attributes = options.map{ |k,v| "#{k}='#{v}'" }.join(" ")
raw("<img #{attributes}/>")
end
There you have it. PDFKit playing nice with Heroku and Rails 3.1
Hi,
Thanks for this post. Very helpful!
I was wondering if you could help with two issues:
1. To serve the stylesheets inline I’d need to pre-process the .scss file to convert them into a regular css files. Do you know how to do this from the controller?
2. I don’t get how you get “pdf_image_tag” method to be called instead of the regular “image_tag”
Hope you can help.
Cheers,
Guillaume
Hi Guillaume,
For converting sass to css checkout this discussion: http://stackoverflow.com/questions/3991603/sass-to-css-converting-utility
To conditionally call pdf_image_tag in my view I use the “send” method:
<%= send((request.format.pdf? ? :pdf_image_tag : :image_tag), 'signature.png' %>or
<%= send((request_from_pdfkit? ? :pdf_image_tag : :image_tag), 'signature.png' %>