<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
  <title>AirBlog - Home</title>
  <id>tag:blog.airbladesoftware.com,2010:mephisto/</id>
  <generator uri="http://mephistoblog.com" version="0.7.3">Mephisto Noh-Varr</generator>
  <link href="http://blog.airbladesoftware.com/feed/atom.xml" rel="self" type="application/atom+xml"/>
  <link href="http://blog.airbladesoftware.com/" rel="alternate" type="text/html"/>
  <updated>2010-03-04T10:52:15Z</updated>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2010-03-04:92544</id>
    <published>2010-03-04T10:52:00Z</published>
    <updated>2010-03-04T10:52:15Z</updated>
    <category term="CSS"/>
    <category term="Typography"/>
    <link href="http://blog.airbladesoftware.com/2010/3/4/thinning-text-in-firefox" rel="alternate" type="text/html"/>
    <title>Thinning Text in Firefox</title>
<content type="html">
            &lt;p&gt;If your web design uses light text on a dark background, you&#8217;ll notice the text looks heavy and blurry.  Instead of sporting a crisp look, your site appears a bit beginner.&lt;/p&gt;

&lt;p&gt;Steve Smith at Ordered List &lt;a href=&quot;http://orderedlist.com/our-writing/resources/html-css/thinning-text-in-webkit-safari/&quot; title=&quot;Thinning Text in Webkit (Safari)&quot;&gt;solved this problem for Safari&lt;/a&gt;.  However his solution doesn&#8217;t work in Firefox &amp;mdash; so I came up with this CSS:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;
@-moz-document url-prefix() {
  body {
    font-weight: 200;
    letter-spacing: 0.3px;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first line targets Firefox, just like an IE conditional comment.  The &lt;code&gt;font-weight&lt;/code&gt; halves the normal weight of the text, and the &lt;code&gt;letter-spacing&lt;/code&gt; gives the font a little breathing space.  (Note the &lt;code&gt;body&lt;/code&gt; is nested within the &lt;code&gt;@-moz-document&lt;/code&gt; selector.)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.markboulton.co.uk/journal/comments/five-simple-steps-to-better-typography&quot; title=&quot;Five simple steps to better typography&quot;&gt;Mark Boulton describes&lt;/a&gt; what&#8217;s going on:&lt;/p&gt;

&lt;blockquote&gt;&lt;div&gt;
&lt;p&gt;When reversing colour out, e.g. white text on black, make sure you increase the leading and tracking, and decrease your font-weight.  This applies to all widths of Measure.  White text on a black background has a higher contrast to the opposite, so the letter forms need to be wider apart, lighter in weight, and have more space between the lines.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;

&lt;p&gt;Here are before and after screenshots of Firefox so you can see for yourself:&lt;/p&gt;

&lt;div class=&quot;imagery&quot;&gt;
  &lt;a href=&quot;/assets/2010/3/4/before.png&quot;&gt;
    &lt;img src=&quot;/assets/2010/3/4/before.png&quot; alt=&quot;Before&quot; width=&quot;220px&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div class=&quot;imagery&quot;&gt;
  &lt;a href=&quot;/assets/2010/3/4/after.png&quot;&gt;
  &lt;img src=&quot;/assets/2010/3/4/after.png&quot; alt=&quot;After&quot; width=&quot;220px&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;

&lt;p&gt;It&#8217;s not quite as crisp as Safari but it&#8217;s close, and certainly much better than it was.&lt;/p&gt;

&lt;p&gt;N.B. I&#8217;ve only done this on my &lt;a href=&quot;http://airbladesoftware.com&quot; title=&quot;AirBlade Software&quot;&gt;main site&lt;/a&gt;, not here on this blog.  You can see the difference.&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2010-02-05:92048</id>
    <published>2010-02-05T15:56:00Z</published>
    <updated>2010-02-05T15:58:16Z</updated>
    <category term="PaperTrail"/>
    <category term="Rails"/>
    <link href="http://blog.airbladesoftware.com/2010/2/5/monkey-patching-a-gem-in-rails-2-3" rel="alternate" type="text/html"/>
    <title>Monkey patching a gem in Rails 2.3</title>
<content type="html">
            &lt;p&gt;Today I needed to add some application-specific functionality to my &lt;a href=&quot;http://github.com/airblade/paper_trail&quot;&gt;PaperTrail gem&lt;/a&gt;.  Specifically I wanted to add &lt;a href=&quot;http://en.wikipedia.org/wiki/Faceted_search&quot;&gt;faceted search&lt;/a&gt; to PaperTrail&#8217;s &lt;a href=&quot;http://github.com/airblade/paper_trail/blob/master/lib/paper_trail/version.rb&quot;&gt;Version&lt;/a&gt; model.  After a few attempts I worked out how to monkey patch a class from a gem:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
# config/initializers/paper_trail_patch.rb
Version.module_eval do
  # ... new code here ...
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once I knew how to add code to the &lt;code&gt;Version&lt;/code&gt; model, I needed to work out what that code would be.  Fortunately Pat Allan&#8217;s &lt;a href=&quot;http://freelancing-god.github.com/ts/en/facets.html&quot;&gt;Thinking Sphinx docs&lt;/a&gt; are excellent so it didn&#8217;t take too long to come up with:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
Version.module_eval do
  belongs_to :area

  define_index do
    indexes :event  # ensures we pick up every record
    indexes :whodunnit, :facet =&gt; true

    has area_id,        :facet =&gt; true
    has created_at,     :sortable =&gt; true

    where 'reviewed = 0'
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here I&#8217;ve defined facets for the built-in &lt;code&gt;whodunnit&lt;/code&gt; field and a custom &lt;code&gt;area_id&lt;/code&gt; attribute, allowing me to present the user with dropdown filters showing result counts for each filter choice.&lt;/p&gt;

&lt;div class=&quot;imagery&quot;&gt;
&lt;img src=&quot;/assets/2010/2/5/dashboard_460.png&quot; alt=&quot;Faceted search example&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;In the screenshot you can see I have two dropdown filters.  Each option shows the number of results you would get if you chose it (leaving the other filter the same).&lt;/p&gt;

&lt;p&gt;The details aren&#8217;t important.  The main point is that &lt;a href=&quot;http://freelancing-god.github.com/ts/en/facets.html&quot;&gt;Thinking Sphinx&lt;/a&gt; makes faceted search, a reasonably complicated task, easy to do.&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2010-02-04:92033</id>
    <published>2010-02-04T14:27:00Z</published>
    <updated>2010-02-04T14:28:23Z</updated>
    <category term="Climate Change"/>
    <link href="http://blog.airbladesoftware.com/2010/2/4/hacked-climate-science-emails" rel="alternate" type="text/html"/>
    <title>Hacked climate science emails</title>
<content type="html">
            &lt;p&gt;However you feel about the &lt;a href=&quot;http://www.guardian.co.uk/environment/hacked-climate-science-emails&quot;&gt;emails hacked&lt;/a&gt; from the &lt;a href=&quot;http://www.cru.uea.ac.uk&quot;&gt;Climatic Research Unit&lt;/a&gt; at the University of East Anglia, you have to admire the sysadmins who successfully kept them backed up and archived for almost 14 years.&lt;/p&gt;

&lt;p&gt;I suppose now they&#8217;re in the &lt;a href=&quot;http://www.eastangliaemails.com&quot;&gt;public domain&lt;/a&gt; the backups won&#8217;t be needed any more.&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2010-02-02:91989</id>
    <published>2010-02-02T14:15:00Z</published>
    <updated>2010-02-09T15:08:24Z</updated>
    <category term="CSS"/>
    <category term="Deployment"/>
    <category term="Rails"/>
    <link href="http://blog.airbladesoftware.com/2010/2/2/generate-css-with-css_dryer-in-your-deployment-process" rel="alternate" type="text/html"/>
    <title>Generate CSS with css_dryer in your deployment process</title>
<content type="html">
            &lt;p&gt;I have just pushed a small, long overdue update to my &lt;a href=&quot;http://github.com/airblade/css_dryer&quot;&gt;css_dryer&lt;/a&gt; plugin which lets you convert your nested stylesheets to CSS as part of your deployment process.  This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You avoid Rails having to render the nested stylesheets on the first request.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;stylesheet_link_tag&lt;/code&gt; helper will bundle your nested stylesheets with your normal ones.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;stylesheet_link_tag&lt;/code&gt; helper will append the cache-busting asset timestamp on your nested stylesheets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this was possible before.&lt;/p&gt;

&lt;h2&gt;How to do it&lt;/h2&gt;

&lt;p&gt;Use the new Rake task &lt;code&gt;css_dryer:to_css&lt;/code&gt;.  This loops over every &lt;code&gt;.css.ncss&lt;/code&gt; file in &lt;code&gt;app/views/stylesheets/&lt;/code&gt; and generates a corresponding CSS file in &lt;code&gt;public/stylesheets/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When deploying you should use this between updating the code and symlinking the new &lt;code&gt;current&lt;/code&gt; directory (assuming a conventional Capistrano deployment).&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
namespace :deploy do
  task :after_update_code do
    generate_css
  end

  task :generate_css, :roles =&gt; [:web] do
    run &quot;cd #{release_path}; RAILS_ROOT=#{release_path} rake css_dryer:to_css&quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This generates the CSS on the server(s) which is where it&#8217;s needed.&lt;/p&gt;

&lt;h2&gt;Asset packaging&lt;/h2&gt;

&lt;p&gt;This is compatible with any asset packaging system you might use, such as &lt;a href=&quot;http://documentcloud.github.com/jammit&quot;&gt;Jammit&lt;/a&gt;.  Just run the Rake task before you package your assets.&lt;/p&gt;

&lt;h2&gt;Asset timestamps&lt;/h2&gt;

&lt;p&gt;Taking a leaf out of &lt;a href=&quot;http://github.com/blog/551-optimizing-asset-bundling-and-serving-with-rails&quot;&gt;GitHub&#8217;s asset bundling&lt;/a&gt; book, we want to be able to deploy code updates without invalidating our users&#8217; CSS and JavaScript caches.  GitHub uses &lt;a href=&quot;http://github.com/mojombo/grit/&quot;&gt;Grit&lt;/a&gt; but I prefer calling out to Git directly:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
# production.rb

# Set the asset timestamp to the timestamp of the most recent commit
# to touch any assets.  Do this so we only bust client caches when we 
# have actually changed assets.
ENV['RAILS_ASSET_ID'] = %w( public/javascripts public/stylesheets app/views/stylesheets ).map do |path|
  `git log -1 --pretty=format:%ct #{path}`
end.max
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Image timestamps&lt;/h2&gt;

&lt;p&gt;As well as JS and CSS, Rails also regards images as assets.  If you set &lt;code&gt;RAILS_ASSET_ID&lt;/code&gt; as above, your images will use that timestamp too.  This doesn&#8217;t seem sensible to me: if you change some Javascript or CSS, you don&#8217;t want to force your users to download all the images again.&lt;/p&gt;

&lt;p&gt;I haven&#8217;t pursued this yet but I think there are a couple of options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Split &lt;code&gt;RAILS_ASSET_ID&lt;/code&gt; into three: one for JS, one for CSS, and one for images.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;RAILS_ASSET_ID&lt;/code&gt; for JS and CSS but file modification time for images.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The latter depends on whether Git (via Capistrano) sets the last modification time of files correctly.  I would be surprised if it didn&#8217;t but I haven&#8217;t checked.  If it does, you would also have to stop Capistrano from touching all the images with &lt;code&gt;set :normalize_asset_timestamps, false&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Smusher&lt;/h2&gt;

&lt;p&gt;While looking into asset timestamps I ran across &lt;a href=&quot;http://github.com/grosser/smusher&quot;&gt;smusher&lt;/a&gt;, a gem which provides an easy command line interface to Yahoo&#8217;s &lt;a href=&quot;http://developer.yahoo.com/yslow/smushit/&quot;&gt;Smush.it&lt;/a&gt; service (for lossless image size reduction of jpg, png and gif).  You can process a whole directory of images at once, which is useful.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://github.com/airblade/css_dryer&quot;&gt;css_dryer&lt;/a&gt; now works properly with Rails&#8217; asset bundling, as well as any asset packaging you might wish to do.&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2010-01-19:91791</id>
    <published>2010-01-19T12:02:00Z</published>
    <updated>2010-01-19T12:03:47Z</updated>
    <category term="Quotations"/>
    <link href="http://blog.airbladesoftware.com/2010/1/19/design-matters-more-than-technnology" rel="alternate" type="text/html"/>
    <title>Design Matters More Than Technnology</title>
<content type="html">
            &lt;blockquote&gt;&lt;div&gt;
&lt;p&gt;&#8230;since everyone is on EC2 and Ruby on Rails, technology is no longer what differentiates most consumer web apps. What does is design. UI/UX design. Social design. Business model design as well.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;http://pegontech.wordpress.com/2010/01/19/why-tumblr-posterous-ass/&quot;&gt;Pascal-Emmanuel Gobry&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2009-12-11:91286</id>
    <published>2009-12-11T10:07:00Z</published>
    <updated>2009-12-11T10:08:11Z</updated>
    <link href="http://blog.airbladesoftware.com/2009/12/11/irony" rel="alternate" type="text/html"/>
    <title>Irony</title>
<content type="html">
            &lt;p&gt;This morning I received this unsolicited commercial email:&lt;/p&gt;

&lt;blockquote&gt;&lt;div&gt;
&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;This is a friendly introduction of our services; we will not send any more e-mail after this.&lt;/p&gt;
&lt;p&gt;&#8220;No Spam 123&#8221; is the email anti spam and antivirus solution for business. The spam email always comes in batch, business will need effective spam filter and spam blocker solution to prevent it. We are using &#8220;behavior&#8221; based anti spam solution, which means the solution is faster, greener and faster.&lt;/p&gt;
&lt;p&gt;&#8220;No Spam 123&#8221; is the most easy-to-use, reliable antispam solution for companies, organizations, and individuals that have their own domain name. No need to install email filter software or anti spam software on each computer. You got spam protection on the whole domain.&lt;/p&gt;
&lt;p&gt;Visit us and trial service for free!&lt;br&gt;
http://www.nospam123.com&lt;/p&gt;
&lt;p&gt;Best Regards,&lt;br&gt;
[name removed]&lt;br&gt;
Business Development&lt;br&gt;
NoSpam123 Business Development&lt;br&gt;
Toll Free: 1-866-9060668 extension 116&lt;br&gt;
Fax: 1-877-8388227&lt;br&gt;
Email: nospam@nospam123.com&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2009-08-10:89543</id>
    <published>2009-08-10T09:33:00Z</published>
    <updated>2010-01-19T12:04:50Z</updated>
    <category term="Deployment"/>
    <link href="http://blog.airbladesoftware.com/2009/8/10/fyi-be-informed-about-task-execution" rel="alternate" type="text/html"/>
    <title>FYI: Find Out What Cron Is Doing</title>
<content type="html">
            &lt;h2&gt;How Do You Know If Cron Worked?&lt;/h2&gt;

&lt;p&gt;Most people who deploy apps set up cron tasks on their servers.  Typically these rotate search engine indexes, generate reports, etc.  But how do you know if your cron tasks are still working?  As time goes by processes get restarted; perhaps they come back up with a different owner, with different permissions.  Or maybe you upgrade a gem and a months-old maintenance task you&#8217;d forgotten about breaks.&lt;/p&gt;

&lt;p&gt;For a long time I got around this by redirecting standard error to standard out and writing all of it to a log file, like this:&lt;/p&gt;

&lt;pre&gt;
&lt;code&gt;0 0,3,6,9,12,15,18,21 * * *
  cd /var/www/apps/someapp/current &amp;&amp; /opt/ree/bin/rake RAILS_ENV=production thinking_sphinx:index
  &gt;&gt; /var/www/apps/someapp/current/log/cron.log 2&gt;&amp;1&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But I could never quite remember the pipe redirection syntax, and once you have a few of these cluttering your crontab it&#8217;s really hard to see what tasks you&#8217;re actually running.  Furthermore you have to remember to check your log files on a regular basis.  We all have better things to do.&lt;/p&gt;

&lt;p&gt;Cron is designed for scheduling tasks.  It is not designed for keeping you posted on how they worked out.&lt;/p&gt;

&lt;h2&gt;FYI Keeps You Posted&lt;/h2&gt;

&lt;p&gt;Here&#8217;s the task (from above) I want to schedule:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;0 0,3,6,9,12,15,18,21 * * *
  cd /var/www/apps/someapp/current &amp;&amp; /opt/ree/bin/rake RAILS_ENV=production thinking_sphinx:index&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When this runs, I want standard out and standard error logged somewhere, and I want to be emailed if it fails.  And I want all this to happen with as little change as possible to my crontab.  Here&#8217;s what I came up with:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;0 0,3,6,9,12,15,18,21 * * *
  fyi &quot;cd /var/www/apps/someapp/current &amp;&amp; /opt/ree/bin/rake RAILS_ENV=production thinking_sphinx:index&quot;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That&#8217;s 6 extra characters.  Pretty minimal.  And it&#8217;s memorable.  Minimal and memorable!&lt;/p&gt;

&lt;p&gt;By default this will log everything to &lt;code&gt;fyi.log&lt;/code&gt; in your home directory.  If you supply some email connection details in a configuration file, you&#8217;ll get emailed if it fails.&lt;/p&gt;

&lt;h2&gt;Installation&lt;/h2&gt;

&lt;p&gt;Install my &lt;a href=&quot;http://github.com/airblade/fyi&quot;&gt;fyi&lt;/a&gt; gem and you&#8217;re ready to go:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo gem install fyi&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can change its log file location via a simple YAML config file.  See the &lt;a href=&quot;http://github.com/airblade/fyi/blob/master/config_example.yml&quot;&gt;example configuration file&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Email Notification&lt;/h2&gt;

&lt;p&gt;Switch this on by supplying some SMTP details in the config file.  By default it&#8217;ll email you when your tasks fail, but you can also have it email you when they succeed.  This is useful when you are setting up a new task in cron and want to check for a few days that it&#8217;s working properly.&lt;/p&gt;

&lt;h2&gt;Campfire / HTTP / etc Notification&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://github.com/airblade/fyi&quot;&gt;fyi&lt;/a&gt; is trivial to extend with additional notifiers.  Just write a subclass of &lt;code class=&quot;ruby&quot;&gt;Fyi::Notifier&lt;/code&gt; which responds to &lt;code&gt;:notify&lt;/code&gt; and pop any configuration you need in the config file.  The &lt;a href=&quot;http://github.com/airblade/fyi&quot;&gt;README&lt;/a&gt; has more information.&lt;/p&gt;

&lt;h2&gt;Statistical Proof of Marvellousness&lt;/h2&gt;

&lt;p&gt;The task I want to schedule is 124 characters long.  The pipe-redirection-plus-remembering-to-check method is 175 characters long, always needs Googling, and doesn&#8217;t really work.  The &lt;a href=&quot;http://github.com/airblade/fyi&quot;&gt;fyi&lt;/a&gt; method, which is marvellous, is a mere 130 characters.  That&#8217;s 88% less crud in your crontab!&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2009-07-24:89177</id>
    <published>2009-07-24T17:03:00Z</published>
    <updated>2009-07-25T15:46:15Z</updated>
    <category term="Client Work"/>
    <category term="Rails"/>
    <link href="http://blog.airbladesoftware.com/2009/7/24/girls-in-football-a-k-a-soccer-&#8212;-launched" rel="alternate" type="text/html"/>
    <title>Girls In Football (a.k.a. Soccer) &#8212; Launched</title>
<content type="html">
            &lt;div class=&quot;imagery&quot;&gt;
&lt;a href=&quot;http://girlsinfootball.co.uk&quot;&gt;
&lt;img src=&quot;/assets/2009/7/24/gif.png&quot; alt=&quot;Girls In Football - the place for women's football&quot; /&gt;
&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;Women&#8217;s football is the fastest growing sport in the UK.  More girls and women — over a million — play football than any other sport.  Astonishingly, until now there hasn&#8217;t been a single website dedicated to helping them flourish and to bringing the women&#8217;s game to a wider audience.&lt;/p&gt;

&lt;p&gt;So a few days ago &lt;a href=&quot;http://www.pollycourtney.com&quot;&gt;Polly Courtney&lt;/a&gt; and I launched &lt;a href=&quot;http://girlsinfootball.co.uk&quot;&gt;Girls In Football&lt;/a&gt; for the thriving community of female footballers in the UK.  As female footballers ourselves (well, one of us), we know exactly what players need and want.  And that&#8217;s what we&#8217;ve built.&lt;/p&gt;

&lt;p&gt;We want to help every girl and woman playing football to get more out of the game, to fuel their passion and to have fun.&lt;/p&gt;

&lt;h2&gt;What&#8217;s On The Site?&lt;/h2&gt;

&lt;p&gt;We launched with a whole range of professional and user-contributed content, including &lt;a href=&quot;http://girlsinfootball.co.uk/skills&quot;&gt;over 60 skills coaching videos&lt;/a&gt;, which are free for everyone to benefit from.  If you play football and want to improve, you&#8217;ll find plenty of &lt;a href=&quot;http://girlsinfootball.co.uk/forums&quot;&gt;advice&lt;/a&gt;, &lt;a href=&quot;http://girlsinfootball.co.uk/skills&quot;&gt;videos&lt;/a&gt; and &lt;a href=&quot;http://girlsinfootball.co.uk/bookmarks&quot;&gt;articles&lt;/a&gt; to help you, not to mention &lt;a href=&quot;http://girlsinfootball.co.uk/tours&quot;&gt;tours and tournaments&lt;/a&gt; to go on and play in.&lt;/p&gt;

&lt;p&gt;Right now a lot of people are using the site to recruit players for their teams.  We have some fancy geographic searching which lets you &lt;a href=&quot;http://girlsinfootball.co.uk/search&quot;&gt;find players, coaches and teams in your area&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you &lt;a href=&quot;http://girlsinfootball.co.uk/members/new&quot;&gt;become a member&lt;/a&gt;, which is also free, you get a whole load of useful functionality letting you organise fixtures, write up matches, vote for player of the match, follow your team in the league tables, and so on.  And of course you can chat with your team mates, upload photos, write your own news stories, etc.&lt;/p&gt;

&lt;p&gt;And even if you&#8217;re just an armchair women&#8217;s footballer, we have plenty of &lt;a href=&quot;http://girlsinfootball.co.uk/news&quot;&gt;news stories and interviews&lt;/a&gt; for you.&lt;/p&gt;

&lt;h2&gt;Discounted Kit&lt;/h2&gt;

&lt;p&gt;We also &lt;a href=&quot;http://girlsinfootball.co.uk/store&quot;&gt;sell kit specifically for female players&lt;/a&gt;, all at a discount, including boots from the only manufacturer to make boots specifically for women (Nomis).&lt;/p&gt;

&lt;h2&gt;The Future&lt;/h2&gt;

&lt;p&gt;Right now we&#8217;re focusing on fine-tuning the site and building traffic.  The more women use the site, the better it will get.  &lt;/p&gt;

&lt;p&gt;Once it&#8217;s ticking along we&#8217;ll expand in two directions: to other countries (U.S.A., Canada, Germany, Holland, &#8230;) and to other sports (hockey, netball, &#8230;).  Watch this space.&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2009-06-23:88124</id>
    <published>2009-06-23T09:56:00Z</published>
    <updated>2010-02-05T15:23:57Z</updated>
    <category term="PaperTrail"/>
    <category term="Rails"/>
    <link href="http://blog.airbladesoftware.com/2009/6/23/a-paper-trail-for-your-models" rel="alternate" type="text/html"/>
    <title>A Paper Trail For Your Models</title>
<content type="html">
            &lt;p&gt;&lt;a href=&quot;http://github.com/airblade/paper_trail/tree/master&quot;&gt;PaperTrail&lt;/a&gt; lets you track changes to your Rails app&#8217;s models&#8217; data.  It&#8217;s good for auditing or versioning.  You can see how a model looked at any stage in its lifecycle and even undelete it after it&#8217;s been destroyed.&lt;/p&gt;

&lt;h2&gt;Features&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Stores every create, update and destroy.&lt;/li&gt;
&lt;li&gt;Does not store updates which don&#8217;t change anything.&lt;/li&gt;
&lt;li&gt;Allows you to get at every version, including the original, even once destroyed.&lt;/li&gt;
&lt;li&gt;Allows you to get at every version even if the schema has since changed.&lt;/li&gt;
&lt;li&gt;Automatically records who was responsible if your controller has a &lt;code&gt;current_user&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;Allows you to set who is responsible at model-level (useful for migrations).&lt;/li&gt;
&lt;li&gt;Can be turned off/on (useful for migrations).&lt;/li&gt;
&lt;li&gt;No configuration necessary.&lt;/li&gt;
&lt;li&gt;Stores everything in a single database table (generates migration for you).&lt;/li&gt;
&lt;li&gt;Thoroughly tested.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Rails Version&lt;/h2&gt;

&lt;p&gt;Known to work on Rails 2.3.  Probably works on Rails 2.2 and 2.1.&lt;/p&gt;

&lt;h2&gt;Basic Usage&lt;/h2&gt;

&lt;p&gt;PaperTrail is simple to use.  Just add 15 characters to a model to get a paper trail of every &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, and &lt;code&gt;destroy&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
class Widget &amp;lt; ActiveRecord::Base
  has_paper_trail
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This gives you a &lt;code&gt;versions&lt;/code&gt; method which returns the paper trail of changes to your model.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
&gt;&gt; widget = Widget.find 42
&gt;&gt; widget.versions             # [&amp;lt;Version&amp;gt;, &amp;lt;Version&amp;gt;, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once you have a version, you can find out what happened:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
&gt;&gt; v = widget.versions.last
&gt;&gt; v.event                     # 'update' (or 'create' or 'destroy')
&gt;&gt; v.whodunnit                 # '153'  (if the update was via a controller and
                               #         the controller has a current_user method,
                               #         here returning the id of the current user)
&gt;&gt; v.created_at                # when the update occurred
&gt;&gt; widget = v.reify            # the widget as it was before the update;
                               # would be nil for a create event
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;PaperTrail stores the pre-change version of the model, unlike some other auditing/versioning plugins, so you can retrieve the original version.  This is useful when you start keeping a paper trail for models that already have records in the database.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
&gt;&gt; widget = Widget.find 153
&gt;&gt; widget.name                                 # 'Doobly'

# Add has_paper_trail to Widget model.

&gt;&gt; widget.versions                             # []
&gt;&gt; widget.update_attributes :name =&gt; 'Wotsit'
&gt;&gt; widget.versions.first.reify.name            # 'Doobly'
&gt;&gt; widget.versions.first.event                 # 'update'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This also means that PaperTrail does not waste space storing a version of the object as it currently stands.  The &lt;code&gt;versions&lt;/code&gt; method gives you previous versions; to get the current one just call a finder on your &lt;code&gt;Widget&lt;/code&gt; model as usual.&lt;/p&gt;

&lt;p&gt;Here&#8217;s a helpful table showing what PaperTrail stores:&lt;/p&gt;

&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;Event&lt;/th&gt;
    &lt;th&gt;Model Before&lt;/th&gt;
    &lt;th&gt;Model After&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;create&lt;/td&gt;
    &lt;td&gt;nil&lt;/td&gt;
    &lt;td&gt;widget&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;update&lt;/td&gt;
    &lt;td&gt;widget&lt;/td&gt;
    &lt;td&gt;widget&#8217;&lt;/td&gt;
  &lt;tr&gt;
    &lt;td&gt;destroy&lt;/td&gt;
    &lt;td&gt;widget&lt;/td&gt;
    &lt;td&gt;nil&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;PaperTrail stores the values in the Model Before column.  Most other auditing/versioning plugins store the After column.&lt;/p&gt;

&lt;h2&gt;Reverting Or Undeleting A Model&lt;/h2&gt;

&lt;p&gt;PaperTrail makes reverting to a previous version easy:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
&gt;&gt; widget = Widget.find 42
&gt;&gt; widget.update_attributes :name =&gt; 'Blah blah'
# Time passes....
&gt;&gt; widget = Widget.find(42).versions.last.reify  # the widget as it was before the update
&gt;&gt; widget.save  # reverted to its previous version
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Undeleting is just as simple:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
&gt;&gt; widget = Widget.find 42
&gt;&gt; widget.destroy
# Time passes....
&gt;&gt; widget = Version.find(153).reify    # the widget as it was before it was destroyed
&gt;&gt; widget.save                         # the widget lives!
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In fact you could use PaperTrail to implement an undo system, though I haven&#8217;t had the opportunity yet to do it myself.&lt;/p&gt;

&lt;h2&gt;Finding Out Who Was Responsible For A Change&lt;/h2&gt;

&lt;p&gt;If your &lt;code&gt;ApplicationController&lt;/code&gt; has a &lt;code&gt;current_user&lt;/code&gt; method, PaperTrail will store the value it returns in the &lt;code&gt;version&lt;/code&gt;&#8217;s &lt;code&gt;whodunnit&lt;/code&gt; column.  Note that this column is a string so you will have to convert it to an integer if it&#8217;s an id and you want to look up the user later on:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
&gt;&gt; last_change = Widget.versions.last
&gt;&gt; user_who_made_the_change = User.find last_change.whodunnit.to_i
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In a migration or in &lt;code&gt;script/console&lt;/code&gt; you can set who is responsible like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
&gt;&gt; PaperTrail.whodunnit = 'Andy Stewart'
&gt;&gt; widget.update_attributes :name =&gt; 'Wibble'
&gt;&gt; widget.versions.last.whodunnit              # Andy Stewart
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Turning PaperTrail Off/On&lt;/h2&gt;

&lt;p&gt;Sometimes you don&#8217;t want to store changes.  Perhaps you are only interested in changes made
by your users and don&#8217;t need to store changes you make yourself in, say, a migration.&lt;/p&gt;

&lt;p&gt;If you are about change some widgets and you don&#8217;t want a paper trail of your changes, you can
turn PaperTrail off like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
&gt;&gt; Widget.paper_trail_off
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And on again like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
&gt;&gt; Widget.paper_trail_on
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Installation&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install PaperTrail either as a gem or as a plugin:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;config.gem 'airblade-paper_trail', :lib =&gt; 'paper_trail', :source =&gt; 'http://gems.github.com'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;script/plugin install git://github.com/airblade/paper_trail.git&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate a migration which will add a &lt;code&gt;versions&lt;/code&gt; table to your database.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;script/generate paper_trail&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the migration.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rake db:migrate&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add &lt;code&gt;has_paper_trail&lt;/code&gt; to the models you want to track.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Testing&lt;/h2&gt;

&lt;p&gt;PaperTrail has a thorough suite of tests.  However they only run when PaperTrail is sitting in a Rails app&#8217;s &lt;code&gt;vendor/plugins&lt;/code&gt; directory.  If anyone can tell me how to get them to run outside of a Rails app, I&#8217;d love to hear it.&lt;/p&gt;

&lt;h2&gt;Problems&lt;/h2&gt;

&lt;p&gt;Please use GitHub&#8217;s &lt;a href=&quot;http://github.com/airblade/paper_trail/issues&quot;&gt;issue tracker&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Inspirations&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://github.com/github/simply_versioned&quot;&gt;Simply Versioned&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://github.com/collectiveidea/acts_as_audited&quot;&gt;Acts As Audited&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Intellectual Property&lt;/h2&gt;

&lt;p&gt;Copyright (c) 2009 Andy Stewart (boss@airbladesoftware.com).
Released under the MIT licence.&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2009-06-17:87884</id>
    <published>2009-06-17T09:14:00Z</published>
    <updated>2009-06-17T09:15:15Z</updated>
    <category term="Productivity"/>
    <category term="Quotations"/>
    <link href="http://blog.airbladesoftware.com/2009/6/17/going-back-to-simple" rel="alternate" type="text/html"/>
    <title>Going Back To Simple</title>
<content type="html">
            &lt;blockquote&gt;&lt;div&gt;
&lt;p&gt;Every mistake we’ve made as a company has been because we tried to do too much, not because we didn’t do enough.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;http://www.37signals.com/svn/posts/1769-reminder-know-what-youre-measuring&quot;&gt;Jason Fried&lt;/a&gt;, founder of 37signals&lt;/cite&gt;&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2009-06-09:87670</id>
    <published>2009-06-09T11:30:00Z</published>
    <updated>2009-06-09T11:32:12Z</updated>
    <category term="Rails"/>
    <link href="http://blog.airbladesoftware.com/2009/6/9/testing-http-digest-authentication-with-shoulda" rel="alternate" type="text/html"/>
    <title>Testing HTTP Digest Authentication With Shoulda</title>
<content type="html">
            &lt;p&gt;I recently started writing a small Rails app where I&#8217;ll be the only administrative user.  (It&#8217;s going to replace Mephisto, which runs this blog and regularly goes rogue on the CPU.)  Anyway, my authentication requirements were simple: log in, log out.  So I asked myself, &#8220;&lt;a href=&quot;http://www.artima.com/intv/simplestP.html&quot;&gt;what&#8217;s the simplest thing that could possibly work&lt;/a&gt;?&#8221;&lt;/p&gt;

&lt;p&gt;Well, &lt;a href=&quot;http://www.ietf.org/rfc/rfc2617.txt&quot;&gt;HTTP authentication&lt;/a&gt; is supposed to be simpler than a form-based approach.  In theory it&#8217;s trivial to code: just copy and paste &lt;a href=&quot;http://github.com/rails/rails/blob/2-3-stable/actionpack/lib/action_controller/http_authentication.rb&quot;&gt;the example from the Rails documentation&lt;/a&gt;.  In practice there were a few catches which took me quite a while to resolve, not least the testing.&lt;/p&gt;

&lt;p&gt;Once I had settled on HTTP authentication I had to choose between basic and digest.  I picked digest because I thought basic and digest would be similarly easy to implement, so I might as well use the securer version.&lt;/p&gt;

&lt;h2&gt;Understanding Rails&#8217; API&lt;/h2&gt;

&lt;p&gt;I hadn&#8217;t implemented HTTP authentication before.  My first problem was finding Rails&#8217; API confusing.  There are three methods:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
  authenticate_or_request_with_http_digest(realm = 'Application', &amp;password_procedure)

  authenticate_with_http_digest(realm = 'Application', &amp;password_procedure)

  request_http_digest_authentication(realm = 'Application', message = nil)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I found them easier to understand in reverse order.&lt;/p&gt;

&lt;p&gt;The last one renders a 401 to the browser, along with headers saying &#8216;use HTTP digest authentication to authenticate&#8217;.  It&#8217;s not actually making a request; it&#8217;s instructing the browser to request the resource again, but this time with the user&#8217;s name and password bundled up as per the HTTP-digest protocol.  The method returns the &lt;code&gt;message&lt;/code&gt;, which defaults to &lt;code&gt;&quot;HTTP Digest: Access denied.\n&quot;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The browser sees the 401 and the &#8216;use HTTP digest authentication&#8217; and pops up a dialog box for the user asking for a name and password.  Once the user has typed these in, the browser sends a new request for the resource, but this time with the user&#8217;s log-in details too.&lt;/p&gt;

&lt;p&gt;The middle one, &lt;code class=&quot;ruby&quot;&gt;authenticate_with_http_digest&lt;/code&gt;, checks whatever name and password the browser supplies to see if they&#8217;re legitimate.  It simply returns &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;.  It doesn&#8217;t know or care whether the browser has just asked the user to type in a name and password, or whether the browser cached whatever legitimate credentials the user typed in previously.&lt;/p&gt;

&lt;p&gt;Now we know what those methods do, we can understand the first one.  It simply wraps them:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
  authenticate_with_http_digest(realm, &amp;password_procedure) ||
  request_http_digest_authentication(realm)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So first it checks whether the user&#8217;s name and password, as sent by the browser, are legitimate.  If the browser had these in a cache, it won&#8217;t have asked the user again.  If the user&#8217;s credentials are good, the method returns &lt;code&gt;true&lt;/code&gt;.  Otherwise — either the browser didn&#8217;t send any credentials or it did but the credentials were bad — we send a 401 to the browser, causing it to ask the user for a name and password, and then to re-request the resource.  In this case the method returns &lt;code&gt;&quot;HTTP Digest: Access denied.\n&quot;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Storing the user in the session&lt;/h2&gt;

&lt;p&gt;Bearing in mind &lt;a href=&quot;http://weblog.rubyonrails.org/2009/6/3/security-problem-with-authenticate_with_http_digest&quot;&gt;a gap in Rails 2.3.2&#8217;s implementation&lt;/a&gt;, we can store the user in the session like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
def authenticate
  result = authenticate_or_request_with_http_digest do |name|
    @user = User.find_by_name(name)
    @user.try(:password) || false
  end
  session[:current_user] = @user.id unless result =~ /denied/
end
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Log out with HTTP digest&lt;/h2&gt;

&lt;p&gt;Since the browser caches the user&#8217;s name and password, we need a way to force the browser to forget them.  I use a flag which tells my &lt;code&gt;authenticate&lt;/code&gt; method to make the authentication fail:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
class SessionsController &amp;lt; ApplicationController
  def logout
    session[:current_user] = nil       # so the app knows
    session[:logout_requested] = true  # so the browser will know
    redirect_to root_path
  end
end

class ApplicationController &amp;lt; ActionController::Base
  def authenticate
    result = authenticate_or_request_with_http_digest do |name|
      if session[:logout_requested]
        session[:logout_requested] = nil   # reset flag
        false
      else
        @user = User.find_by_name(name)
        @user.try(:password) || false
      end
    end
    session[:current_user] = @user.id unless result =~ /denied/
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I got this idea from &lt;a href=&quot;http://blog.edendevelopment.co.uk/2009/03/23/under-the-hood-not-so-basic-authentication/&quot;&gt;Eden Development&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Alternative implementation&lt;/h2&gt;

&lt;p&gt;Given our triumvirate of HTTP digest authentication methods above, we could rewrite our &lt;code&gt;authenticate&lt;/code&gt; method in terms of the two lower-level methods like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
class ApplicationController &amp;lt; ActionController::Base
  def authenticate
    success = authenticate_with_http_digest do |name|
      if session[:logout_requested]
        session[:logout_requested] = nil   # reset flag
        false
      else
        @user = User.find_by_name(name)
        @user.try(:password) || false
      end
    end

    if success
      session[:current_user] = @user.id
    else
      request_http_digest_authentication
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;The Tests&lt;/h2&gt;

&lt;p&gt;Testing HTTP digest authentication is not easy.  Fortunately &lt;a href=&quot;http://lightyearsoftware.com/blog/2009/04/testing-http-digest-authentication-in-rails/&quot;&gt;Steve Madsen figured it out&lt;/a&gt; .  I incorporated his code into this &lt;a href=&quot;http://thoughtbot.com/projects/shoulda&quot;&gt;Shoulda&lt;/a&gt; macro:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
# test/shoulda_macros/security.rb:

module AirWeb
  module Shoulda

    def should_be_unauthorized(http_method, action, options = {})
      context &quot;on #{http_method} to #{action}&quot; do
        setup do
          options.each{ |k,v| options[k] = instance_eval(v) }
          send http_method, action, options
        end
        should_respond_with :unauthorized
      end
    end

  end
end

module AirWeb
  module Shoulda
    module Helpers

      def log_in
        # Any user will do.
        authenticate_with_http_digest(User.first.name, User.first.password)
        @request.session[:current_user] = User.first.id
        @request.session[:logout_requested ] = false
      end

      def log_out
        @request.session[:current_user] = nil
        @request.session[:logout_requested ] = true
      end

    end
  end
end

ActiveSupport::TestCase.send :include, AirWeb::Shoulda::Helpers
ActiveSupport::TestCase.extend AirWeb::Shoulda

require 'digest/md5'

class ActionController::TestCase
  def authenticate_with_http_digest(user = 'admin', password = 'admin', realm = 'Application')
    unless ActionController::Base &amp;lt; ActionController::ProcessWithTest
      ActionController::Base.class_eval { include ActionController::ProcessWithTest }
    end

    @controller.instance_eval %Q(
      alias real_process_with_test process_with_test

      def process_with_test(request, response)
        credentials = {
          :uri =&gt; request.env['REQUEST_URI'],
          :realm =&gt; &quot;#{realm}&quot;,
          :username =&gt; &quot;#{user}&quot;,
          :nonce =&gt; ActionController::HttpAuthentication::Digest.nonce,
          :opaque =&gt; ActionController::HttpAuthentication::Digest.opaque,
        }
        request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Digest.encode_credentials(
          request.request_method, credentials, &quot;#{password}&quot;, false
        )
        real_process_with_test(request, response)
      end
    )
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This allows me to write test code like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
  context 'The public' do
    setup { log_out }
    should_be_unauthorized :get, :new
    should_be_unauthorized :create, :new
    should_be_unauthorized :update, 'Article.first.id'
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It took me a while to realise that &lt;code&gt;Article&lt;/code&gt; is not visible in the &lt;code&gt;context&lt;/code&gt;&#8217;s scope.  You have to pass a string to your custom should method, then &lt;code&gt;instance_eval&lt;/code&gt; it in the &lt;code&gt;setup&lt;/code&gt; block.  (Or you could pass an instance variable.)&lt;/p&gt;

&lt;h2&gt;PUT and DELETE problem with HTTP digest&lt;/h2&gt;

&lt;p&gt;In the course of all this, I realised that my app wouldn&#8217;t allow me to update (via PUT) or destroy (via DELETE).  It turns out &lt;a href=&quot;https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/2490-http-digest-auth-uses-wrong-request-method-for-put-delete#ticket-2490-1&quot;&gt;there was a bug&lt;/a&gt; but it has been &lt;a href=&quot;http://github.com/rails/rails/commit/dbb025827992331843566be418a6f86d89f41868&quot;&gt;fixed in commit dbb025&lt;/a&gt; by Steve Madsen.  This is on the 2-3-stable branch but not in the gems, so you&#8217;ll have to &lt;a href=&quot;http://blog.airbladesoftware.com/2009/4/28/how-to-vendor-rails&quot;&gt;vendor Rails&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I rather wish I had stuck with form-based authentication!  However that&#8217;s because I hadn&#8217;t looked at HTTP authentication before.  Now I have, the next time I come to implement authentication, HTTP authentication actually will be the simplest thing that could work.&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2009-06-05:87639</id>
    <published>2009-06-05T17:44:00Z</published>
    <updated>2009-06-06T14:18:47Z</updated>
    <category term="Rails"/>
    <link href="http://blog.airbladesoftware.com/2009/6/5/the-rails-toolbox" rel="alternate" type="text/html"/>
    <title>The Rails Toolbox</title>
<content type="html">
            &lt;h2&gt;The Goal&lt;/h2&gt;

&lt;p&gt;Inspired by &lt;a href=&quot;http://ruby-toolbox.com/&quot;&gt;The Ruby Toolbox&lt;/a&gt;, I wanted to construct a Rails Toolbox.  The Ruby Toolbox mines &lt;a href=&quot;http://github.com&quot;&gt;GitHub&lt;/a&gt;, rating the tools by the number of watchers and forks; how to do something similar for Rails?&lt;/p&gt;

&lt;h2&gt;The Method&lt;/h2&gt;

&lt;p&gt;First of all we need to define what counts as a Rails tool.  I&#8217;m saying plugins and gems.&lt;/p&gt;

&lt;p&gt;Second, we need to find out which plugins and gems people have installed in their Rails apps.  Here we make use of a handy feature introduced in Rails 2.3: &lt;a href=&quot;http://railscasts.com/episodes/148-app-templates-in-rails-2-3&quot;&gt;Rails Application Templates&lt;/a&gt;.  These templates are scripts which let you customise a new Rails application, and crucially they have these three attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They specify which plugins to install in the new Rails app.&lt;/li&gt;
&lt;li&gt;They specify which gems to install in the new Rails app.&lt;/li&gt;
&lt;li&gt;People tend to pop them on GitHub even if their apps aren&#8217;t open-source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So all we need to do is mine GitHub for these templates, hammer through them, and tot up the plugins and gems they use.&lt;/p&gt;

&lt;h2&gt;The Code&lt;/h2&gt;

&lt;p&gt;I wrote some &lt;a href=&quot;http://github.com/airblade/rails-templates/blob/master/rails_toolbox.rb&quot;&gt;code&lt;/a&gt; to do this.  It uses &lt;a href=&quot;http://railstips.org&quot;&gt;John Nunemaker&lt;/a&gt;&#8217;s &lt;a href=&quot;http://github.com/jnunemaker/httparty/tree/master&quot;&gt;HTTParty&lt;/a&gt; to wrap &lt;a href=&quot;http://develop.github.com/&quot;&gt;GitHub&#8217;s API&lt;/a&gt;, or at least the parts I needed.&lt;/p&gt;

&lt;p&gt;This was the first time I&#8217;ve used &lt;a href=&quot;http://github.com/jnunemaker/httparty/tree/master&quot;&gt;HTTParty&lt;/a&gt; and I liked it a lot.  I haven&#8217;t got up and running with someone else&#8217;s code so quickly for ages.  As an aside, I think it&#8217;s supposed to be &#8220;HTTP-party&#8221; but I prefer &#8220;HTTP-arty&#8221;.&lt;/p&gt;

&lt;h2&gt;The Results&lt;/h2&gt;

&lt;p&gt;I would love to build a website for you as shiny as &lt;a href=&quot;http://ruby-toolbox.com/&quot;&gt;The Ruby Toolbox&#8217;s&lt;/a&gt;.  But not quite enough to actually do it.  You&#8217;ll have to settle for two lists.&lt;/p&gt;

&lt;p&gt;There&#8217;s also one small caveat to bear in mind but I&#8217;ll come to that later.&lt;/p&gt;

&lt;h3&gt;Top 33 Gems&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;thoughtbot-factory_girl (6)&lt;/li&gt;
&lt;li&gt;RedCloth (6)&lt;/li&gt;
&lt;li&gt;mislav-will_paginate (5)&lt;/li&gt;
&lt;li&gt;ruby-openid (5)&lt;/li&gt;
&lt;li&gt;thoughtbot-shoulda (5)&lt;/li&gt;
&lt;li&gt;hpricot (3)&lt;/li&gt;
&lt;li&gt;mocha (3)&lt;/li&gt;
&lt;li&gt;haml (2)&lt;/li&gt;
&lt;li&gt;rspec (2)&lt;/li&gt;
&lt;li&gt;rubyist-aasm (2)&lt;/li&gt;
&lt;li&gt;sqlite3-ruby (2)&lt;/li&gt;
&lt;li&gt;rspec-rails (2)&lt;/li&gt;
&lt;li&gt;stefanpenner-my_scaffold (2)&lt;/li&gt;
&lt;li&gt;dm-validations (1)&lt;/li&gt;
&lt;li&gt;sevenwire-attr_encrypted (1)&lt;/li&gt;
&lt;li&gt;datamapper4rail (1)&lt;/li&gt;
&lt;li&gt;dm-timestamps (1)&lt;/li&gt;
&lt;li&gt;mislav-will-paginate (1)&lt;/li&gt;
&lt;li&gt;rcov (1)&lt;/li&gt;
&lt;li&gt;authlogic (1)&lt;/li&gt;
&lt;li&gt;chriseppstein-compass (1)&lt;/li&gt;
&lt;li&gt;addressable (1)&lt;/li&gt;
&lt;li&gt;quietbacktrace (1)&lt;/li&gt;
&lt;li&gt;yfactorial-utility_scopes (1)&lt;/li&gt;
&lt;li&gt;rdiscount (1)&lt;/li&gt;
&lt;li&gt;jchris-couchrest (1)&lt;/li&gt;
&lt;li&gt;bj (1)&lt;/li&gt;
&lt;li&gt;do_sqlite3 (1)&lt;/li&gt;
&lt;li&gt;thoughtbot-quietbacktrace (1)&lt;/li&gt;
&lt;li&gt;sevenwire-forgery (1)&lt;/li&gt;
&lt;li&gt;sevenwire-configatron (1)&lt;/li&gt;
&lt;li&gt;aws-s3 (1)&lt;/li&gt;
&lt;li&gt;faker (1)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Top 40 Plugins&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;rspec (8)&lt;/li&gt;
&lt;li&gt;rspec-rails (8)&lt;/li&gt;
&lt;li&gt;exception_notifier (6)&lt;/li&gt;
&lt;li&gt;open_id_authentication (5)&lt;/li&gt;
&lt;li&gt;restful-authentication (4)&lt;/li&gt;
&lt;li&gt;asset_packager (4)&lt;/li&gt;
&lt;li&gt;role_requirement (3)&lt;/li&gt;
&lt;li&gt;hoptoad_notifier (3)&lt;/li&gt;
&lt;li&gt;acts_as_taggable_redux (3)&lt;/li&gt;
&lt;li&gt;aasm (2)&lt;/li&gt;
&lt;li&gt;active_scaffold (2)&lt;/li&gt;
&lt;li&gt;will_paginate (2)&lt;/li&gt;
&lt;li&gt;squirrel (2)&lt;/li&gt;
&lt;li&gt;cucumber (2)&lt;/li&gt;
&lt;li&gt;year_after_year (1)&lt;/li&gt;
&lt;li&gt;semantic_form_builder (1)&lt;/li&gt;
&lt;li&gt;acl_system2 (1)&lt;/li&gt;
&lt;li&gt;factory_girl (1)&lt;/li&gt;
&lt;li&gt;basic_model (1)&lt;/li&gt;
&lt;li&gt;restful_authentication (1)&lt;/li&gt;
&lt;li&gt;permanent_records (1)&lt;/li&gt;
&lt;li&gt;webrat (1)&lt;/li&gt;
&lt;li&gt;dataset (1)&lt;/li&gt;
&lt;li&gt;make_resourceful (1)&lt;/li&gt;
&lt;li&gt;machinist (1)&lt;/li&gt;
&lt;li&gt;bootstrapper (1)&lt;/li&gt;
&lt;li&gt;ssl_requirement (1)&lt;/li&gt;
&lt;li&gt;authlogic (1)&lt;/li&gt;
&lt;li&gt;mile_marker (1)&lt;/li&gt;
&lt;li&gt;flash-message-conductor (1)&lt;/li&gt;
&lt;li&gt;active_presenter (1)&lt;/li&gt;
&lt;li&gt;shoulda (1)&lt;/li&gt;
&lt;li&gt;quietbacktrace (1)&lt;/li&gt;
&lt;li&gt;forgery (1)&lt;/li&gt;
&lt;li&gt;newrelic_rpm (1)&lt;/li&gt;
&lt;li&gt;limerick_rake (1)&lt;/li&gt;
&lt;li&gt;action_mailer_tls (1)&lt;/li&gt;
&lt;li&gt;facebooker (1)&lt;/li&gt;
&lt;li&gt;in_place_editing (1)&lt;/li&gt;
&lt;li&gt;rr (1)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Caveat&lt;/h3&gt;

&lt;p&gt;These results are based on the first 30 repositories returned by &lt;a href=&quot;http://develop.github.com/&quot;&gt;GitHub&#8217;s API&lt;/a&gt;;  The GUI returns 243 results.  If anyone know hows to get the next 30 (and the 30 after, etc), please let me know in the comments.  Thanks ;)&lt;/p&gt;

&lt;p&gt;Update: this is a &lt;a href=&quot;http://github.com/develop/develop.github.com/issues/labels/feature#issue/1&quot;&gt;known shortcoming&lt;/a&gt;.  There&#8217;s not much we can do until it&#8217;s fixed.&lt;/p&gt;

&lt;h2&gt;Discussion&lt;/h2&gt;

&lt;p&gt;We can&#8217;t reach any statistically significant conclusions about the popularity of Rails plugins and gems: with only 30 repositories out of 243 we do not have a sufficiently powerful sample.  Also none of mine are showing up in the results so the data must be incomplete.&lt;/p&gt;

&lt;p&gt;We can mine all the data we need to build a complete &lt;a href=&quot;http://oreilly.com/catalog/9780596529321/&quot;&gt;recommendation system&lt;/a&gt;.  Rails&#8217; plugin installer and gem-installing Rake task could be beefed up like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
  $ script/plugin install git://github.com/technoweenie/restful-authentication.git
  [...Git stuff...]
  People who installed restful-authentication were also interested in authlogic.
  Would you like to try authlogic instead (y/n)?
  $ y
  Uninstalling restful-authentication...
  Installing authlogic...
  [...Git stuff...]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Of course not all Rails app template creators are going to choose the same plugins and gems as you.  The recommendation system could incorporate some Bayesian mathematics or a neural network to learn your predilections and adapt.  I see a great future for this.  Somebody should get right on it.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The mining method seems sound, though, and the code works.  All we need now is a shiny website!&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2009-06-04:87635</id>
    <published>2009-06-04T12:00:00Z</published>
    <updated>2009-06-04T12:02:04Z</updated>
    <category term="CSS"/>
    <category term="Typography"/>
    <link href="http://blog.airbladesoftware.com/2009/6/4/vertical-baseline-rhythm" rel="alternate" type="text/html"/>
    <title>Vertical Baseline Rhythm</title>
<content type="html">
            &lt;h2&gt;Why bother?&lt;/h2&gt;

&lt;p&gt;I spend my working life creating web apps.  I do everything, but first and foremost I am a programmer.  Although I like to think I have a good eye for web design — how things look — it&#8217;s &#8220;read only&#8221;: I know and appreciate a good design when I see it but I can&#8217;t create it myself.&lt;/p&gt;

&lt;p&gt;Since I&#8217;m hopelessly out of my depth with every image editor on the market, including &lt;a href=&quot;http://www.flyingmeat.com/acorn/&quot;&gt;Acorn&lt;/a&gt; (&#8220;you don&#8217;t need a Ph.D in computer graphics&#8221;), I have been drawn increasingly to &lt;a href=&quot;http://www.aisleone.net/2009/design/8-ways-to-improve-your-typography/&quot;&gt;typography&lt;/a&gt;.  Not in terms of designing typefaces but rather learning how to make text look better.  You tend to use a calculator, not an image editor, so it&#8217;s more tractable for me.&lt;/p&gt;

&lt;p&gt;Anyway, establishing a vertical baseline rhythm is one technique to improve the legibility of text.  After reading &lt;a href=&quot;http://24ways.org/2006/compose-to-a-vertical-rhythm&quot;&gt;Compose to a Vertical Rhythm&lt;/a&gt;, &lt;a href=&quot;http://www.fivesimplesteps.co.uk/&quot;&gt;Designing for the Web&lt;/a&gt;, and &lt;a href=&quot;http://nubyonrails.com/articles/get-rhythm-in-your-baseline&quot;&gt;Get Rhythm in Your Baseline&lt;/a&gt;, I thought I&#8217;d have a go on &lt;a href=&quot;http://girlsinfootball.co.uk&quot;&gt;Girls in Football&lt;/a&gt; (in beta, so unfortunately you can&#8217;t see what I mean yet).&lt;/p&gt;

&lt;h2&gt;Helpful Tools&lt;/h2&gt;

&lt;p&gt;Sorting out the textual elements was straightforward.  Two tools helped greatly: &lt;a href=&quot;http://site.ringce.com/products/slammer/slammer.html&quot;&gt;Slammer&lt;/a&gt; and &lt;a href=&quot;http://www.debugbar.com/&quot;&gt;DebugBar&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://site.ringce.com/products/slammer/slammer.html&quot;&gt;Slammer&lt;/a&gt; is an OS X app that floats a semi-transparent grid over other windows, allowing you to see at a glance whether your baselines are rhythmic.  It does more, but this alone makes it indispensable.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.debugbar.com/&quot;&gt;DebugBar&lt;/a&gt; is an Internet Explorer plugin which comes closer to &lt;a href=&quot;http://getfirebug.com/&quot;&gt;Firebug&lt;/a&gt; than anything else I&#8217;ve seen on IE.  It has a DOM inspector, HTTP inspector, Javascript inspector and console, and more.  I find it extremely useful for getting to the bottom of bizarre IE behaviour.&lt;/p&gt;

&lt;h2&gt;Stumbling Blocks&lt;/h2&gt;

&lt;p&gt;Once the textual elements are done, you are left with forms and images.  Forms elements and buttons throw a spanner in the works because their sizes vary from browser to browser.  Images disrupt the baseline rhythm unless their heights are an exact multiple of your line spacing — which generally they aren&#8217;t.&lt;/p&gt;

&lt;p&gt;I haven&#8217;t tackled forms yet, but I did solve the image problem.&lt;/p&gt;

&lt;h2&gt;jQuery Plugin for Images&lt;/h2&gt;

&lt;p&gt;Geoffrey Grosenback &lt;a href=&quot;http://nubyonrails.com/articles/get-rhythm-in-your-baseline&quot;&gt;mentioned&lt;/a&gt; a technique he had heard about for handling images: replace each image with a div of the same width and a height which is a multiple of the baseline rhythm, and set the image as the div&#8217;s background.  Since the image is now in the background it won&#8217;t overflow the div and will therefore slot neatly into the baselines.&lt;/p&gt;

&lt;p&gt;This avoids squashing the image, though it does mean chopping a little off if you round the height down to the nearest baseline.&lt;/p&gt;

&lt;p&gt;Anyway, I made this into a &lt;a href=&quot;http://github.com/airblade/jquery-rhythm/tree/master&quot;&gt;jQuery plugin called rhythm&lt;/a&gt; which can be used like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;javascript&quot;&gt;
  $(window).load(function() {
    $('img').rhythm();
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It works in Safari, Firefox and IE7.&lt;/p&gt;

&lt;p&gt;You can see it in action on &lt;a href=&quot;http://girlsinfootball.co.uk&quot;&gt;Girls in Football&lt;/a&gt;, or at least you will be able to when we come out of beta.  For every image it calculates where the nearest baseline is and &#8220;rounds the image&#8221; up or down to it.  When rounding down you lose a little bit of image, and when rounding up you gain a little whitespace.&lt;/p&gt;

&lt;p&gt;It works really well, and it&#8217;s very satisfying to drop &lt;a href=&quot;http://site.ringce.com/products/slammer/slammer.html&quot;&gt;Slammer&lt;/a&gt; over the bottom of your page and find the vertical baseline rhythm still going strongly.&lt;/p&gt;

&lt;p&gt;Now I need to apply everything I&#8217;ve learned to this site&#8230;&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2009-05-27:87412</id>
    <published>2009-05-27T15:12:00Z</published>
    <updated>2009-05-27T15:13:33Z</updated>
    <category term="Quotations"/>
    <link href="http://blog.airbladesoftware.com/2009/5/27/managing-expectations" rel="alternate" type="text/html"/>
    <title>Managing Expectations</title>
<content type="html">
            &lt;blockquote&gt;&lt;div&gt;
&lt;p&gt;The GUI is very limited, and I’m not really motivated to address that, or to give users the features they’re asking for. Similarly, I don’t take bug reports very seriously.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;http://po-ru.com/diary/thoughts-on-the-future-of-the-iplayer-downloader/&quot;&gt;Paul Battley keeps it real&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="http://blog.airbladesoftware.com/">
    <author>
      <name>Andy Stewart</name>
    </author>
    <id>tag:blog.airbladesoftware.com,2009-05-23:87287</id>
    <published>2009-05-23T20:00:00Z</published>
    <updated>2009-05-23T20:00:41Z</updated>
    <category term="Databases"/>
    <category term="Rails"/>
    <link href="http://blog.airbladesoftware.com/2009/5/23/a-better-way-to-import-legacy-data-in-rails" rel="alternate" type="text/html"/>
    <title>A Better Way To Import Legacy Data In Rails</title>
<content type="html">
            &lt;p&gt;Do you have to run a Rails app alongside the legacy app it&#8217;s replacing?  The legacy app remains the authority for some data, such as users, while the Rails app is the authority for other data.&lt;/p&gt;

&lt;p&gt;I&#8217;m in this situation on one project.  My Rails app is replacing a Mambo CMS piece by piece.  Once the Mambo app is fully replaced the Rails app will be the authority for all data in the system.  Until then, the Rails app must keep itself synchronised with the Mambo app&#8217;s data for those models where the Mambo app remains the authority.&lt;/p&gt;

&lt;p&gt;In my Rails app eight models&#8217; data must be imported from five legacy tables.  Other models refer to those eight models so I need to maintain referential integrity.&lt;/p&gt;

&lt;h2&gt;Acts As Importable&lt;/h2&gt;

&lt;p&gt;My first effort at writing the import code was alright.  It worked.  Recently, though, I came across &lt;a href=&quot;http://openmonkey.com/about&quot;&gt;Tim Riley&lt;/a&gt;&#8217;s excellent &lt;a href=&quot;http://github.com/timriley/acts-as-importable/tree/master&quot;&gt;acts_as_importable plugin&lt;/a&gt; and this has made all the difference.  Before his plugin my code was a little ugly and its intent was obscure.  Now, using his plugin, my code is clean, clear, and trivial to maintain.&lt;/p&gt;

&lt;p&gt;Tim wrote a &lt;a href=&quot;http://openmonkey.com/articles/2009/05/importing-legacy-data-in-rails&quot;&gt;clear article explaining how to import legacy data into Rails&lt;/a&gt;.  Two of his design decisions stood out for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The legacy models know how to turn themselves into non-legacy models, not vice-versa.&lt;/li&gt;
&lt;li&gt;The legacy models don&#8217;t export themselves; a dedicated class has that responsibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I made three additions to Tim&#8217;s recipe.&lt;/p&gt;

&lt;p&gt;First I made my legacy models read-only, using &lt;a href=&quot;http://onehackoranother.com/&quot;&gt;Jason Frame&lt;/a&gt;&#8217;s &lt;a href=&quot;http://github.com/jaz303/read_only_model/tree/master&quot;&gt;read_only_model plugin&lt;/a&gt;, just to be on the safe side.  My client would be displeased if I accidentally wrote over any of his data.&lt;/p&gt;

&lt;p&gt;Second I turned off &lt;a href=&quot;http://freelancing-god.github.com/ts/en/deltas.html&quot;&gt;Thinking Sphinx&#8217;s delta indexing&lt;/a&gt; before importing any data, turning it on again afterwards and re-indexing.  Leaving the delta indexing on during the import would be a waste of resources and would significantly slow down the import.&lt;/p&gt;

&lt;p&gt;Third I turned off auditing (using the &lt;a href=&quot;http://github.com/collectiveidea/acts_as_audited/tree/master&quot;&gt;acts_as_audited plugin&lt;/a&gt;) during the import process for a similar reason.  The audit trail of who updated which record would become next to useless if swamped with tens of thousands of entries each night.&lt;/p&gt;

&lt;p&gt;So my importer looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;
class Legacy::Importer
  def self.run
   disable_auditing
   disable_delta_indexing

   # The import (see Tim Riley's article)
   # ...

  ensure
    enable_auditing
    enable_delta_indexing
  end

  private

  def self.disable_delta_indexing
    ThinkingSphinx.deltas_enabled = false
  end

  def self.enable_delta_indexing
    ThinkingSphinx.deltas_enabled = true
  end

  @@audited_models = [ Member, Report ] # etc

  def self.disable_auditing
    @@audited_models.each { |m| m.disable_auditing }
  end

  def self.enable_auditing
    @@audited_models.each { |m| m.enable_auditing }
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Anyway, if you are in the same boat you should try out &lt;a href=&quot;http://github.com/timriley/acts-as-importable/tree/master&quot;&gt;acts_as_importable&lt;/a&gt;.  It certainly improved my import process.&lt;/p&gt;
          </content>  </entry>
</feed>
