Creating PDF using Prawn in Ruby on Rails

5 January 2012 by Aniket Joshi 15 comments

Recently i was working on a client project and i realized that i need to create a PDF for generating a invoice on the fly.
I wanted something that could be fast and easy to use.As i started searching my way for options, i found out that there are lot of gems and plugins available in the rails community for generating PDF.

There were two options for me which were widely used for generating PDF.

1. Prawnhttp://prawn.majesticseacreature.com/

2. PDFkithttps://github.com/jdpace/PDFKit

So i chose Prawn for this project and believe me adding this to my rails application wasn’t that difficult.

1. First thing that we need to do here is to add the prawn gem inside our gemfile and run the bundle command.

gem 'prawn'

2. Second thing that we need to do is that we need to create a PDF Mime::Type inside config/initializers/mime_types.rb that is because we need to notify rails about the PDF mime type.

Mime::Type.register “application/pdf”, :pdf

(Now when there is a pdf request our application can respond to it. )

3. Then we have to do couple of changes inside our controller action for which we need to return a pdf version.

This is how my Controller looks like.

class InvoicesController < ApplicationController

  before_filter :authenticate_customer!, :only => [:index, :show]

  def index
    @invoices = Invoice.all_invoices(current_customer)
  end

  def show
    @invoice = Invoice.find(params[:id])
    respond_to do |format|
      format.html
      format.pdf do
        pdf = InvoicePdf.new(@invoice, view_context)
        send_data pdf.render, filename: 
        "invoice_#{@invoice.created_at.strftime("%d/%m/%Y")}.pdf",
        type: "application/pdf"
      end
    end
  end
end

Here u can see inside my show controller i have created a respond block with html and pdf format.
And inside the format.pdf block there is a send_data method we are calling

send_data pdf.render, filename: 
"invoice_#{@invoice.created_at.strftime("%d/%m/%Y")}.pdf",
type: "application/pdf"

And what it does is, that it sends the data for the pdf document. we can have couple of options passed to this send data method. Like here i have passed a filename option to name the pdf document. and the type option.

One more option you can use here is to pass the disposition: “inline” after type and what this will do is instead of downloading a pdf it would open in the browser.

Now to work more on our PDF document, we will create a new class.
app/pdfs/inovice_pdf

Good way would be creating a new directory called pdfs inside our apps folder and then creating a new file called inovice_pdf (this is what i have created) and then create a class inside that called InvoicePdf and inherit that with a prawn document.

class InvoicePdf < Prawn::Document

end

then inside that we can have a initialize method

class InvoicePdf < Prawn::Document

  def initialize(invoice, view)
    super()
    text "This is an order invoice"
  end
end

here we gave a call to a super method and display the string that we need to show on our PDF.

4. Now remember the format.pdf block inside the show action in Invoices controller. inside that we have a line

pdf = InvoicePdf.new(@invoice, view_context)

Here we have instantiated the InvoicePdf. here @invoice is the invoice instance so it can be accessible inside our pdf document and to access to all the view helpers in our PDF document we would pass view_context as the argument.

5. Restart your application.

And we are ready to go. now we have to make the changes inside the InvoicePdf class for the styling that we need to apply to our PDF document.

6. Now inside our InvoicePdf class we need to set the @invoice and view_context.

def initialize(invoice, view)
  super()
  @invoice = invoice
  @view = view
  text "Invoice #{@invoice.id}"
end

7. Now you can create different method inside your InvoicePdf class as per what you want to show on your pdf.

For example to show your logo on the pdf we can have a logo method.

def logo
  logopath =  "#{Rails.root}/app/assets/images/logo.png"
  image logopath, :width => 197, :height => 91
end

and then give a call to that logo method inside your initialize method.and wow logo shows up on the pdf.

and this is how we can have separate method for separate section of our pdf.

For more styling options you can refer to http://prawn.majesticseacreature.com/manual.pdf .

I have created a standard invoice and have pasted the code below.You can modify it as you want your PDF to look like.

class InvoicePdf < Prawn::Document

  def initialize(invoice, view)
    super()
    @invoice = invoice
    @view = view
    logo
    thanks_message
    subscription_date
    subscription_details
    subscription_amount
    regards_message
  end

  def logo
    logopath =  "#{Rails.root}/app/assets/images/logo.png"
    image logopath, :width => 197, :height => 91
    move_down 10
    draw_text "Receipt", :at => [220, 575], size: 22
  end

  def thanks_message
    move_down 80
    text "Hello #{@invoice.customer.profile.first_name.capitalize},"
    move_down 15
    text "Thank you for your order.Print this receipt as 
    confirmation of your order.",
    :indent_paragraphs => 40, :size => 13
  end

  def subscription_date
    move_down 40
    text "Subscription start date: 
    #{@invoice.start_date.strftime("%d/%m/%Y")} ", :size => 13
    move_down 20
    text "Subscription end date :  
    #{@invoice.end_date.strftime("%d/%m/%Y")}", :size => 13
  end

  def subscription_details
    move_down 40
    table subscription_item_rows, :width => 500 do
      row(0).font_style = :bold
      columns(1..3).align = :right
      self.header = true
      self.column_widths = {0 => 200, 1 => 100, 2 => 100, 3 => 100}
    end
  end

  def subscription_amount
    subscription_amount = @invoice.calculate_subscription_amount
    vat = @invoice.calculated_vat
    delivery_charges = @invoice.calculated_delivery_charges
    sales_tax =  @invoice.calculated_sales_tax
    table ([["Vat (12.5% of Amount)", "", "", "#{precision(vat)}"] ,
    ["Sales Tax (10.3% of half the Amount)", "", "",
    "#{precision(sales_tax)}"]   ,
    ["Delivery charges", "", "", "#{precision(delivery_charges)}  "],
    ["", "", "Total Amount", "#{precision(@invoice.total_amount) }  "]]), 
    :width => 500 do
      columns(2).align = :left
      columns(3).align = :right
      self.header = true
      self.column_widths = {0 => 200, 1 => 100, 2 => 100, 3 => 100}
      columns(2).font_style = :bold
    end
  end

  def subscription_item_rows
    [["Description", "Quantity", "Rate", "Amount"]] +
    @invoice.subscriptions.map do |subscribe|
      [ "#{subscribe.description} ", subscribe.quantity, 
      "#{precision(subscribe.rate)}  ",  
      "#{precision(subscribe.quantity  * subscribe.rate)}" ]
    end
  end

  def precision(num)
    @view.number_with_precision(num, :precision => 2)
  end

  def regards_message
    move_down 50
    text "Thank You," ,:indent_paragraphs => 400
    move_down 6
    text "XYZ",
    :indent_paragraphs => 370, :size => 14, style:  :bold
  end

