Rails is simpler than Office

Posted by Nick Sieger Fri, 26 May 2006 03:32:00 GMT

Before my big blog drought at the beginning of the year, I had an entry queued up talking about some success I’d experienced with Rails. A lot in the Rails world has progressed since then, but I still think the story is worth documenting. Also, the code to generate a PDF of mailing labels may be useful to somebody out there.

I’ve had some Rails success lately building a home-use mailing list manager/rolodex application. There are plenty of ways that such a list could be maintained without resorting to a full web application framework such as Rails, but what the heck! The mailing list started life as an MS Access database; after my work computer was re-imaged I no longer had “access to Access” so it had a temporary layover in an Excel spreadsheet. Within the past couple of months I had moved it to a MySQL database as a way to nurture my fledgling Rails efforts.

Ok, so nothing real special so far, except that in order to print mailing labels (one of the primary reasons for keeping such a list) I’d have to export the names to a .csv file and do a mail merge with Word. Until the most recent mailing.

On a Saturday night I had the brainstorm to use Austin Ziegler’s PDF::Writer library to create a printable PDF directly from the Rails app, thus skipping the need to go through the mail merge rigamarole. Only a couple of hours of effort later, I had my mother-in-law’s Christmas mailing list printed out! Anyone who has ever done a mail merge with Word knows that clicking a single link to create the printable versions of the mailing labels is a huge improvement in usability. And finally, no MS bits were harmed in the production of this mailing!

My starting point in building the code to generate PDFs was this page in the Rails wiki. I decided to use the method that describes installing an “rpdf” template handler. Nowadays, you may as well use Josh Charles’ Rails PDF plugin, but for posterity I’ve packaged up my effort as a simple plugin as well (install into an existing Rails application with ./script/plugin install http://svn.caldersphere.net/svn/main/plugins/pdfrender).

With the plugin in place, all that’s necessary is a controller method to set up the data for the view, and the view code itself. The controller is as straightforward as you’d expect:

class AddressController < ApplicationController
  # ...

  def pdf
    @addresses = Address.find(:all, :order => 'last_name, first_name')
    render :layout => false

The view code is a little more hairy but with a little thought the dimensioning and layout code could easily be DRY’d out.

FONT = "Times-Roman"

COLS = 3

# margins: .5in top & bottom, 0.19 in left and right
# table column widths: 2.63in | 0.13in | 2.63in | 0.13in | 2.63in
# table rows: 1in height

MARG_X = pdf.in2pts 0.19
MARG_Y = pdf.in2pts 0.5

CELL_Y = pdf.in2pts 1
CELL_X = pdf.in2pts 2.63

COL_PAD_X = pdf.in2pts 0.19


CELL_PAD_X = pdf.in2pts 0.13
CELL_PAD_Y = pdf.in2pts 0.25

def cell_x(col)
  [COL1_X, COL2_X, COL3_X][col] + CELL_PAD_X

def cell_y(row, line)
  MARG_Y + ((LABELS_PER_COL - row) * CELL_Y) - CELL_PAD_Y - (line * CELL_LINE_Y)

def add_label(row, col, addr, pdf)
  if addr
    pdf.add_text_wrap(cell_x(col), cell_y(row, 0), CELL_X, addr.name, FONT_SIZE)
    pdf.add_text_wrap(cell_x(col), cell_y(row, 1), CELL_X, addr.address, FONT_SIZE)
    pdf.add_text_wrap(cell_x(col), cell_y(row, 2), CELL_X, "#{addr.city}, #{addr.state} #{addr.zip}", FONT_SIZE)


pages = @addresses.length / LABELS_PER_PAGE
pages += 1 if (@addresses.length % LABELS_PER_PAGE) > 0

0.upto(pages - 1) do |page|
  start = page * LABELS_PER_PAGE
  address_page = @addresses[start..start+LABELS_PER_PAGE]

  0.upto(LABELS_PER_COL - 1) do |row|
    add_label(row, 0, address_page[row*COLS], pdf)
    add_label(row, 1, address_page[row*COLS+1], pdf)
    add_label(row, 2, address_page[row*COLS+2], pdf)

  pdf.new_page unless page + 1 == pages

And that’s it! Avery labels in Rails!

Posted in ,  | Tags , ,  | no comments | no trackbacks