← Back to Upcase

Generating PDFs With Rails

Great episode, good to see the pdf-reader gem. I’d not come across that before.

Excellent episode! I’m really glad you covered the topic all the way on through deployment. Thanks!

Great episode! But shouldn’t the pdf be generated in the background? because it can take quite some time.
It would be great to explain how to generate it in the background then serve it to the user.
Thanks.

Hey @benoitwerner, you’re definitely right about that. I should have clarified this in the video, but in the real world implementation I do create the PDF outside of the request, saving to an external onject store. I omitted this from the video for the sake of brevity, but I should have at least mentioned the concept. Thanks for keeping me honest! :slight_smile:

Thanks @christoomey for this episode. I’d like to ask about the difference between wkhtmltopdf-binary vs wkhtmltopdf-heroku. Could one be switched for the other?

@pedropaf thanks for the question. I assume they’d both serve similar purposes, but I reach for the Heroku specific version as that my need.

wkhtmltopdf-heroku provides only the binary for an x64 architecture on Linux, whereas wkhtmltopdf-binary also has the x86 Mac and x86 Linux binaries.

Each binary is quite big (~40MB) so using the Heroku-only version should result in a smaller slug.

@christoomey This is a bit of a tangent but I’m interested in some of the commands you’re using like stat and next-commit, and couldn’t find them in your dotfiles.

@christoomey how would you test this if you need mora capybara like testing on pdf content ?
Would you include ability to render html in testing environment as you did in development env, and test on html ?

Hey @tsigo, tangents fully welcome! Here are the configurations for stat and next-commit. Both are defined as git aliases. We have a section in Mastering Git on git aliases if you want any more detail on them, but for the specifics of the two commands, they are implemented as:

  next-commit = !git checkout $(git log --reverse --ancestry-path --pretty=%H HEAD..master | head -1)
  stat = show --stat

@dixpac I believe there are a number of tools that can go further in extracting content from a PDF, but I’m not terribly familiar with them. My first tact would be to make sure I’ve extracted as much of the logic about what is rendered into the PDF into an object and test that thoroughly. Things like calculations, translations, etc. From there, I would use the minimal text matching testing I showed in the video. In practice, I’ve not found a need to go further, but it certainly is possible.

Following this tutorial, I was able to generate PDF’s on development mode. My issue is on Heroku as I’ve tried uploading the repo and but can’t get the PDF generation working.

Is this because Heroku doesn’t allow serving of PDF files, just like images when using Paperclip? BTW, I didn’t upload the exact repo of the tutorial, I just wrote mine from scratch and followed along. Did I perhaps missed some gems?

It’s working locally but the PDF generation on Heroku does not.

@christoomey, I worked on this tutorial and pushed a repo to Heroku. Only the PDF generation part fails but in development mode locally it was fine at all.

Does this mean that Heroku can’t serve the PDF generated files just like other static assets?

Hey @panoysia, Heroku will happily serve the PDF, although you have to make sure you have the needed wkhtmltopdf binary available. There is a section in the video that covers PDF generation on Heroku which should have everything you need. Are you still having issues after following those notes?

The PDF generation in Heroku is working fine now @christoomey, and the issue was that the <%= Rails.application.assets.find_asset(‘invoice_pdf’).to_s %> heroku line of code in the invoice_pdf.html.erb layout file was causing the error.

I think this is because when assets are precompiled, Rails adds a hash to the invoice_pdf.html.erb file which in turn makes the call to** <%= Rails.application.assets.find_asset('invoice_pdf').to_s %> invalid.

Sorry to bring up an old topic, but I thought it would be worth adding this can now be done in Rails 5 without the render_anywhere gem: you can use ApplicationController.renderer (method signature is identical). Just keep in mind that you need to set your asset host correctly in all envs so Rails picks ups on things (I think Suspenders does this out of the box, which is probably why @christoomey did not run into it during this vid). Hope this is useful to somebody!

3 Likes

Error in the docs I think. Step describing ‘inlining’ css with a Rails.application.assets call has the filename as invoice.pdf whereas it should be invoice_pdf since the full filename is invoice_pdf.scss

@pedrosmmoreira How did you get this working without render_anywhere gem? Can you show me a sample, I am using rails 5.1.0 (beta). Reducing a dependency if it’s not needed would be great for me. Thanks.

Hi, let me create a sample repo and I’ll get back to you

I’ve been trying to get this to work by following the tutorial and having to make some modifications that suit my situation which involves 2 tables. I keep getting this error:

 NoMethodError (undefined method `view_paths=' for # 
<ActionView::LookupContext:0x00007f900bcaa2b8>
Did you mean?  view_paths):

My DownloadsController:

class DownloadsController < ApplicationController
 
  
  def show
    respond_to do |format|
      format.pdf { send_audit_pdf }
    end
  end

  private

  def audit
    audit = {
    :record_incoming => params[:record_incoming],
    :record_sent => params[:record_sent]
    }
  end

  def download
    Download.new(audit)
  end

  def send_audit_pdf
    byebug 
    send_file download.to_pdf, download_attributes
  end

  def download_attributes
    {
      filename: download.filename,
      type: "application/pdf",
      disposition: "inline"
    }
  end


end

and my Download model:

require "render_anywhere"

class Download 
  include RenderAnywhere

  
  
  def initialize(audit) 
    @audit = audit
  end
 
  def to_pdf 
    kit = PDFKit.new(as_html, page_size: 'A4')
    kit.to_file("#{Rails.root}/public/audit.pdf")
  end
 
  def filename
    "audit #{audit.id}.pdf"
  end
 
  private
 
    attr_reader :audit
 
    def as_html
      render template: "audits/pdf", layout: "audit_pdf", locals: { audit: audit }
    end
end

So the 2 tables are incoming and sent. The audit object I’m trying to make a pdf from draws from both. So here’s the pdf.html.erb:

<h1>Request</h1>

<div class="flex-grid">
    <div class="items-col">

        <h3>State</h3>
        <h3>Time Modified</h3>
        <h3>License</h3>
        <h3>Account</h3>
        <h3>DPPA Reason</h3>

    </div>

    <div class="items-col">

        <h3><%= @record_incoming[:request_state] %></h3>
        <h3><%= @record_sent[:time_modified] %></h3>
        <h3><%= @record_incoming[:request_license] %></h3>
        <h3><%= @record_incoming[:account] %></h3>
        <h3><%= @record_incoming[:customers_reason] %></h3>

    </div>

</div>