I had a requirement to update a field that went into production with no restrictions to include some restrictions and with that the need to update pre-existing data. I wrote a migration responsible for handling the conversion and I wanted to test all the cases. Here's how I solved the problem, using mocks (Mocha specifically, but rspec's mocks would also work) instead of trying to cram legacy data into the database.
Here's the migration:
# db/migrate/20130113181838_update_public_name_values.rb
class UpdatePublicNameValues < ActiveRecord::Migration
def up
User.select("id, public_name").each do |user|
next if user.public_name.nil?
original_public_name = user.public_name.clone
user.public_name.gsub!(/[^A-Za-z0-9_]/, '_')
user.public_name = user.public_name.slice(0,12)
begin
if original_public_name != user.public_name
user.save!
end
rescue
puts "Unable to update #{user.id} (#{original_public_name} => #{user.public_name})"
end
end
end
def down
puts "irreversible"
# a more responsible migration would make this reversible
end
end
And the spec (no I did not TDD this, I wrote the migration first and the tests second, tsk tsk):
# spec/db/migrate/update_public_name_values_spec.rb
require "spec_helper"
require "#{Rails.root}/db/migrate/20130113181838_update_public_name_values"
describe UpdatePublicNameValues do
describe "#up" do
let(:special_char) { User.new(public_name:'Bits & Bytes') }
let(:long_name) { User.new(public_name:'This is a very long name') }
let(:normal_name) { User.new(public_name:'SomeUser') }
let(:error_name) { User.new(public_name:'Causes Error') }
let(:nil_name) { User.new(public_name:nil) }
before do
User.expects(:select).with("id, public_name")
.returns([special_char, long_name, normal_name, error_name, nil_name])
special_char.expects(:save!)
long_name.expects(:save!)
normal_name.expects(:save!).never
error_name.expects(:save!).throws(RuntimeError)
nil_name.expects(:save!).never
end
it "performs the appropriate actions" do
UpdatePublicNameValues.new.up
special_char.public_name.should == "Bits___Bytes"
long_name.public_name.should == "This_is_a_ve"
end
end
end
I also ran across this post which shows how to verify structural changes during migrations.