Darwinweb

Converting Hash to OrderedHash in Serialized ActiveRecord Column

November 1, 2012     

Suppose you have an ActiveRecord class like this:

class Report < ActiveRecord::Base
  serialize :options, Hash
end

And, because you are foolishly still using Ruby 1.8 and doing direct comparisons of the serialized format you need to change it to this:

class Report < ActiveRecord::Base
  serialize :options, ActiveSupport::OrderedHash
end

The problem that you face is that the change will cause any existing rows to start throwing `ActiveRecord::SerializationTypeMismatch`. The solution is to run something like this in a migration or console simultaneous with the deploy:

@tables = %w(reports)
@tables.each do |table|
  puts "============== #{table} =============="

  query = "SELECT id,options FROM `#{table}` ORDER BY id"
  result = ActiveRecord::Base.connection.execute(query)

  result.each do |row|
    id = row[0]
    hash = YAML.load(row[1])

    if hash.is_a?(ActiveSupport::OrderedHash)
      puts "Skipping row ##{id} which is already an OrderedHash"
    else
      ordered_hash = ActiveSupport::OrderedHash.new
      hash.keys.sort{ |a,b| a.to_s <=> b.to_s }.each do |k|
        ordered_hash[k] = hash[k]
      end
      escaped_options = ActiveRecord::Base.sanitize(ordered_hash.to_yaml)
      query = "UPDATE `#{table}` SET options=#{escaped_options} WHERE id=#{id}"
      puts "Running " + query
      ActiveRecord::Base.connection.execute query
    end
  end

  puts ""
end