#
# Audit saves the changes to ActiveRecord models. It has the following attributes:
#
# * auditable: the ActiveRecord model that was changed
# * user: the user that performed the change; a string or an ActiveRecord model
# * action: one of create, update, or delete
# * changes: a serialized hash of all the changes
# * created_at: Time that the change was performed
#
class Audit < ActiveRecord::Base
belongs_to :auditable, :polymorphic => true
belongs_to :user, :polymorphic => true
before_create :set_version_number
serialize :changes
cattr_accessor :audited_classes
self.audited_classes = []
# Allows user to be set to either a string or an ActiveRecord object
def user_as_string=(user) #:nodoc:
# reset both either way
self.user_as_model = self.username = nil
user.is_a?(ActiveRecord::Base) ?
self.user_as_model = user :
self.username = user
end
alias_method :user_as_model=, :user=
alias_method :user=, :user_as_string=
def user_as_string #:nodoc:
self.user_as_model || self.username
end
alias_method :user_as_model, :user
alias_method :user, :user_as_string
def revision
attributes = self.class.reconstruct_attributes(ancestors).merge({:version => version})
clazz = auditable_type.constantize
returning clazz.find_by_id(auditable_id) || clazz.new do |m|
m.attributes = attributes
end
end
def ancestors
self.class.find(:all, :order => 'version',
:conditions => ['auditable_id = ? and auditable_type = ? and version <= ?',
auditable_id, auditable_type, version])
end
def self.reconstruct_attributes(audits)
changes = {}
result = audits.collect do |audit|
attributes = (audit.changes || {}).inject({}) do |attrs, (name, (_,value))|
attrs[name] = value
attrs
end
changes.merge!(attributes.merge!(:version => audit.version))
yield changes if block_given?
end
block_given? ? result : changes
end
private
def set_version_number
max = self.class.maximum(:version,
:conditions => {
:auditable_id => auditable_id,
:auditable_type => auditable_type
}) || 0
self.version = max + 1
end
end