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
end
end
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"
FONT_SIZE = 12
COLS = 3
LABELS_PER_PAGE = 30
LABELS_PER_COL = 10
# 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
COL1_X = MARG_X
COL2_X = COL1_X + CELL_X + COL_PAD_X
COL3_X = COL2_X + CELL_X + COL_PAD_X
CELL_PAD_X = pdf.in2pts 0.13
CELL_PAD_Y = pdf.in2pts 0.25
CELL_LINE_Y = FONT_SIZE + 2
def cell_x(col)
[COL1_X, COL2_X, COL3_X][col] + CELL_PAD_X
end
def cell_y(row, line)
MARG_Y + ((LABELS_PER_COL - row) * CELL_Y) - CELL_PAD_Y - (line * CELL_LINE_Y)
end
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)
end
end
pdf.select_font(FONT)
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)
end
pdf.new_page unless page + 1 == pages
end
And that’s it! Avery labels in Rails!