← Back to Upcase

PDFs with HyPDF


(Charlie Gaines) #1

Anyone have experience using HyPDF on Heroku? The docs suggest this:

@hypdf = HyPDF.htmltopdf(
    '<html><body><h1>Title</h1></body></html>',
    orientation: 'Landscape',
    copies: 2,
    # ... other options ...
)

I’ve tried getting the html from my view with the render_to_string method, but it’s not working. How do I capture the html generated by a view from within a controller?

Also, can anyone suggest a better solution for creating PDFs in Rails? Many thanks for any tips…


(Geoff Harcourt) #2

I’ve used https://code.google.com/p/wkhtmltopdf/ before. It’s tried and true, but you have to use a buildpack. I am the most unabashed Heroku supporter in the world, but anything involving files is kind of a drag.


(Jon Kinney) #3

I use WKHTMLTOPDF as well, and I created this module (currently stored in lib/pdf_generator.rb) to handle creating PDFs from a given view template in our app:

module PDFGenerator
  def generate_pdf_name(attrs)
    file_dir = attrs[:file_dir]
    file_name_prefix = attrs.fetch(:file_name_prefix, '')

    begin
      name = "#{file_name_prefix}#{SecureRandom.urlsafe_base64(22)}"
    end while File.exists?("#{file_dir}/#{name}.pdf")

    name
  end

  def generate_pdf(attrs)
    view_template       = attrs[:view_template]
    locals              = attrs[:locals]
    file_dir            = attrs[:file_dir]
    file_name_prefix    = attrs.fetch(:file_name_prefix, '')
    wkhtmltopdf_options = attrs.fetch(:wkhtmltopdf_options, {})

    html = render_to_string(
      partial: view_template,
      locals: locals,
      layout: false
    )

    kit = PDFKit.new(html, wkhtmltopdf_options)

    css_file_path = "#{Rails.root}/app/assets/stylesheets/pdf.css"
    kit.stylesheets << css_file_path

    FileUtils.mkdir_p(file_dir)

    pdf_name =
      generate_pdf_name(file_name_prefix: file_name_prefix, file_dir: file_dir)

    file_name = "#{pdf_name}.pdf"
    file_path = Rails.root.join("#{file_dir}/#{file_name}").to_s

    kit.to_file(file_path)

    { file_name: file_name, file_path: file_path }
  end
end

Include the module, then you can use it like this:

pdf = generate_pdf(view_template:        'invoices/pdf_content.html.haml',
                    file_dir:            'tmp/invoices',
                    file_name_prefix:    'invoice_',
                    wkhtmltopdf_options: {
                      header_center:  Time.now.to_s(:full_w_time),
                      margin_top:     14
                    })

And you get back a hash stored in the pdf variable with the file_name and file_path. Obviously you can modify this to your needs.

You could also probably apply extract class a few times on the generate_pdf method, but I liked having the locality of it all in one, albeit larger, method call. It’s already in it’s own module after all. I’m sure there is other cleanup that could be done to this as well. I’m certainly open to suggestions, though this has been working quite well for me for a few months.


(Charlie Gaines) #4

Fantastic, thanks guys


(Jon Kinney) #5

I should mention two things… the :full_w_time is a custom time format that I added, so yea, just FYI. And also, my solution doesn’t take Heroku into account at all… I actually prefer to host on DigitalOcean for toy stuff and BlueBox for production apps so I have access to the local file system specifically for things like generating PDF files, etc. Hopefully what I provided can still be useful though. Maybe in the kit.to_file(file_path) call you could save the file to an S3 bucket or something instead of trying to save it to the local file system. Maybe someone with more Heroku/S3 experience can help chime in.