Skip to content

Instantly share code, notes, and snippets.

@omgreenfield
Last active February 10, 2022 18:17
Show Gist options
  • Save omgreenfield/2b995d2e5877b28475a2e48e7e3856ba to your computer and use it in GitHub Desktop.
Save omgreenfield/2b995d2e5877b28475a2e48e7e3856ba to your computer and use it in GitHub Desktop.
Generating and Download PDF's in Rails (Windows)

Generating and Downloading PDF's in Rails (Windows)

I had a lot of trouble trying to get one of my Rails apps to accomplish this common task. For one thing, my dev box runs Windows 7, which notoriously does not play nicely with Ruby on Rails. Another thing... all the instructions/tutorials/document for the gem I'm using, Wicked PDF, were either incomplete or, for some reason or another, didn't work for me.

That's all to say, here are the steps I took, and I hope they will help someone.

I used the Wicked PDF gem. Ryan Bates over at RailsCasts goes over using PDFKit, but this writeup will be all about Wicked PDF.

These next steps I got straight from the instructions on the GitHub page:

Installation

First, you'll need the Wicked PDF gem, and the HTML to PDF converted that Wicked uses.

Add these guys to your Gemfile:

gem 'wicked_pdf'
gem 'wkhtmltopdf-binary'

If you're running Rails3 or older, you'll also need to register the PDF mime type. Rails4 and up does it for you.

Add this to config/initializers/mime_types.rb

Mime::Type.register 'application/pdf', :pdf`

And create the initializer by running this from the command line:

rails generate wicked_pdf

Now... the instructions say that the inclusion of gem 'wkhtmltopdf-binary' in our Gemfile will allow us to run wkhtmltopdf. This did not work for me. Instead, I downloaded the .exe from the web and installed it under C:\ as to avoid permission issues.

Then in the initializer, config/initializers/wicked_pdf.rb, point exe_path to the .exe file you just downloaded. For me, that initializer looks like this:

WickedPdf.config = {
  exe_path: 'C:\wkhtmltopdf\bin\wkhtmltopdf.exe'
}

Setting up pages and whatnot

My use case was for some "Download PDF" link to simply generate a PDF from some HTML, and allow the user to download the PDF. This required the following steps:

  1. Adding the route for the download request
  2. Adding the controller action to render a view, convert the resulting HTML to a PDF, and send that PDF to the user.
  3. Creating the view to be converted to PDF
  4. Adding a link that points to the download route

In my routes.rb, I added a get route for downloading the PDF:

get 'download' => 'downloader#download'

This routes mywebsite.com/download to the download action in downloader_controller.rb.

In some cases, you might want to generate a view based on some resource. For instance, if your view used some user resource, you would put this in your routes.rb instead:

resources :users do
  get 'download'
end

I'm going to continue under the assumption that you're doing it my way, but the process should be fairly similar.

Next, it's time to add the action in downloader_controller.rb:

def download
  pdf = WickedPdf.new.pdf_from_string(            #1
    render_to_string('download', layout: false))  #2
  send_data(pdf,                                  #3
    filename: 'download.pdf',                     #4
    type: 'application/pdf',                      #5
    disposition: 'attachment')                    #6
end

Let's break down what's going on at each line:

Line 1, we're asking Wicked PDF to generate a PDF from some string.

Line 2, we're rendering 'views/downloader/download.html.erb' without a layout. Wicked PDF runs outside of the Rails app, so it does not have normal access to layouts and other stuff. Any required assets have to be referenced with Wicked PDF helper functions. I'll go over that later.

Line 3, we're making a call to send the PDF file to the user.

Line 4, we're specifying the default name of the file that will be downloaded.

Line 5, specifying mime type,

Line 6, specifying that the file should be downloaded, not interpreted by the browser.

Now let's make the view. I got stuck at this part for a while. This article helped me out quite a bit.

Create the file views/downloader/download.html.erb, and edit it.

Here's where we need those Wicked PDF helper functions. Remember that Wicked is not inside of Rails, so our assets aren't imported automatically; we have to import them. For me, all I needed was my downloader.scss stylesheet. Then, I rendered some partial that had all the HTML I needed, but for simplicitly, let's say I didn't use a partial and instead just had all the HTML in this one view. It would look like this:

<%= wicked_pdf_stylesheet_link_tag 'downloader' %>
<div class="some-div">
  <div class="some-other-div">
    <p>Some content</p>
  </div>
</div>

That's it. I include the downloader stylesheet via the helper function and put it some HTML. I don't include doctypes or HTML, head, or body tags. The HTML to PDF converter does fine without them. If you're output PDF looks weird, maybe include them.

Now, the final step, adding a link to download the thing. Somewhere in a toolbar, I've got this link:

<%= link_to 'Download PDF', download_path, :target => '_blank' %>

I had to include the :target => '_blank' bit. It opens a new tab/window, starts the download, then closes that new tab/window. Without that, clicking on the link redirected me to a page with the PDF binary in plain text; not very useful.

Next Steps

Took a while, but at least now I can download a PDF. My output looked a little funky at first because my CSS was designed to work with the application layout present. After some CSS adjustments, it worked out.

It's possible that I'll run into some issues trying to get this working on Heroku. So after I do that, I'll update this document.

Sources

1: https://github.com/mileszs/wicked_pdf 2: pdfkit/pdfkit#123 3: https://imvishaltyagi444.wordpress.com/2016/03/04/pdf-file-create-and-download-by-wicked_pdf/

@omgreenfield
Copy link
Author

omgreenfield commented Feb 10, 2022

Looks like you might be missing the views/downloader/download.html.erb file.

Edit: if not, You may to explicitly state the controller: render_to_string("downloader/download, layout: false)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment