A subtle problem with using update_attributes in Rails migrations

It’s fairly common to create a Rails migration that adds a column, then stuffs data into that column for legacy data, for example the addition of a guid field.

However, using update_attributes can quietly bungle your database by skipping certain records if you have added any new validations which did not apply to legacy data in the database.

For example, suppose you’ve had a Widgets table for a long time, and it has lots of existing Widgets in it. Originally, the name of a Widget could be any length, but a few months ago you added a validation so a Widget name should be at least 10 characters going forward.

Now you want to add an 8 character guid to Widgets. If you run the following migration using update_attributes (instead of a save(:validate => false) as I show here) your migration will pass, so you’ll think everything is fine, BUT any widgets with a too-short name will not receive their guid because the valiation fails.

Obviously there ARE times when you want validations when adding data, but probably not in a case like this where you are adding a new field with a use that is independent of any prior data values.

class AddGuidToWidgets < ActiveRecord::Migration  
  def self.up
    add_column :widgets, :public_guid, :string

      # MUST reset to ensure new column info used
    Widget.find(:all).each do |w|
      g = (0...8).map { 65.+(rand(25)).chr}.join
      while !FinderItem.find_by_public_guid(g).nil?
        g = (0...8).map { 65.+(rand(25)).chr}.join
        # can NOT use update_attributes because might have validation errors
        # due to new validations added on certain other fields
      w.public_guid = g 
      w.save( :validate => false )
      #DONT USE: w.update_attributes :public_guid => g