« May 2008 | Main | July 2008 »

June 07, 2008

Prevent accidental deployment with a prompt

Listen to this articleListen to this article

This morning I went to push out a new version of an application to our staging environment on an Engine Yard slice. I knew I had done exactly that last night so I navigated through my bash history and hit enter. Two minutes later the new version had been deployed and I was about to walk out the door to do some chores before coming back to start playing with the app. Thankfully, Steve came online and informed me that production was broken.

A quick look through my bash history and it seemed I'd used the deploy to production rather than deploy to staging but, being in a hurry, hadn't looked carefully enough. Of course some might argue that I should have looked more carefully. That I shouldn't have deployed before heading out. All valid points but I very rarely have full control over what's going on around me. So, while it's all very well and good to hope that I will be more careful next time, that's a bit like hoping global warming isn't a reality: we all hope it's not but maybe we should do something about it just in case?

And so I added the following at the start of the :production task in my deploy.rb file:

  unless Capistrano::CLI.ui.agree("Are you sure you want to deploy to production? (yes/no): ")
    puts "Phew! That was a close call."
    exit
  end

For all other environments, the deployment goes through without question. Attempt to deploy to production however, and I'm now forced to be explicit about my intentions.

June 06, 2008

Generating lots of little test cases

Listen to this articleListen to this article

When writing code that is largely algorithmic, I find I end up writing specs that sit in a loop repeating the same operations over a set of data.

This works well enough but it has the downside that the tests abort as soon as a single failing case is detected which can lead to vicious cycles of fixing one case only to find you've broken another.

The solution is of course to break out each expectation--inputs and expected outputs--so they are all run and reported individually. Doing so by hand however, is tedious to say the least so why not generate them on the fly instead:

describe Fibonacci do
  [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5], [6, 8]].each do |input, output|
    it "should generate #{output} for #{input}" do
      Fibonacci.calculate(input).should == output
    end
  end
end

This is such a elegant solution I'm not sure why it only just occurred to me.

June 02, 2008

Quickly Migrate all database times to UTC

Listen to this articleListen to this article

If you're thinking about updating to Rails 2.1 to get the timezone support, you'll need to update all database records to UTC. Here's a quick migration script to do just that:

class ConvertTimestampsToUtc < ActiveRecord::Migration
  # Assume all times were in UTC+10:00
  OFFSET = "interval '10 hours'"

  # Adjust any date/time column
  COLUMN_TYPES = [:datetime, :timestamp]

  def self.up
    adjust("-")
  end

  def self.down
    adjust("+")
  end

  private
  
    def self.adjust(direction)
      connection = ActiveRecord::Base.connection
      connection.tables.each do |table|
        columns = connection.columns(table).select { |column| COLUMN_TYPES.include?(column.type) }
        updates = columns.map { |column| "#{column.name} = #{column.name} #{direction} #{OFFSET}"}.join(", ")
        execute("UPDATE #{table} SET #{updates}") unless updates.blank?
      end
    end
end

As you can see, I've assumed that the dates were previously stored as AEST (UTC+10:00) so you'll likely need to adjustthat and I'm also assuming PostgreSQL for date manipulation though it should be pretty simple to convert to run under MySQL. It may even work asis.