The best approach for generating PDFs in Rails really depends on the types of PDFs you need to generate. This tutorial will provide step-by-step instructions for generating PDFs by using wkhtmltopdf, an open source CLI for rendering HTML into PDF from standard Rails view and style code. This approach is ideal if you don't need a publishing workflow or precise control over page output.

For the purpose of this tutorial I've made a simple demo application on Heroku to show you what we'll be building. The application lists a series of sample invoices that can be previewed in the browser, and then converted to PDF. The source code is available in our repo rails-generate-pdf. Here's my configuration:

Rails 5.2.1
Postgresql 10.5
wkhtmltopdf 0.12.4
macOS Mojave 10.14.1

If you're looking for more granular control over PDF output, wkhtmltopdf may not be the best. Instead, Prawn is a popular open source pure Ruby PDF generation library that is reasonably performant and uses a X,Y coordinate system for placing elements on a page. The downside is you'll need time to wrap your head around its rendering model, and learn its DSL (Domain Specific Language). Alternatively, you could use a commercial PDF generation library (like PDFTron PDF SDK).

Let's begin!

linkStep 1 - Install wicked_pdf

Start by downloading and installing Wicked PDF's precompiled binary. You can test the output by converting a URL into a PDF file by opening the command line and typing this:

wkthmltopdf http://google.com google.pdf

This will generate the file google.pdf.

But, how do we use this in our Ruby on Rails projects?

Wicked PDF uses the shell utility wkhtmltopdf to serve a PDF file from HTML. Rather than dealing with a PDF generation DSL of some sort, you simply write an HTML view as you would normally do, then let Wicked PDF take care of the hard stuff.

linkStep 2 - Create a New Ruby on Rails App

# command line - create Ruby on Rails project
rails new rails-generate-pdf –webpack=react –database=postgresql

linkStep 3 – Add and Install Dependencies

We will use Bootstrap 4 for styling (which also requires jQuery) and wicked_pdf for generationg PDF. Here we'll install the required gems:

# Gemfile
# rails-generate-pdf/Gemfile
gem 'jquery-rails'
gem 'bootstrap', '~> 4.1.3'
gem 'wicked_pdf'
gem 'wkhtmltopdf-binary'

Because wicked_pdf is a wrapper for wkhtmltopdf, we install that too (gem wkhtmltopdf-binary). Once all gems are added to rails-generate-pdf/Gemfile, run this from the root of your project:

# command line
bundle install

Then generate the initializer:

# command line
rails g wicked_pdf

linkStep 4 – Register the PDF MIME Type

For older rails versions, you'll need to register the PDF MIME type in the initializer:

# file: rails-generate-pdf/config/initializer/mime_types.rb
Mime::Type.register “application/pdf”, :pdf

linkStep 5 - Create the Models

Our demo application will only have two models: Invoice and InvoiceItem. To create the Invoice model, navigate to the root of your project from the command line and type:

# command line -> generate Invoice Model
rails generate model Invoice from_full_name from_address from_email from_phone to_full_name to_address to_email to_phone status discount:decimal vat:decimal

Then open the Invoice model file (app/models/invoice.rb) and add the following code:

# file: rails-generate-pdf/app/models/invoice.rb
class Invoice < ApplicationRecord
    has_many :invoice_items, dependent: :destroy

    STATUS_CLASS = {
        :draft => "badge badge-secondary",
        :sent => "badge badge-primary",
        :paid => "badge badge-success"
    }

    def subtotal
        self.invoice_items.map { |item| item.qty * item.price }.sum
    end

    def discount_calculated
        subtotal * (self.discount / 100.0)
    end

    def vat_calculated
        (subtotal - discount_calculated) * (self.vat / 100.0)
    end

    def total
        subtotal - discount_calculated + vat_calculated
    end

    def status_class
        STATUS_CLASS[self.status.to_sym]
    end

end

Next, create the InvoiceItem model using the command line (in the root of your project):

# command line -> generate InvoiceItem Model
rails generate model InvoiceItem name description price:decimal qty:integer invoice:references

Now open the InvoiceItem model file (app/models/invoice_item.rb) and add the following code:

