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.