homeASCIIcasts

153: PDFs With Prawn 

(view original Railscast)

In this episode we’re going to show you how to generate PDFs from Rails Applications. Below is the order page from an e-commerce application. What we’d like to be able to do is add a link to it that will allow the customer to download the order as a PDF.

The order page for our e-commerce application.

Railscast 78 showed how to generate PDFs with the PDF::Writer gem. That is still a good way to generate PDF files, but there is now a much newer gem called Prawn available and all the cool kids are using that. Prawn is a very fast Ruby PDF generator that is installed as as gem, but it isn’t specifically targeted towards Rails. There is a Rails plugin called Prawnto, however, that makes it easier to work with Prawn in Rails. It adds a new template handler class that will process any views with a .prawn extension, so we can generate PDF files in the same way we generate any other file format.

Installing Prawn and Prawnto

To start off we’ll need to install the prawn gem. It’s installed in the usual way.

sudo gem install prawn

Once we have Prawn installed we can install the Prawnto plugin from GitHub. In our application’s directory we just run the following comand.

script/plugin install git://github.com/thorny-sun/prawnto.git

Now we’re all set and can start generating PDFs.

Generating our Order

In our e-commerce application the show page for the order renders the order summary as an HTML table. For the PDF version we’ll need to create another view file. Note that unless we already have a respond_to block in our order controller there’s no need to add a new block for the PDF format, we can just create the new view template. The template file should be called show.pdf.prawn, pdf for the file format we’re generating and prawn for the template handler that will generate the file.

prawnto gives us a pdf object that we can use in the view code. We can call any Prawn method on this object to start creating our PDF. We’ll start off simply by using the text method to print “Hello, World!”.

pdf.text "Hello, World!"

We can view our PDF file by just adding .pdf to the URL of the order page. We should then see the generated PDF.

Our first Hello, World! PDF

Populating The Order Data

Obviously we want our order information on the screen rather than “Hello, World!”. At the top of the page we’ll put the order number, which we’ll render in 30 point, bold text.

pdf.text "Order ##{@order.id}", :size => 30, :style => :bold

Below that we’ll want to render our list of products.

@order.cart.line_items.each do |item|
  pdf.text item.product.name
end

We could just render the names out as a list, but it would look better if we displayed them as a table along with the price and the quantity bought. Prawn has support for rendering tables with the pdf.table method, which takes a two-dimensional array of items to render in cells. To create the array we’ll loop through the line items in the order’s cart and use map on each item.

items = @order.cart.line_items.map do |item|
  [
		item.product.name,
		item.quantity,
		item.unit_price,
		item.full_price
  ]
end
pdf.table items

When we reload our PDF file now we’ll see the order’s items in a table.

Our orders are now shown in a table.

Formatting The Table

Our order’s items are now listed but the table looks quite basic. Fortunately, the pdf.table method takes a lot of parameters that enable us to customise the table to suit our needs.

pdf.table items, :border_style => :grid,
  :row_colors => ["FFFFFF", "DDDDDD"],
  :headers => ["Product", "Qty", "Unit Price", "Full Price"],
  :align => { 0 => :left, 1 => :right, 2 => :right, 3 => :right }

The complete list of parameters can be found on the Prawn documentation pages. We’re going to add some to render the borders as a grid; to provide alternating background colours for the rows; to supply header text and to align the cells so that the three numeric columns are right-aligned. Reloading the document again shows the changes.

The table is now formatted better.

Adding Spacing

Although the table looks better now, it’s pushed up against the header. We can adjust the gap by using move_down. We’ll move it down by 30 points by adding the following line between the header and table.

pdf.move_down(30)

The next thing we’ll want to add to our table is the total price of the order. To do that we’ll just use move_down again then add the text in 16 point bold.

pdf.move_down(10)
pdf.text "Total Price: #{@order.cart.total_price}", :size => 16, :style => :bold

Formatting The Currency Fields

The PDF order is nearly there now, but the currency fields aren’t formatted correctly. One of the benefits of using Prawnto with a PDF template is that it gives us access to all of Rails’ helper methods so we can use number_to_currency to format the fields. The final code is below.

pdf.text "Order##{@order.id}", :size => 30, :style => :bold
pdf.move_down(30)
items = @order.cart.line_items.map do |item|
  [
		item.product.name,
		item.quantity,
		number_to_currency(item.unit_price, :unit => "£"),
		number_to_currency(item.full_price, :unit => "£")
  ]
end
pdf.table items, :border_style => :grid,
  :row_colors => ["FFFFFF", "DDDDDD"],
  :headers => ["Product", "Qty", "Unit Price", "Full Price"],
  :align => { 0 => :left, 1 => :right, 2 => :right, 3 => :right }

pdf.move_down(10)
pdf.text "Total Price: #{number_to_currency(@order.cart.total_price, :unit => '£')}", :size => 16, :style => :bold

Note that, unlike when we’re generating HTML, we don’t have to escape currency symbols such as the pound sign (£). The final PDF document now looks like we want it to.

Our final PDF order.

Adding The Link

The final thing we have to do is add a link from the HTML order page to the PDF one. We’ll just have to add a normal link_to to the PDF, but there’s a slight change from Rails 2.2 to 2.3 that changes the way the link’s path is written. In Rails 2.2 and below we’d use

link_to "Printable Invoice (PDF)", formatted_order_path(@order, :pdf)

Rails 2.3 has removed formatted_x_path. Instead we just use order_path and provide the format as a parameter. Removing the formatted path routes saves memory as it reduces the number of named routes in the application.

link_to "Printable Invoice (PDF)", order_path(@order, :format => "pdf")

Our customers can now click a link on the order page to be taken to the printable PDF version.

Passing Options to Prawnto

In the controller we can specify options to pass options to Prawnto to control how the PDF is generated. If, for example, we want to add a 75 point margin we can add

prawnto :prawn => { :top_margin => 75 }

at the top of our OrdersController. More details about the options you can pass are available on the Prawnto documentation pages.