Skip to content

Instantly share code, notes, and snippets.

@rusterholz
Last active February 10, 2016 23:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rusterholz/a454dae50e2c50ab0127 to your computer and use it in GitHub Desktop.
Save rusterholz/a454dae50e2c50ab0127 to your computer and use it in GitHub Desktop.
Migration to fix serialized attributes on Versions made with paper_trail 4.0.2 or earlier
class PatchVersions < ActiveRecord::Migration
# This modifies PaperTrail versions made with 4.0.2 or before to be compatible with 4.1.0.
#
# The difference is that 4.0.2 stored serialized attributes in their native (reified) form,
# while 4.1.0 and above store serialized attributes in their serialized form; e.g.:
# {'foo'=>52, 'bar'=>'baz'} <----- 4.0.2 yamlizes this Hash
# '{"foo":52,"bar":"baz"}' <----- 4.1.0 yamlizes this String
# This causes 4.1.0 to raise errors when attempting to reify versions created with 4.0.2.
#
def up
# Get the list of all extant model classes currently referenced by PaperTrail versions:
models = PaperTrail::Version.pluck(:item_type).uniq.sort.map do |k|
begin
k.constantize
rescue NameError
nil
end
end.compact
# Determine which ones have serialized attributes:
serialized = Hash[ models.zip( models.map{ |m| m.columns.select{ |c| ActiveRecord::Type::Serialized === c.cast_type } } ) ]
serialized.delete_if{ |_,c| c.blank? }
# Perform all fixes in a single transaction so no data is lost if the migration fails:
PaperTrail::Version.transaction do
serialized.each do |klass,cols|
# For each affected model, correct each affected Version record:
print "Patching #{klass.name} versions (#{cols.map(&:name).join(', ')})"
PaperTrail::Version.where( item_type: klass.name ).each do |v|
obj = v.object && YAML.load( v.object )
chgs = v.object_changes && YAML.load( v.object_changes )
cols.each do |c|
# For each serialized attribute, if this version contains that attribute, replace it with its serialized form:
obj[ c.name ] = c.cast_type.coder.dump( obj[ c.name ] ) if obj && obj[ c.name ]
chgs[ c.name ].map!{ |x| x && c.cast_type.coder.dump(x) } if chgs && chgs[ c.name ]
end
# Replace the YAML strings on the Version record with the corrected strings:
updates = {}
updates[:object] = YAML.dump( obj ) if obj
updates[:object_changes] = YAML.dump( chgs ) if chgs
v.update! updates if updates.present?
print '.'
end
puts
end
end
end
def down
raise ActiveRecord::IrreversibleMigration # In principle this could be done, but has not been implemented.
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment