Generating PDFs From HTML With Rails

There are many ways to generate PDFs in Ruby and Rails. Chances are that you are already familiar with HTML and CSS, so we are going to use PDFKit to generate PDF files using HTML from standard Rails view and style code.

Introduction to PDFKit

Internally, PDFKit uses wkhtmltopdf (WebKit HTML to PDF), an engine that will take HTML and CSS, render it using WebKit, and output it as a PDF with high quality.

To start, install wkhtmltopdf on your computer. You can download the binary or install from Brew on Mac, or your preferred Linux repository.

You also need to install the pdfkit gem, and then run the following bit of Ruby to generate a PDF with the text “Hello Envato!”

You should have a new file called hello.pdf with the text at the top.

An example of the PDF that was generated

PDFKit also allows you to generate a PDF from a URL. If you want to generate a PDF from the Google homepage, you can run:

As you can see, I’m specifying the page_size—by default, A4 is used. You can see a full list of options here.

An example of the Google homepage in PDF format

Styling Your PDF Using CSS

Earlier I mentioned that we are going to generate PDF files using HTML and CSS. In this sample, I have added a bit of CSS to style the HTML for a sample invoice, as you can see:

If you run this script, the file envato_invoice.pdf will be generated. This photo shows the result of the sample invoice:

An example of an Envato invoice PDF

As you can see, PDFKit is very easy to use, if you are already familiar with HTML and CSS. You can continue customising or styling this document as you like.

Using PDFKit From a Rails Application

Now let's take a look at how to use PDFKit in the context of a Rails application, so we can dynamically generate PDF files using the data from our models. In this section we're going to build a simple rails application to generate the previous "Envato Invoice" dynamically. Start by creating a new rails app and adding three models:

Now, we have to add some sample data to the database. Add this code snippet to db/seeds.rb.

Run rake db:seed in your terminal to add the sample invoice to the database.

We are also interested in generating a list of invoices and the detail of one invoice in our app, so using rails generators, run rails generate controller Invoices index show to create the controller and views.



We need to modify rails routes to redirect to InvoicesController by default, so edit config/routes.rb:

Start your rails server and navigate to localhost:3000 to see the list of invoices:

List of invoices


The CSS for this invoice details page has been moved to app/assets/stylesheets/application.scss

Then when you click on an invoice in the main listing page, you'll see the details:

Invoice view

At this point, we are ready to add the functionality to our rails application to view or download the invoices in PDF.

InvoicePdf Class to Handle PDF Rendering

In order to render invoices from our rails app to PDF, we need to add three gems to the Gemfile: PDFKitrender_anywhere, and wkhtmltopdf-binary. By default, rails only allows you to render templates from a controller, but by using render_anywhere, we can render a template from a model or background job.

In order not to pollute our controllers with too much logic, I'm going to create a new InvoicePdf class inside the app/models folder to wrap the logic to generate the PDF. 

This class is just taking the invoice to render as a parameter on the class constructor. The private method as_html is reading the view template invoices/pdf and layout_pdf that we are using to generate the HTML that we need to render as PDF. Lastly, the method to_pdf is using PDFKit to save the PDF file in the rails public folder. 

Possibly you want to generate a dynamic name in your real application so the PDF file doesn't get overwritten by accident. You might want to store the file on AWS S3 or a private folder too, but that is outside of the scope of this tutorial.



One thing to notice in this layout file is that we are rendering the styles in the layout. WkHtmlToPdf does work better if we render the styles this way.

DownloadsController to Render the PDF Invoice

At this point we need a route and controller that call the class InvoicePdf to send the PDF file to the browser, so edit config/routes.rb to add a nested resource:

If we run rake routes, we see the list of routes available in the application:

Add app/controllers/downloads_controller.rb:

As you can see, when the request is asking for a PDF file, the method send_invoice_pdf is processing the request. The method invoice_pdf is just finding the invoice from the database by id, and creating an instance of InvoicePdf. Then send_invoice_pdf is just calling the method to_pdf, to send the generated PDF file to the browser. 

One thing to note is that we are passing the parameter disposition: "inline" to send_file. This parameter is sending the file to the browser, and it will be displayed. If you want to force the file to be downloaded, then you'll need to pass disposition: "attachment" instead.

Add a download button to your invoice show template app/views/invoices/show.html.erb:

Run the application, navigate to the invoice details, click on download, and a new tab will open displaying the PDF Invoice.

Invoice view PDF dynamically generated

Render PDF as HTML in Development

When you're working on the markup for your PDF, having to generate a PDF every time you want to test a change can be slow sometimes. For this reason, being able to view the HTML that will be converted to PDF as plain HTML can be really useful. We only need to edit /app/controllers/downloads_controller.rb.

Now the show method is also responding for HTML requests in development mode. The route for a PDF invoice would be something like http://localhost:3000/invoices/1/download.pdf. If you change it to http://localhost:3000/invoices/1/download.html, you will see the invoice in HTML using the markup that is used to generate the PDF.

Given the code above, generating PDF files using Ruby on Rails is straightforward assuming you're familiar with the Ruby language and the Rails framework. Perhaps the nicest aspect of the entire process is that you don't have to learn any new markup languages or specifics about PDF generation.

I hope this tutorial has proved useful. Please leave any questions, comments, and feedback in the comments and I'll be happy to follow-up.



Related Articles