#153 PDFs with Prawn
- Download:
- source codeProject Files in Zip (121 KB)
- mp4Full Size H.264 Video (14.6 MB)
- m4vSmaller H.264 Video (10.2 MB)
- webmFull Size VP8 Video (27.7 MB)
- ogvFull Size Theora Video (20.4 MB)
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.
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.
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.
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.
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.
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.