Search code examples
ruby-on-railsrubypdf-generationwkhtmltopdfpdfkit

Downloading PDF's using PDFkit error on Rails


I am trying to download an HTML view in Rails5 as a PDF, however, whenever I download the PDF I get an error: Failed to load PDF document. I am using the following gems:

gem 'pdf kit' gem 'wkhtmltopdf-binary'

here is my controller:

class InputController < ApplicationController

def index
    @inputs = Input.all
end


def show
    @input = Input.find_by(id: params[:id])

            send_data @pdf, :filename => @input.company + ".pdf",
                                    :type => "application/pdf",
                                    :disposition => "attachment"
end


end

when i type in a download URL ie: localhost:3000/input/1.pdf i get a downloaded pdf file with an error:

enter image description here

My show view is very simple:

<ul>
<li><%= @input.company %></li>
<li><%= @input.position %></li>
<li><%= @input.date %></li>
</ul>

Any help would be appreciated.

Best, Ayaz

UPDATE

I also just tried taking out @pdf and putting in @input:

    def show
    @input = Input.find_by(id: params[:id])
            send_data @input, :filename => @input.company + ".pdf",
                                    :type => "application/pdf",
                                    :disposition => "attachment"
    end

No change in results


Solution

  • Your code doesn't assign anything to the instance variable @pdf:

    def show
      @input = Input.find_by(id: params[:id])
    
      send_data @pdf, :filename => @input.company + ".pdf",
                      :type => "application/pdf",
                      :disposition => "attachment"
    end
    

    and instance variables have a default value of nil. You need to do something like:

    input = Input.find_by(id: params[:id])
    
    html = %Q{
    <ul>
    <li>#{input.company}</li>
    <li>#{input.position}</li>
    <li>#{input.date}</li>
    </ul>
    }
    
    kit = PDFKit.new(html, :page_size => 'Letter')
    pdf = kit.to_pdf
    
    
    send_data pdf, :filename => input.company + ".pdf",
                   :type => "application/pdf",
                   :disposition => "attachment"
    

    Or, if you want to read the view file then run it through the ERB engine, something like this:

    file_path = File.expand_path("../views/input/show.html.erb", __FILE__)
    erb_template = File.read(file_path)
    
    @input = Input.find_by(id: params[:id])
    renderer = ERB.new(erb_template)
    html = renderer.result()
    
    #As above...
    

    From the Rails Guide:

    By default, controllers in Rails automatically render views with names that correspond to valid routes.
    ...
    ...
    The rule is that if you do not explicitly render something at the end of a controller action, Rails will automatically look for the action_name.html.erb template in the controller's view path and render it.

    And from the docs for send_data():

    This method is similar to render plain: data, but also allows you to specify whether the browser should display the response as a file attachment (i.e. in a download dialog) or as inline data

    Therefore, when you call send_data() in the controller, you are explicitly rendering something, so the default rendering of the action_name.html.erb file doesn't occur.