Nick Sieger: Tag pdfdo what you lovetag:blog.nicksieger.com,2005:TypoTypo2007-08-31T18:30:26+00:00Nick Siegerurn:uuid:3d364af1-4743-44e4-bbe9-248c750675ef2006-05-26T03:32:00+00:002007-08-31T18:30:26+00:00Rails is simpler than Office<p>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.</p>
<blockquote>
<p>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.</p>
<p>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.</p>
<p>On a Saturday night I had the brainstorm to use Austin Ziegler’s
<a href="http://ruby-pdf.rubyforge.org/">PDF::Writer</a> 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 <em>huge</em>
improvement in usability. And finally, no MS bits were harmed in
the production of this mailing!</p>
</blockquote>
<p>My starting point in building the code to generate PDFs was <a href="http://wiki.rubyonrails.com/rails/pages/HowtoGeneratePDFs">this page
in the Rails wiki</a>. I decided to use the method that
describes installing an “rpdf” template handler. Nowadays, you may as
well use <a href="http://rubyforge.org/projects/railspdfplugin/">Josh Charles’ Rails PDF plugin</a>, but for posterity
I’ve packaged up my effort as a <a href="http://svn.caldersphere.net/svn/main/plugins/pdfrender/">simple plugin</a> as well
(install into an existing Rails application with <code>./script/plugin
install http://svn.caldersphere.net/svn/main/plugins/pdfrender</code>).</p>
<p>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:</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">AddressController</span> <span class="punct"><</span> <span class="constant">ApplicationController</span>
<span class="comment"># ...</span>
<span class="keyword">def </span><span class="method">pdf</span>
<span class="attribute">@addresses</span> <span class="punct">=</span> <span class="constant">Address</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="symbol">:all</span><span class="punct">,</span> <span class="symbol">:order</span> <span class="punct">=></span> <span class="punct">'</span><span class="string">last_name, first_name</span><span class="punct">')</span>
<span class="ident">render</span> <span class="symbol">:layout</span> <span class="punct">=></span> <span class="constant">false</span>
<span class="keyword">end</span>
<span class="keyword">end</span></code></pre></div>
<p>The view code is a little more hairy but with a little thought the
dimensioning and layout code could easily be DRY’d out.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="constant">FONT</span> <span class="punct">=</span> <span class="punct">"</span><span class="string">Times-Roman</span><span class="punct">"</span>
<span class="constant">FONT_SIZE</span> <span class="punct">=</span> <span class="number">12</span>
<span class="constant">COLS</span> <span class="punct">=</span> <span class="number">3</span>
<span class="constant">LABELS_PER_PAGE</span> <span class="punct">=</span> <span class="number">30</span>
<span class="constant">LABELS_PER_COL</span> <span class="punct">=</span> <span class="number">10</span>
<span class="comment"># margins: .5in top & bottom, 0.19 in left and right</span>
<span class="comment"># table column widths: 2.63in | 0.13in | 2.63in | 0.13in | 2.63in</span>
<span class="comment"># table rows: 1in height</span>
<span class="constant">MARG_X</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">0.19</span>
<span class="constant">MARG_Y</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">0.5</span>
<span class="constant">CELL_Y</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">1</span>
<span class="constant">CELL_X</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">2.63</span>
<span class="constant">COL_PAD_X</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">0.19</span>
<span class="constant">COL1_X</span> <span class="punct">=</span> <span class="constant">MARG_X</span>
<span class="constant">COL2_X</span> <span class="punct">=</span> <span class="constant">COL1_X</span> <span class="punct">+</span> <span class="constant">CELL_X</span> <span class="punct">+</span> <span class="constant">COL_PAD_X</span>
<span class="constant">COL3_X</span> <span class="punct">=</span> <span class="constant">COL2_X</span> <span class="punct">+</span> <span class="constant">CELL_X</span> <span class="punct">+</span> <span class="constant">COL_PAD_X</span>
<span class="constant">CELL_PAD_X</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">0.13</span>
<span class="constant">CELL_PAD_Y</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">0.25</span>
<span class="constant">CELL_LINE_Y</span> <span class="punct">=</span> <span class="constant">FONT_SIZE</span> <span class="punct">+</span> <span class="number">2</span>
<span class="keyword">def </span><span class="method">cell_x</span><span class="punct">(</span><span class="ident">col</span><span class="punct">)</span>
<span class="punct">[</span><span class="constant">COL1_X</span><span class="punct">,</span> <span class="constant">COL2_X</span><span class="punct">,</span> <span class="constant">COL3_X</span><span class="punct">][</span><span class="ident">col</span><span class="punct">]</span> <span class="punct">+</span> <span class="constant">CELL_PAD_X</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">cell_y</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="ident">line</span><span class="punct">)</span>
<span class="constant">MARG_Y</span> <span class="punct">+</span> <span class="punct">((</span><span class="constant">LABELS_PER_COL</span> <span class="punct">-</span> <span class="ident">row</span><span class="punct">)</span> <span class="punct">*</span> <span class="constant">CELL_Y</span><span class="punct">)</span> <span class="punct">-</span> <span class="constant">CELL_PAD_Y</span> <span class="punct">-</span> <span class="punct">(</span><span class="ident">line</span> <span class="punct">*</span> <span class="constant">CELL_LINE_Y</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">add_label</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="ident">col</span><span class="punct">,</span> <span class="ident">addr</span><span class="punct">,</span> <span class="ident">pdf</span><span class="punct">)</span>
<span class="keyword">if</span> <span class="ident">addr</span>
<span class="ident">pdf</span><span class="punct">.</span><span class="ident">add_text_wrap</span><span class="punct">(</span><span class="ident">cell_x</span><span class="punct">(</span><span class="ident">col</span><span class="punct">),</span> <span class="ident">cell_y</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">0</span><span class="punct">),</span> <span class="constant">CELL_X</span><span class="punct">,</span> <span class="ident">addr</span><span class="punct">.</span><span class="ident">name</span><span class="punct">,</span> <span class="constant">FONT_SIZE</span><span class="punct">)</span>
<span class="ident">pdf</span><span class="punct">.</span><span class="ident">add_text_wrap</span><span class="punct">(</span><span class="ident">cell_x</span><span class="punct">(</span><span class="ident">col</span><span class="punct">),</span> <span class="ident">cell_y</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">1</span><span class="punct">),</span> <span class="constant">CELL_X</span><span class="punct">,</span> <span class="ident">addr</span><span class="punct">.</span><span class="ident">address</span><span class="punct">,</span> <span class="constant">FONT_SIZE</span><span class="punct">)</span>
<span class="ident">pdf</span><span class="punct">.</span><span class="ident">add_text_wrap</span><span class="punct">(</span><span class="ident">cell_x</span><span class="punct">(</span><span class="ident">col</span><span class="punct">),</span> <span class="ident">cell_y</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">2</span><span class="punct">),</span> <span class="constant">CELL_X</span><span class="punct">,</span> <span class="punct">"</span><span class="string"><span class="expr">#{addr.city}</span>, <span class="expr">#{addr.state}</span> <span class="expr">#{addr.zip}</span></span><span class="punct">",</span> <span class="constant">FONT_SIZE</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="keyword">end</span>
<span class="ident">pdf</span><span class="punct">.</span><span class="ident">select_font</span><span class="punct">(</span><span class="constant">FONT</span><span class="punct">)</span>
<span class="ident">pages</span> <span class="punct">=</span> <span class="attribute">@addresses</span><span class="punct">.</span><span class="ident">length</span> <span class="punct">/</span> <span class="constant">LABELS_PER_PAGE</span>
<span class="ident">pages</span> <span class="punct">+=</span> <span class="number">1</span> <span class="keyword">if</span> <span class="punct">(</span><span class="attribute">@addresses</span><span class="punct">.</span><span class="ident">length</span> <span class="punct">%</span> <span class="constant">LABELS_PER_PAGE</span><span class="punct">)</span> <span class="punct">></span> <span class="number">0</span>
<span class="number">0</span><span class="punct">.</span><span class="ident">upto</span><span class="punct">(</span><span class="ident">pages</span> <span class="punct">-</span> <span class="number">1</span><span class="punct">)</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">page</span><span class="punct">|</span>
<span class="ident">start</span> <span class="punct">=</span> <span class="ident">page</span> <span class="punct">*</span> <span class="constant">LABELS_PER_PAGE</span>
<span class="ident">address_page</span> <span class="punct">=</span> <span class="attribute">@addresses</span><span class="punct">[</span><span class="ident">start</span><span class="punct">..</span><span class="ident">start</span><span class="punct">+</span><span class="constant">LABELS_PER_PAGE</span><span class="punct">]</span>
<span class="number">0</span><span class="punct">.</span><span class="ident">upto</span><span class="punct">(</span><span class="constant">LABELS_PER_COL</span> <span class="punct">-</span> <span class="number">1</span><span class="punct">)</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">row</span><span class="punct">|</span>
<span class="ident">add_label</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">0</span><span class="punct">,</span> <span class="ident">address_page</span><span class="punct">[</span><span class="ident">row</span><span class="punct">*</span><span class="constant">COLS</span><span class="punct">],</span> <span class="ident">pdf</span><span class="punct">)</span>
<span class="ident">add_label</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">1</span><span class="punct">,</span> <span class="ident">address_page</span><span class="punct">[</span><span class="ident">row</span><span class="punct">*</span><span class="constant">COLS</span><span class="punct">+</span><span class="number">1</span><span class="punct">],</span> <span class="ident">pdf</span><span class="punct">)</span>
<span class="ident">add_label</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">2</span><span class="punct">,</span> <span class="ident">address_page</span><span class="punct">[</span><span class="ident">row</span><span class="punct">*</span><span class="constant">COLS</span><span class="punct">+</span><span class="number">2</span><span class="punct">],</span> <span class="ident">pdf</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="ident">pdf</span><span class="punct">.</span><span class="ident">new_page</span> <span class="keyword">unless</span> <span class="ident">page</span> <span class="punct">+</span> <span class="number">1</span> <span class="punct">==</span> <span class="ident">pages</span>
<span class="keyword">end</span></code></pre></div>
<p>And that’s it! Avery labels in Rails!</p>