end

Now if we go to the address bar and type the url for the show action.

http://localhost:3000/invoices/1.pdf (press enter)

or you can have a link like this in your view
<%= link_to “Download invoice”, invoice_path(invoice.id, :format => “pdf”) %>;

the invoice would be downloaded. Remember at top i told you if you dont want to download and just show up in your browser just add this option to the send_data disposition: “inline.

send_data pdf.render, filename: 
"invoice_#{@invoice.created_at.strftime("%d/%m/%Y")}.pdf",
type: "application/pdf", disposition: "inline

I hope this helps you all who are looking out for quick generation of pdf. There is lot more to Prawn and i have tried to provide all information that i did while generating pdf.
That’s all there is to it.

Aniket Joshi

Delivery Director, Thinker & Tinkerer at Idyllic.

Follow me on Twitter

15 thoughts on “Creating PDF using Prawn in Ruby on Rails”

  1. Hi! Great post to get started on prawn, just one comment, on Prawn documentation they recommend to extend from Prawn::View instead of Prawn::Document they say it’s safer and it works to be independent of the state of Prawn::Document.

    Great post!

  2. Hello there! I could have sworn I’ve visited this website before but after going through
    some of the articles I realized it’s new to me. Nonetheless, I’m definitely happy I discovered it and I’ll be book-marking it and checking back regularly!

  3. Nice write up. Do you know a way to put some of those methods in an external file, so it would work in the same way as a partial? I have a couple of PDFs that have simial code, and want to DRY them up a bit

    1. Michael, you could extract some of that code into a model, then have a PdfInvoice-like class for each use case and include that module in each.

      1. Hi Ivor, yes I can see that with creating strings or values but if I have some pdf code that looks like that shown below, how would I put that in a model and get it returned to the Prawn class?

        def line_items
        move_down 110
        font_size 8
        table line_item_rows do
        row(0).font_style = :bold
        column(4).align = :right
        column(0).align = :right
        column(3).align = :right
        cells.border_width = 0.5
        self.row_colors = [“DDDDDD”, “FFFFFF”]
        self.header = true
        end
        end

        1. What I understand is that you want this line_items method to be rendered in various different classes that are used to render pdfs. I would suggest making a “module” that contains this method.

          module MySharedPdfMethods
          def line_items

          end
          end

          Then in your class:
          class OrderPdf < Prawn::Document
          include MySharedPdfMethods

          def some_method
          self.line_items
          end
          end

          So any class that includes the MySharedPdfMethods module will have that method. The line_items will probably assume that "self" will be an instance of Prawn::Document or a subclass so it would have ensure that for this to work.

          Does that make sense?

  4. I have used Prawn quite a bit. I was looking for a blog post to share with someone to get them started. This is such a great post. Well done, sir!

  5. This is a great write up. I’ve used Prawn on a couple occasions but usually a quick hack. This is a nice example of a clean implementation – thanks!

  6. Prawn is working in development but not in production. I get this Logs from production and loops “198.xxx.xxx.xxx: Connecting to database specified by database.yml”

  7. How does your controller know what InvoicePdf.new is? Mine errors saying ‘uninitialized constant’. Thanks for this write up, by the way! Great stuff!

    1. I got it. You need to add config.autoload_paths << "#{Rails.root}/app/pdfs" to application.rb and create config/initilizers/prawn.rb and at the top, require 'prawn'. Again, great stuff, man. Many thanks!

      1. Hi Dan thank you for liking this blog. i guess the reason you were getting this error was because that rails wasn’t able to find the invoice_pdf.rb and hence was complaining when u tried to instantiate the InvoicePdf class, but what you have done here is also a good way of having a separate prawn document and requiring it instead of inheriting the Prawn::Document.

Leave a Reply

Your email address will not be published. Required fields are marked *

Subscribe To Our Blog

Get access to proven marketing ideas, latest trends and best practices.

Next up home

Contact

Lets build cool stuff

Share your contact information & we will get in touch!

I want (Tell us more about your dream project)