Skip to content

Background PDF creation via delayed_job gem

danblaker edited this page Oct 4, 2012 · 1 revision

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:

Controller:

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

Delayed Job

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.

Clone this wiki locally