﻿<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>acts_as_developer &#187; Ruby</title>
	<atom:link href="http://kevinelliott.net/blogs/acts_as_developer/category/languages/ruby/feed/" rel="self" type="application/rss+xml" />
	<link>http://kevinelliott.net/blogs/acts_as_developer</link>
	<description>Adventures in Software Development Patterns, Frameworks, Techniques, and Prototyping</description>
	<lastBuildDate>Wed, 16 Mar 2011 20:42:05 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Project Euler: Problem1/Variation2 &#8211; Add all the natural numbers below one thousand that are multiples of 3 or 5</title>
		<link>http://kevinelliott.net/blogs/acts_as_developer/2011/03/16/project-euler-problem1variation2-add-all-the-natural-numbers-below-one-thousand-that-are-multiples-of-3-or-5/</link>
		<comments>http://kevinelliott.net/blogs/acts_as_developer/2011/03/16/project-euler-problem1variation2-add-all-the-natural-numbers-below-one-thousand-that-are-multiples-of-3-or-5/#comments</comments>
		<pubDate>Wed, 16 Mar 2011 20:40:29 +0000</pubDate>
		<dc:creator>kevin</dc:creator>
				<category><![CDATA[Languages]]></category>
		<category><![CDATA[Practicing]]></category>
		<category><![CDATA[Project Euler]]></category>
		<category><![CDATA[Ruby]]></category>

		<guid isPermaLink="false">http://kevinelliott.net/blogs/acts_as_developer/?p=137</guid>
		<description><![CDATA[My previous post contained my first variation for solving Problem 1 of Project Euler. A basic loop was intentionally used to illustrate a brute force method of determining the answer. The solution is easy to understand and the code is very simple. For Variation 2, I decided to simply refactor Variation 1 so that it [...]]]></description>
			<content:encoded><![CDATA[<p>My previous post contained my first variation for solving Problem 1 of Project Euler. A basic loop was intentionally used to illustrate a brute force method of determining the answer. The solution is easy to understand and the code is very simple. For Variation 2, I decided to simply refactor Variation 1 so that it was more reusable and more clear. I&#8217;ll explain what was discovered after the code dump:</p>
<pre class="brush: ruby; title: ; notranslate">
#!/usr/bin/ruby
#
# Project Euler
# Problem #1 - Add all the natural numbers below one thousand that are multiples of 3 or 5.
# Solution #2 - refactored loop
#
# Copyright 2011, Kevin Elliott, kevin@phunc.com
#

# Definitions
floor = 1
ceiling = 999999
multiples_of = [3, 5]

class DivisibleDetector

  # Multiple of a value?
  def self.multiple?(i, multiple)
    (i % multiple) == 0
  end

  # Multiple of one of the values in an array?
  def self.multiple_of_array?(i, multiples)
    is_multiple = false
    multiples.each do |m|
      if self.multiple?(i, m)
        is_multiple = true
        break
      end
    end
    is_multiple
  end

  def self.find_divisible(start=1, stop=999, multiples=[])
    results = []
    (start..stop).each do |i|
      results &lt;&lt; i if self.multiple_of_array?(i, multiples)
    end
    results
  end

  def self.sum(results)
    sum = 0
    results.each { |r| sum += r }
    sum
  end

end

# Main
results = DivisibleDetector.find_divisible(floor, ceiling, multiples_of)
puts &quot;Numbers below &quot;+ceiling.to_s+&quot; that are multiples of 3 or 5:&quot;
results.each do |r|
  puts &quot;  &quot;+r.to_s
end
puts &quot;Sum = &quot; + DivisibleDetector.sum(results).to_s
</pre>
<p>As expected, I noticed that Variation 2 was slightly slower than Variation 1, because it introduces several method calls as well as a new inner loop for checking to see if the iterating value is a multiple of multiple values in an array. While this allows for flexibility (say, for example, you add a few more numbers to check for divisibility by) and modularity, it introduces complexity and a slight performance degradation.</p>
<p>Aside from using a loop as a strategy for solving this problem, the structure and format of Variation 2 is &#8220;the Right Way&#8221; to structure your code. But remember this comes at a cost of complexity and performance degradation. To most this is a small price to pay to have a consistent structure for organizing your code.</p>
<p>Variation 3 will attack the problem from a whole other perspective, rather than simply refactoring a previous variation.</p>
 <p><a href="http://kevinelliott.net/blogs/acts_as_developer/?flattrss_redirect&amp;id=137&amp;md5=016f051f88a834fa323171f4713f873a" title="Flattr" target="_blank"><img src="http://kevinelliott.net/blogs/acts_as_developer/wp-content/plugins/flattr/img/flattr-badge-large.png" alt="flattr this!"/></a></p>]]></content:encoded>
			<wfw:commentRss>http://kevinelliott.net/blogs/acts_as_developer/2011/03/16/project-euler-problem1variation2-add-all-the-natural-numbers-below-one-thousand-that-are-multiples-of-3-or-5/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<atom:link rel="payment" href="http://kevinelliott.net/blogs/acts_as_developer/?flattrss_redirect&amp;id=137&amp;md5=016f051f88a834fa323171f4713f873a" type="text/html" />
	</item>
		<item>
		<title>Project Euler: Problem1/Variation1 &#8211; Add all the natural numbers below one thousand that are multiples of 3 or 5</title>
		<link>http://kevinelliott.net/blogs/acts_as_developer/2011/03/16/project-euler-problem1variation1-add-all-the-natural-numbers-below-one-thousand-that-are-multiples-of-3-or-5/</link>
		<comments>http://kevinelliott.net/blogs/acts_as_developer/2011/03/16/project-euler-problem1variation1-add-all-the-natural-numbers-below-one-thousand-that-are-multiples-of-3-or-5/#comments</comments>
		<pubDate>Wed, 16 Mar 2011 19:39:07 +0000</pubDate>
		<dc:creator>kevin</dc:creator>
				<category><![CDATA[Languages]]></category>
		<category><![CDATA[Practicing]]></category>
		<category><![CDATA[Project Euler]]></category>
		<category><![CDATA[Ruby]]></category>

		<guid isPermaLink="false">http://kevinelliott.net/blogs/acts_as_developer/?p=123</guid>
		<description><![CDATA[Problem 1 on Project Euler was posted on October 5th, 2001 (just days before my birthday). I&#8217;m solving the problem nearly 10 years later! And since it&#8217;s one of the early ones, it&#8217;s quite simple. The problem description is as follows: If we list all the natural numbers below 10 that are multiples of 3 [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://projecteuler.net/index.php?section=problems&amp;id=1">Problem 1</a> on <a href="http://projecteuler.net/">Project Euler</a> was posted on October 5th, 2001 (just days before my birthday). I&#8217;m solving the problem nearly 10 years later! And since it&#8217;s one of the early ones, it&#8217;s quite simple. The problem description is as follows:</p>
<blockquote><p>If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.</p>
<p>Find the sum of all the multiples of 3 or 5 below 1000.</p></blockquote>
<p>In honor of code katas and for the sake of reinforcing knowledge I already have, I will do several variations of solutions for this problem. I will try to intentionally choose the most unoptimized solution to start with (often a for loop) and work towards a solution that can scale to 1 million numbers and still be executed quickly. This will remind me of why certain solutions are poor, and give me an opportunity to slowly ramp into a good solution by exposing myself to different parts of the problem that surface when you evaluate the previous solution.</p>
<p>For the first variation, I chose to use a loop to cycle through all 999 numbers. The reason we&#8217;re only using 999 numbers is that the problem specifically states that it wants all natural numbers below 1000, which excludes 1000 itself. I am then applying modulus of 3 and 5 independently to the current iteration value and looking for a remainder. A result of 0 tells me that the number is evenly divisible by the number it is checked against (3 or 5). In these cases, I&#8217;m storing the current iteration value to an array for summation later on. If the current iteration value is evenly divisible by 3 and 5, we don&#8217;t want to add it to the array twice (which will throw off our summation) so we&#8217;re simply or&#8217;ing the modulus results.</p>
<p><strong>Here&#8217;s the solution:</strong></p>
<pre class="brush: ruby; title: ; notranslate">
#!/usr/bin/ruby
#
# Project Euler
# Problem #1 - Add all the natural numbers below one thousand that are multiples of 3 or 5.
# Solution #1 - for loop
#
# Copyright 2011, Kevin Elliott, kevin@phunc.com
#

# Definitions
floor = 1
ceiling = 1000
multiples_of = [3, 5]

# Initializations
results = []

# Calculate
puts &quot;Numbers below &quot;+ceiling.to_s+&quot; that are multiples of 3 or 5:&quot;
(floor..ceiling-1).each do |i|
  if ((i % 3) == 0) || ((i % 5) == 0)
    results &amp;lt;&amp;lt; i
    puts &quot;  &quot;+i.to_s
  end
end

# Sum
sum = 0
results.each do |r|
  sum += r
end
puts &quot;Sum = &quot; + sum.to_s
</pre>
<p>Keep in mind that I have intentionally made it readable, because readable code is more useful to other people than super obfuscated and optimized code. I prefer to have code document itself, but for the sake of showing others I have put in some commenting. There are glaring ways to improve this, which I will begin to do in Problem1/Solution2.</p>
<p>What does your code look like?</p>
 <p><a href="http://kevinelliott.net/blogs/acts_as_developer/?flattrss_redirect&amp;id=123&amp;md5=777c38c663e02031f19b8a68d5b2dfa5" title="Flattr" target="_blank"><img src="http://kevinelliott.net/blogs/acts_as_developer/wp-content/plugins/flattr/img/flattr-badge-large.png" alt="flattr this!"/></a></p>]]></content:encoded>
			<wfw:commentRss>http://kevinelliott.net/blogs/acts_as_developer/2011/03/16/project-euler-problem1variation1-add-all-the-natural-numbers-below-one-thousand-that-are-multiples-of-3-or-5/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<atom:link rel="payment" href="http://kevinelliott.net/blogs/acts_as_developer/?flattrss_redirect&amp;id=123&amp;md5=777c38c663e02031f19b8a68d5b2dfa5" type="text/html" />
	</item>
		<item>
		<title>Refactoring a Ruby on Rails Navigation Helper</title>
		<link>http://kevinelliott.net/blogs/acts_as_developer/2010/09/27/refactoring-a-ruby-on-rails-navigation-helper/</link>
		<comments>http://kevinelliott.net/blogs/acts_as_developer/2010/09/27/refactoring-a-ruby-on-rails-navigation-helper/#comments</comments>
		<pubDate>Mon, 27 Sep 2010 21:25:57 +0000</pubDate>
		<dc:creator>kevin</dc:creator>
				<category><![CDATA[Refactoring]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Ruby on Rails]]></category>

		<guid isPermaLink="false">http://kevinelliott.net/blogs/acts_as_developer/?p=52</guid>
		<description><![CDATA[I was going through some code in an older Rails application&#8217;s code base of mine in order to migrate it from 2.1.x to 2.3.9 and finally to 3.0.0. My primary task at hand was to migrate the code to Rails 3.0.0, so I avoided getting too sidetracked into modifying code that didn&#8217;t need it specifically [...]]]></description>
			<content:encoded><![CDATA[<p>I was going through some code in an older Rails application&#8217;s code base of mine in order to <strong>migrate it from 2.1.x to 2.3.9 and finally to 3.0.0</strong>. My primary task at hand was to migrate the code to Rails 3.0.0, so I avoided getting too sidetracked into modifying code that didn&#8217;t need it specifically for the upgrades. However, I made a note of some areas where <strong>I saw sprawling code that could use a touch up</strong>.</p>
<p>If you are anything like me, when you look at code from several years ago, you realize that you&#8217;ve 1) grown a lot as a developer, and 2) learned new strategies and techniques that you can now apply. This never ends; over the years you will continue to grow and learn new technologies. You will certainly see some implementations of code where you think to yourself <em>&#8220;Wow, did I write that?&#8221;</em>, and instead of getting frustrated with it, take it on as a great opportunity to flex what you&#8217;ve learned since then, and <strong>clean it up</strong>!</p>
<p>Once I was done migrating the application to Rails 3.0.0 and everything was working properly, I began to refactor areas I made a note of needing some attention. Here&#8217;s an example of a Ruby on Rails helper that I originally wrote to help construct a navigation menu using <strong>content_tag</strong> to generate a <strong>&lt;ul&gt;</strong> and embedded <strong>&lt;li&gt;</strong> elements. There is CSS used to style these elements, but for the sake of this refactoring, I&#8217;ll leave that off.</p>
<pre class="brush: ruby; title: ; notranslate">module ApplicationHelper

  def navigation(links=[],options={})
    id_string = options[:id] ? options[:id] : &quot;nav&quot;
    selected_class = options[:selected_class] ? options[:selected_class] : &quot;selected&quot;
    controller_only = (options[:controller_only] &amp;&amp; options[:controller_only] == true)
    items = []
    if (controller_only)
      links.each do |link|
        if (controller.controller_name.to_sym == link[:path])
          items &lt;&lt; content_tag(:li, link_to(link[:text], link[:path]), :class =&gt; selected_class)
        else
          items &lt;&lt; content_tag(:li, link_to(link[:text], link[:path]))
        end
      end
    else
      links.each do |link|
        if (request.path == link[:path])
          items &lt;&lt; content_tag(:li, link_to(link[:text], link[:path]), :class =&gt; selected_class)
        else
          items &lt;&lt; content_tag(:li, link_to(link[:text], link[:path]))
        end
      end
    end
    content_tag :ul, items, :id =&gt; id_string
  end
end
</pre>
<p>Wow, that&#8217;s quite a sprawling method now isn&#8217;t it? At first glance you might even struggle to understand what I was trying to do. There is some obvious misuse of repetitive code, and excessive if blocks. Embarrassing! (But don&#8217;t worry, you&#8217;ve got cruddy code like this littered in your projects too, so don&#8217;t get snarky yet.)</p>
<p><strong>To clean this up we&#8217;re going to do four things:</strong></p>
<ol>
<li>Remove the object assignments at the top of the method, since we already have the data in the options hash. No need to explicitly pull out values and store them again in order to use them in our method.</li>
<li>Replace the if statements with simpler and condensed notation.</li>
<li>Destroy repetitive code. Remember, DRY &#8230; Do not Repeat Yourself.</li>
<li>Use <b>concat</b> instead of an array that is converted to strings later.</li>
</ol>
<p>And here&#8217;s the results&#8230;</p>
<pre class="brush: ruby; title: ; notranslate">
module ApplicationHelper
   def navigation(links=[],options={})
     path = (options[:controller_only] == true) ? controller.controller_name.to_sym : request.path
     klass = (path == link[:path]) ? {:class =&gt; options[:selected_class] || 'selected'} : nil
     content_tag :ul, :id =&gt; (options[:id] || 'nav') do
     links.each do |link|
       concat content_tag(:li, link_to(link[:text], link[:path]), klass)
     end
  end
end
</pre>
<p>Much cleaner, right? It&#8217;s more legible, more clear, and actually performs better (although in this particular case performance is not much of a concern, since this method is only called once per request). Can you think of other ways to improve upon this refactoring?</p>
 <p><a href="http://kevinelliott.net/blogs/acts_as_developer/?flattrss_redirect&amp;id=52&amp;md5=e007aefc6360120ad178f750144241a1" title="Flattr" target="_blank"><img src="http://kevinelliott.net/blogs/acts_as_developer/wp-content/plugins/flattr/img/flattr-badge-large.png" alt="flattr this!"/></a></p>]]></content:encoded>
			<wfw:commentRss>http://kevinelliott.net/blogs/acts_as_developer/2010/09/27/refactoring-a-ruby-on-rails-navigation-helper/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<atom:link rel="payment" href="http://kevinelliott.net/blogs/acts_as_developer/?flattrss_redirect&amp;id=52&amp;md5=e007aefc6360120ad178f750144241a1" type="text/html" />
	</item>
		<item>
		<title>Script It: Convert A List Apart articles to PDFs</title>
		<link>http://kevinelliott.net/blogs/acts_as_developer/2009/04/28/script-it-convert-a-list-apart-articles-to-pdfs/</link>
		<comments>http://kevinelliott.net/blogs/acts_as_developer/2009/04/28/script-it-convert-a-list-apart-articles-to-pdfs/#comments</comments>
		<pubDate>Wed, 29 Apr 2009 01:45:08 +0000</pubDate>
		<dc:creator>kevin</dc:creator>
				<category><![CDATA[Languages]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[alistapart]]></category>
		<category><![CDATA[hpricot]]></category>
		<category><![CDATA[osx]]></category>
		<category><![CDATA[pdf]]></category>
		<category><![CDATA[script]]></category>

		<guid isPermaLink="false">http://kevinelliott.net/blogs/acts_as_developer/?p=9</guid>
		<description><![CDATA[Several times a week, I find that I need to automate a task to save me considerable amounts of time. The following is an example of this need. I wanted to share the thinking behind the task and the code with you. Improvements and suggestions are welcome. Submit them as comments to this article. This [...]]]></description>
			<content:encoded><![CDATA[<p><em>Several times a week, I find that I need to automate a task to save me considerable amounts of time. The following is an example of this need. I wanted to share the thinking behind the task and the code with you. Improvements and suggestions are welcome. Submit them as comments to this article.</em></p>
<p>This particular task took about 1 hour to complete, which includes some research, coming up with a plan of attack, and then implementing. </p>
<h3>Objective</h3>
<p><a href="http://www.alistapart.com/">A List Apart</a> is an ezine published by a small consortium of web design experts who pride themselves in focusing on typography, usability, simplicity, and elegance. I have been a fan of their ezine for many years now, but realized that due to my busy life I have missed a considerable number of articles and entire issues. I recently decided that it would benefit me greatly if I were to catch up on all the articles that I&#8217;ve missed. They&#8217;ve kindly provided an archive of all of their past issues, but they do not offer a PDF version of them. I needed a simple way to most effectively retrieve all of the issues and then convert them to PDF format in a printer friendly layout (we don&#8217;t want a screen format). Creating a script will make it easy to print each article out in bulk, and potentially join them all up into an eBook for further reading on an Amazon Kindle 2 for example. Automating this is a much better use of my time than clicking on a thousand articles and clicking &#8220;Print&#8221; (more likely, I&#8217;d have to also then click &#8220;Save as PDF&#8230;&#8221;, find the new file a home on my hard drive, and then name it).</p>
<h3>General Process</h3>
<p>A basic high-level process to make this happen might look something like:</p>
<ol>
<li>Retrieve a list of all issues.</li>
<li>Retrieve a list of all articles for each issue. (If possible, combine both steps into one)</li>
<li>Fetch the contents of each article in print format.</li>
<li>Convert each article to PDF format.</li>
</ol>
<p></p>
<h3>Basic Design</h3>
<p>The plan of attack will be to design a class that is dedicated to communicating with the A List Apart website. This class will fetch the list of issues and articles. Next, some simple code will harness the class to retrieve the information that is necessary, ultimately returning some issue numbers, article titles, and URLs to retrieve the articles. Lastly, the URLs will be passed into a class called PDFerator that was built by <a href="mailto:pete@notahat.com">pete@notahat.com</a> which uses OS X frameworks to simulate printing the article in a hidden web browser.</p>
<h3>Code</h3>
<p><em>Pre-Requisites: In order to run this code, you MUST be running OS X 10.5 on an Apple Mac in order to take advantage of the Print to PDF features.</em></p>
<h5>AListApartFetcher Class</h5>
<p>The general strategy for this class is to encapsulate all of the code necessary to communicate with the AListApart.com website. Since they do not offer any web services APIs, I used <a href="http://wiki.github.com/why/hpricot">Why&#8217;s Hpricot</a> to screen scrape the information I needed. In order to come up with the xpathing used with Hpricot to find the elements I was looking for, I used <a href="http://www.selectorgadget.com/">SelectorGadget</a> to analyze the AListApart.com pages.</p>
<pre class="brush: ruby; title: A List Apart Fetcher Class; notranslate">
#!/usr/bin/ruby

require 'rubygems'
require 'hpricot'
require 'open-uri'

class AListApartFetcher
  attr_accessor :all_issue_urls, :all_article_urls
  attr_accessor :base_url, :main_url, :yaml_filename
  attr_accessor :page_count, :issue_count, :article_count
  attr_accessor :debug

  def initialize
    @debug = true
    @base_url = &quot;http://alistapart.com&quot;
    @main_url = &quot;http://alistapart.com/articles&quot;
    @yaml_filename = &quot;alistapart.yaml&quot;
  end

  # Retrieve a page of Issue URLs
  def retrieve_page(page_url)
    issue_urls = []
    count = 0

    doc = Hpricot(open(page_url))
    (doc/&quot;.ishno&quot;).each do |a|
      count += 1
      name = a.inner_text
      url = a.attributes[&quot;href&quot;]
      puts &quot;  - #{name} =&gt; #{url}&quot;
      article_urls = retrieve_issue(&quot;http://alistapart.com#{url}&quot;)
      issue_urls &lt;&lt; { :name =&gt; name, :url =&gt; url, :article_urls =&gt; article_urls }
    end
    issue_urls
  end

  # Retrieve a page of Article URLs for a given Issue
  def retrieve_issue(issue_url)
    article_urls = []
    count = 0

    doc = Hpricot(open(issue_url))
    doc = (doc/&quot;#content&quot;)
    (doc/&quot;.title/a&quot;).each do |a|
      count += 1
      name = a.inner_text
      url = a.attributes[&quot;href&quot;]
      puts &quot;    - #{name} =&gt; #{url}&quot;
      article_urls &lt;&lt; { :name =&gt; name, :url =&gt; url }
    end
    article_urls
  end

  def retrieve_remote
    puts &quot;Loading data from remote.&quot;

    run = true
    @page_count = 0
    all_issue_urls = []

    # Loop until all pages retrieved
    while run
      page_url = @page_count ? &quot;#{@main_url}?page=#{@page_count + 1}&quot; : @main_url
      puts &quot;Retrieving page #{@page_count + 1}: #{page_url}&quot;
      issue_urls = retrieve_page(page_url)
      if issue_urls.size &gt; 0
        fetched_end = false
        issue_urls.each do |i|
          if i[:name] == &quot;Issue 8&quot;
            fetched_end = true
          end
        end

        all_issue_urls.concat issue_urls
        @page_count += 1

        if fetched_end
          run = false
        end
      else
        run = false
      end
    end
    all_issue_urls
  end

  def load_yaml
    puts &quot;Loading data from YAML file.&quot;
    @all_issue_urls = YAML.load_file(@yaml_filename)
    @article_urls_count = @all_issue_urls.inject(0) {|sum, n| sum + (n[:article_urls].size) }
    puts &quot;Loaded #{@all_issue_urls.size} issue URLs.&quot;
    puts &quot;Loaded #{@article_urls_count} article URLs.&quot;
  end

  def save_yaml
    puts &quot;Saving data to YAML file.&quot;
    File.open(@yaml_filename, 'w') do |out|
      YAML.dump(@all_issue_urls, out )
    end
  end

  def self.clean_string(string)
    chars_to_replace = { ' ' =&gt; '_', '?' =&gt; '', &quot;'&quot; =&gt; '', '(' =&gt; '', ')' =&gt; '', '&amp;' =&gt; '', ';' =&gt; '', '/' =&gt; '', '!' =&gt; '' }
    string.split(//).map { |c| chars_to_replace.has_key?(c) ? chars_to_replace1 : c }.join
  end

  def self.create_article_filename(issue_name, article_name, extension)
    issue_name = clean_string(issue_name)
    article_name = clean_string(article_name)
    &quot;#{issue_name}-#{article_name}.#{extension}&quot;
  end
end
</pre>
<h5>Main Loop</h5>
<p>This code was written to live in the same ruby file as the AListApartFetcher class (i.e. alistapart.rb) but could be anywhere. Just be sure to require the necessary gems and ruby class above. For sake of brevity, I did not provide a command line toggle for whether or not this script fetches the issue/article list remotely or from the local YAML file that is created during each run. You can change it by setting &#8220;remote&#8221; to &#8220;false&#8221;.</p>
<pre class="brush: ruby; title: ; notranslate">
# MAIN CODE

remote = true
all_issue_urls = []

fetcher = AListApartFetcher.new
if remote
  all_issue_urls = fetcher.retrieve_remote
  article_urls_count = all_issue_urls.inject(0) {|sum, n| sum + (n[:article_urls].size) }
  puts &quot;Retrieved #{fetcher.page_count} pages.&quot;
  puts &quot;Retrieved #{all_issue_urls.size} issue URLs.&quot;
  puts &quot;Retrieved #{article_urls_count} article URLs.&quot;

  fetcher.save_yaml
else
  fetcher.load_yaml
  all_issue_urls = fetcher.all_issue_urls
end

puts &quot;Article Filenames&quot;
all_issue_urls.each do |issue|
  issue[:article_urls].each do |article|
    filename = AListApartFetcher.create_article_filename(issue[:name], article[:name], 'pdf')
    generator_command = &quot;./pdf.rb http://alistapart.com#{article[:url]} #{filename}&quot;
    puts &quot;Generating #{filename} with cmd: #{generator_command}&quot;
    system(generator_command)
  end
end
</pre>
<h5>PDFerator Class</h5>
<p><em>This class was written by <a href="mailto:pete@notahat.com">pete@notahat.com</a>. You can get the original version from <a href="http://svn.bustikated.net/snap/browser/trunk/lib/pdferator.rb">http://svn.bustikated.net/snap/browser/trunk/lib/pdferator.rb</a>.</em></p>
<pre class="brush: ruby; title: ; notranslate">
#!/usr/bin/env ruby

# pete@notahat.com

require 'osx/cocoa'
OSX.require_framework 'WebKit'

class PDFerator &lt; OSX::NSObject

  def init
    initWithWidth(950)
  end

  def initWithWidth(width)
    # This sets up some context that we need for creating windows.
    OSX::NSApplication.sharedApplication

    # Create an offscreen window into which we can stick our WebView.
    # The height is zero because we'll resize to fit the document later.
    @window = OSX::NSWindow.alloc.initWithContentRect_styleMask_backing_defer(
      [0, 0, width, 0], OSX::NSBorderlessWindowMask, OSX::NSBackingStoreBuffered, false
    )

    # Create a WebView and stick it in our offscreen window.
    @webView = OSX::WebView.alloc.initWithFrame([0, 0, width, 0])
    @window.setContentView(@webView)

    # Use the screen stylesheet, rather than the print one.
    #@webView.setMediaStyle('screen')
    @webView.setMediaStyle('print')
    # Make sure we don't save any of the prefs that we change.
    @webView.preferences.setAutosaves(false)
    # Set some useful options.
    @webView.preferences.setShouldPrintBackgrounds(true)
    @webView.preferences.setJavaScriptCanOpenWindowsAutomatically(false)
    @webView.preferences.setAllowsAnimatedImages(false)
    # Make sure we don't get a scroll bar.
    @webView.mainFrame.frameView.setAllowsScrolling(false)

    self
  end

  def fetch(url)
    # This sets up the webView_*  methods to be called when loading finishes.
    @webView.setFrameLoadDelegate(self)
    # Tell the webView what URL to load.
    @webView.setValue_forKey(url, 'mainFrameURL')
    # Pass control to Cocoa for a bit.
    OSX.CFRunLoopRun
    @succeeded
  end

  attr_reader :error

  def webView_didFinishLoadForFrame(view, frame)
    @succeeded = true

    # Resize the view to fit the page.
    @docView = @webView.mainFrame.frameView.documentView
    @docView.window.setContentSize(@docView.bounds.size)
    @docView.setFrame(@docView.bounds)

    # Return control to the fetch method.
    OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
  end

  def webView_didFailLoadWithError_forFrame(webview, error, frame)
    @error = error
    @succeeed = false
    # Return control to the fetch method.
    OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
  end

  def webView_didFailProvisionalLoadWithError_forFrame(webview, error, frame)
    @error = error
    @succeeed = false
    # Return control to the fetch method.
    OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
  end

  def save(filename, options = {})
    if options[:paginated]
      save_paginated(filename, options)
    else
      @docView.dataWithPDFInsideRect(@docView.bounds).writeToFile_atomically(filename, true)
    end
  end

private

  def save_paginated(filename, options = {})
    # To generate paginated PDF, we create a print job and set it to save
    # the results to a file.
    printInfo = OSX::NSPrintInfo.alloc.initWithDictionary(
      OSX::NSPrintJobDisposition =&gt; OSX::NSPrintSaveJob,
      OSX::NSPrintSavePath       =&gt; filename
    )
    printInfo.setHorizontalPagination OSX::NSAutoPagination
    printInfo.setVerticalPagination   OSX::NSAutoPagination
    printInfo.setVerticallyCentered   false

    if options.has_key?(:margin)
      printInfo.setTopMargin    options[:margin]
      printInfo.setRightMargin  options[:margin]
      printInfo.setBottomMargin options[:margin]
      printInfo.setLeftMargin   options[:margin]
    end

    # Create a print operation to write out the PDF.
    printOp = OSX::NSPrintOperation.printOperationWithView_printInfo(@docView, printInfo)
    # Make sure we don't display the page setup and print dialogs.
    printOp.setShowPanels(false)
    # Do the printing!
    printOp.runOperation()
  end

end

pdferator = PDFerator.alloc.init
if pdferator.fetch(ARGV[0])
  pdferator.save(ARGV[1], :paginated =&gt; true)
else
  print &quot;Error: #{pdferator.error}\n&quot;
end
</pre>
<h3>Output</h3>
<p>As the script runs, you will be presented with some basic debugging output to give you an idea of where things are. After a considerable amount of time, you will have a directory full of PDFs.</p>
<pre>
-rw-r--r--  1 kelliott  2098   129084 Apr  8 15:08 Issue_100-Back_to_Basics.pdf
-rw-r--r--  1 kelliott  2098    67470 Apr  8 15:08 Issue_100-Web_Designer_and_Proud_of_It.pdf
-rw-r--r--  1 kelliott  2098    55132 Apr  8 15:08 Issue_101-How_to_be_Soopa_Famous.pdf
-rw-r--r--  1 kelliott  2098    98592 Apr  8 15:08 Issue_101-SMIL_When_You_Play_That.pdf
-rw-r--r--  1 kelliott  2098   103323 Apr  8 15:08 Issue_102-The_Declination_of_Independence.pdf
-rw-r--r--  1 kelliott  2098    74346 Apr  8 15:07 Issue_102-This_Web_Business_III:_Selecting_Professionals.pdf
-rw-r--r--  1 kelliott  2098    82872 Apr  8 15:07 Issue_103-A_Failure_to_Communicate.pdf
-rw-r--r--  1 kelliott  2098   109797 Apr  8 15:07 Issue_104-Down_By_Law.pdf
-rw-r--r--  1 kelliott  2098    77894 Apr  8 15:07 Issue_104-Flash’s_Got_a_Brand_New_Bag.pdf
-rw-r--r--  1 kelliott  2098   134241 Apr  8 15:07 Issue_105-The_Road_to_Dystopia.pdf
-rw-r--r--  1 kelliott  2098   109874 Apr  8 15:07 Issue_106-Beyond_Usability_and_Design:_The_Narrative_Web.pdf
-rw-r--r--  1 kelliott  2098   115614 Apr  8 15:07 Issue_107-“Forgiving”_Browsers_Considered_Harmful.pdf
-rw-r--r--  1 kelliott  2098    86811 Apr  8 15:07 Issue_109-CSS_Design:_Size_Matters.pdf
-rw-r--r--  1 kelliott  2098    71067 Apr  8 15:07 Issue_112-The_Devil_His_Due:_What_Online_Porn_Portends.pdf
-rw-r--r--  1 kelliott  2098   105460 Apr  8 15:07 Issue_113-Game_Design_in_Flash_5,_Part_II:_Heroes_and_Villains.pdf
-rw-r--r--  1 kelliott  2098    72844 Apr  8 15:07 Issue_114-Cheaper_Over_Better:_Why_Web_Clients_Settle_for_Less.pdf
-rw-r--r--  1 kelliott  2098    70946 Apr  8 15:07 Issue_114-The_Client_Did_It:_A_WWW_Whodunit.pdf
-rw-r--r--  1 kelliott  2098   142411 Apr  8 15:07 Issue_115-All_the_Access_Money_Can_Buy.pdf
-rw-r--r--  1 kelliott  2098   196737 Apr  8 15:07 Issue_115-Much_Ado_About_Smart_Tags.pdf
-rw-r--r--  1 kelliott  2098    75678 Apr  8 15:07 Issue_116-CSS_Talking_Points:_Selling_Clients_on_Web_Standards.pdf
-rw-r--r--  1 kelliott  2098    65023 Apr  8 15:07 Issue_116-Nipping_Client_Silliness_in_the_Bud.pdf
-rw-r--r--  1 kelliott  2098    99482 Apr  8 15:07 Issue_117-Kick_ASP_Design:_ASP_for_Non-Programmers.pdf
-rw-r--r--  1 kelliott  2098    66008 Apr  8 15:07 Issue_118-Process,_Methodology,_Life_Cycle,_Oh_My.pdf
-rw-r--r--  1 kelliott  2098    90553 Apr  8 15:07 Issue_119-Global_Treaty_Could_Transform_the_Web.pdf
-rw-r--r--  1 kelliott  2098   133381 Apr  8 15:07 Issue_119-Practical_CSS_Layout_Tips,_Tricks,_and_Techniques.pdf
-rw-r--r--  1 kelliott  2098    91600 Apr  8 15:07 Issue_120-Build_a_“Send_to_Friend”_Page.pdf
-rw-r--r--  1 kelliott  2098    84174 Apr  8 15:07 Issue_120-Evolving_Client_Content.pdf
...etc...
</pre>
<p>So that you can see an example of the results, I&#8217;ve uploaded two of these PDFs. Please note that they are copyright of A List Apart Magazine and its authors.</p>
<ul>
<li><a href='http://kevinelliott.net/blogs/acts_as_developer/files/issue_249-how_to_size_text_in_css.pdf'>Issue 249 &#8211; How To Size Text In CSS</a></li>
<li><a href='http://kevinelliott.net/blogs/acts_as_developer/files/issue_249-understanding_web_design.pdf'>Issue 249 &#8211; Understanding Web Design</a></li>
</ul>
<p>The outcome is beautiful PDFs that do not have the artifacts created by most web based layouts because we are taking advantage of the print stylesheet that A List Apart was kind enough to create.<br />
</p>
<h3>Download</h3>
<p>Please feel free to modify and distribute this code as you&#8217;d like. If you do use it, I only ask that you ping this posting with a comment and make a reference in your code to this article, so that others can benefit.</p>
<h5>Download as a <a href="http://kevinelliott.net/blogs/acts_as_developer/files/alistapart.zip">zip file</a> / <a href="http://kevinelliott.net/blogs/acts_as_developer/files/alistapart.tgz">tgz file</a>.</h5>
<p></p>
 <p><a href="http://kevinelliott.net/blogs/acts_as_developer/?flattrss_redirect&amp;id=9&amp;md5=bd061caa704d6ac76b7fd5bf6042a44f" title="Flattr" target="_blank"><img src="http://kevinelliott.net/blogs/acts_as_developer/wp-content/plugins/flattr/img/flattr-badge-large.png" alt="flattr this!"/></a></p>]]></content:encoded>
			<wfw:commentRss>http://kevinelliott.net/blogs/acts_as_developer/2009/04/28/script-it-convert-a-list-apart-articles-to-pdfs/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		<atom:link rel="payment" href="http://kevinelliott.net/blogs/acts_as_developer/?flattrss_redirect&amp;id=9&amp;md5=bd061caa704d6ac76b7fd5bf6042a44f" type="text/html" />
	</item>
	</channel>
</rss>