# file: rails-generate-pdf/app/models/invoice_item.rb
class InvoiceItem < ApplicationRecord
    belongs_to :invoice
end

Next, create your database and tables by running these commands:

# command line -> run commands
rake db:create
rake db:migrate

linkStep 6 – Create a Controller and Setup Routes

First generate the controller by running this command:

# command line -> run command
rails generate controller Invoices index show

Next, open your controller file (app/controllers/invoices_controller.rb) and add the following code (which will render page contents into PDF format):

# file: rails-generate-pdf/app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
    def index
        @invoices = scope
    end

    def show
        @invoice = scope.find(params[:id])

        respond_to do |format|
            format.html
            format.pdf do
                render pdf: "Invoice No. #{@invoice.id}",
                page_size: 'A4',
                template: "invoices/show.html.erb",
                layout: "pdf.html",
                orientation: "Landscape",
                lowquality: true,
                zoom: 1,
                dpi: 75
            end
        end
    end

    private
        def scope
            ::Invoice.all.includes(:invoice_items)
        end
end

You'll notice we have two actions in our controller:

  • index: shows a list of all invoices
  • show: previews a single invoice

The show action has 2 formats (html and pdf), which will be used to define how the content is rendered. For example, if you access the invoice directly without the .pdf extension, you'll see an HTML preview, while adding the .pdf extension will render it to PDF. Try it here:

You can configure the options however you need (see wkhtmltopdf documentation for additional details). To customize the layout of the PDF, use the following settings:

  • template (invoices/show.html.erb): defines the template to be used when rendering the pdf
  • layout (pdf.html.erb): defines the main application layout

(These files are available in our repo.)

Once you're happy with the layout, open your routes file (app/config/routes.rb) and add the following code:

# file: rails-generate-pdf/app/config/routes.rb
Rails.application.routes.draw do
    root to: 'invoices#index'

    resources :invoices, only: [:index, :show]
end

linkStep 7 - Setup the View Part of the Application

First we need to create the new layout and use the helper from wicked_pdf to reference the stylesheet, JavaScript, or image assets. For this example, I will only use the stylesheet helper:

# file: rails-generate-pdf/views/layotus/pdf.html.erb
<!DOCTYPE html>
<html>
<head>
<title>PDFs - Ruby on Rails</title>
    <%= wicked_pdf_stylesheet_link_tag "invoice" %>
</head>
<body>
    <%= yield %>
</body>
</html>

In this case the css or scss file name is "invoice" (app/assets/stylesheets/invoice.scss).

Now you need to make sure all the assets will are precompiled (according to the official GitHub, this next step is essential for your gem to work perfectly when you deploy to production):

# file: config/initializers/assets.rb
Rails.application.config.assets.precompile += %w( invoice.scss )

The above array will need to include every stylesheet and JavaScript library that you're using.

Next, we need to add a link to the invoice page for generatingto generate the PDF by pointing to the show action of the invoice controller, and using the pdf format (/invoices/:id.pdf)):

# file rails-generate-pdf/app/views/layouts/shared/_header.html.erb
# invoice_path(@invoice, format: :pdf) -> /invoice/1.pdf
<%= link_to ‘DOWNLOAD PDF, invoice_path(@invoice, format: :pdf) %>

And that’s it! You can now deploy your application for generating PDFs. Here's our demo application that we deployed to a free Heroku web dyno.

Here's how the index looks like:

Here's how the PDF of the invoice looks like:

linkConclusion

Using wkhtmltopdf is a quick and easy way to generate PDFs from Ruby. However, if you need to generate PDFs from something other than HTML, want to integrate this into a workflow, or need precise control over positioning, then you're out of luck with wkhtmltopdf.

Additionally, wkhtmltopdf is based on the QtWebKit rendering engine, which is based on Apple's WebKit (which is a fork of KDE's KHTML). These engines are best suited for simple documents where you can afford to have some inconsistencies or failures.

For more robust functionality and reliable generation of PDF, PDFTron SDK supports Ruby (as well as many other languages and frameworks). Check out some of the Ruby code samples to help get you up and running quickly.

If you have any questions about PDFTron's PDF SDK, please feel free to get in touch!

You can find the source code for this blog post at our GitHub rails-generate-pdf.