-
Notifications
You must be signed in to change notification settings - Fork 0
Background PDF creation via delayed_job gem
Generating PDFs in your Rails app can be resource-intensive, especially if multiple people are generating PDFs at the same time. This can be mitigated by using a background task queue via delayed_job or resque. I went with delayed_job to avoid the extra overhead of setting up Redis. Here's the working code:
class DocsController < ApplicationController
def generate_pdf
@doc = Doc.find(params[:id])
# enqueue our custom job object that uses delayed_job methods
Delayed::Job.enqueue GeneratePdfJob.new(@doc.id)
# update the status so nobody generates a PDF twice
doc.update_attribute(:status, 'queued')
end
end
I put this code in /lib/generate_pdf_job in my Rails app, but you can put it wherever it makes sense. Note that I'm not including any layouts when rendering my PDF view, because it's a standalone view without headers or footers. Also note that I'm passing a local 'doc' variable to the view, rather than an instance '@doc' variable.
class GeneratePdfJob < Struct.new(:doc_id)
# delayed_job automatically looks for a "perform" method
def perform
# create an instance of ActionView, so we can use the render method outside of a controller
av = ActionView::Base.new()
av.view_paths = ActionController::Base.view_paths
pdf_html = av.render :template => "docs/pdf.html.erb", :layout => nil, :locals => {:doc => doc}
# use wicked_pdf gem to create PDF from the doc HTML
doc_pdf = WickedPdf.new.pdf_from_string(pdf_html, :page_size => 'Letter')
# save PDF to disk
pdf_path = Rails.root.join('tmp', "#{doc.id}.pdf")
File.open(pdf_path, 'wb') do |file|
file << doc_pdf
end
end
# delayed_job's built-in success callback method
def success(job)
doc.update_attribute(:status, 'complete')
end
private
# get the Doc object when the job is run
def doc
@doc = Doc.find(doc_id)
end
end
###View
This lives in /app/views/docs/pdf.html.erb
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>PDF Doc</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<%= wicked_pdf_stylesheet_link_tag 'pdf' %>
</head>
<body>
<% @doc = doc %>
<div class="page">
<p>
The doc ID is: <%= @doc.id %>.
</p>
</div>
</body>
</html>
This is a very simple example. There's obviously lots of other stuff you could do in the "perform" method, like send an email with the PDF attached, and a number of other callbacks supported by delayed_job. Switching to background PDF generation made a huge difference in the CPU activity on my production server, simply because I no longer had multiple instances of wkhtmltopdf running at the same time.