Navigation Menu

Skip to content

Commit

Permalink
Added support for composite primary keys, legacy systems, specs TBD yet
Browse files Browse the repository at this point in the history
  • Loading branch information
pulley committed Mar 25, 2011
1 parent 53cb56b commit fce2419
Showing 1 changed file with 32 additions and 30 deletions.
62 changes: 32 additions & 30 deletions lib/acts_as_revisionable/revision_record.rb
Expand Up @@ -3,12 +3,12 @@

module ActsAsRevisionable
class RevisionRecord < ActiveRecord::Base

before_create :set_revision_number
attr_reader :data_encoding

set_table_name :revision_records

class << self
# Find a specific revision record.
def find_revision (klass, id, revision)
Expand All @@ -30,7 +30,7 @@ def truncate_revisions (revisionable_type, revisionable_id, options)
delete_all(['revisionable_type = ? AND revisionable_id = ? AND revision <= ?', revisionable_type.base_class.to_s, revisionable_id, start_deleting_revision.revision])
end
end

def create_table
connection.create_table :revision_records do |t|
t.string :revisionable_type, :null => false, :limit => 100
Expand All @@ -43,7 +43,7 @@ def create_table
connection.add_index :revision_records, [:revisionable_type, :revisionable_id, :revision], :name => "revisionable", :unique => true
end
end

# Create a revision record based on a record passed in. The attributes of the original record will
# be serialized. If it uses the acts_as_revisionable behavior, associations will be revisioned as well.
def initialize (record, encoding = :ruby)
Expand All @@ -54,19 +54,19 @@ def initialize (record, encoding = :ruby)
associations = record.class.revisionable_associations if record.class.respond_to?(:revisionable_associations)
self.data = Zlib::Deflate.deflate(serialize_hash(serialize_attributes(record, associations)))
end

# Returns the attributes that are saved in the revision.
def revision_attributes
return nil unless self.data
uncompressed = Zlib::Inflate.inflate(self.data)
deserialize_hash(uncompressed)
end

# Restore the revision to the original record. If any errors are encountered restoring attributes, they
# will be added to the errors object of the restored record.
def restore
restore_class = self.revisionable_type.constantize

# Check if we have a type field, if yes, assume single table inheritance and restore the actual class instead of the stored base class
sti_type = self.revision_attributes[restore_class.inheritance_column]
if sti_type
Expand All @@ -80,9 +80,9 @@ def restore
# Seems our assumption was wrong and we have no STI
end
end

attrs, association_attrs = attributes_and_associations(restore_class, self.revision_attributes)

record = restore_class.new
attrs.each_pair do |key, value|
begin
Expand All @@ -91,20 +91,20 @@ def restore
record.errors.add(key.to_sym, "could not be restored to #{value.inspect}")
end
end

association_attrs.each_pair do |association, attribute_values|
restore_association(record, association, attribute_values)
end

record.instance_variable_set(:@new_record, nil) if record.instance_variable_defined?(:@new_record)
# ActiveRecord 3.0.2 and 3.0.3 used @persisted instead of @new_record
record.instance_variable_set(:@persisted, true) if record.instance_variable_defined?(:@persisted)

return record
end

private

def serialize_hash (hash)
encoding = data_encoding.blank? ? :ruby : data_encoding
case encoding.to_sym
Expand All @@ -116,7 +116,7 @@ def serialize_hash (hash)
return Marshal.dump(hash)
end
end

def deserialize_hash (data)
if data.starts_with?('---')
return YAML.load(data)
Expand All @@ -126,7 +126,7 @@ def deserialize_hash (data)
return Marshal.load(data)
end
end

def set_revision_number
last_revision = self.class.maximum(:revision, :conditions => {:revisionable_type => self.revisionable_type, :revisionable_id => self.revisionable_id}) || 0
self.revision = last_revision + 1
Expand All @@ -136,7 +136,7 @@ def serialize_attributes (record, revisionable_associations, already_serialized
return if already_serialized["#{record.class}.#{record.id}"]
attrs = record.attributes.dup
already_serialized["#{record.class}.#{record.id}"] = true

if revisionable_associations.kind_of?(Hash)
record.class.reflections.values.each do |association|
if revisionable_associations[association.name]
Expand All @@ -156,14 +156,14 @@ def serialize_attributes (record, revisionable_associations, already_serialized
end
end
end

return attrs
end

def attributes_and_associations (klass, hash)
attrs = {}
association_attrs = {}

if hash
hash.each_pair do |key, value|
if klass.reflections.include?(key.to_sym)
Expand All @@ -173,16 +173,16 @@ def attributes_and_associations (klass, hash)
end
end
end

return [attrs, association_attrs]
end

def restore_association (record, association, association_attributes)
association = association.to_sym
reflection = record.class.reflections[association]
associated_record = nil
exists = false

begin
if reflection.macro == :has_many
if association_attributes.kind_of?(Array)
Expand All @@ -192,8 +192,10 @@ def restore_association (record, association, association_attributes)
end
else
associated_record = record.send(association).build
associated_record.id = association_attributes['id']
exists = associated_record.class.find(associated_record.id) rescue nil
associated_record.class.primary_key.each do |key|
associated_record.send("#{key.to_s}=", association_attributes[key.to_s])
end
exists = associated_record.class.find(associated_record.send(c.class.primary_key)) rescue nil
end
elsif reflection.macro == :has_one
associated_record = reflection.klass.new
Expand All @@ -206,9 +208,9 @@ def restore_association (record, association, association_attributes)
rescue => e
record.errors.add(association, "could not be restored from the revision: #{e.message}")
end

return unless associated_record

attrs, association_attrs = attributes_and_associations(associated_record.class, association_attributes)
attrs.each_pair do |key, value|
begin
Expand All @@ -218,11 +220,11 @@ def restore_association (record, association, association_attributes)
record.errors.add(association, "could not be restored from the revision") unless record.errors[association]
end
end

association_attrs.each_pair do |key, values|
restore_association(associated_record, key, values)
end

if exists
associated_record.instance_variable_set(:@new_record, nil) if associated_record.instance_variable_defined?(:@new_record)
# ActiveRecord 3.0.2 and 3.0.3 used @persisted instead of @new_record
Expand Down

0 comments on commit fce2419

Please sign in to comment.