So you’re writing a web-based application and you want journalling: when database records change, you want to keep track of the changes so that you have an audit trail. There are many ways to do this. If your application is going to be dealing with a lot of data, it’s best to have a separate ‘versions’ table for each table, where you store the old versions of your records.A couple of plugins for Rails exist to make this easier. The acts_as_versioned plugin deals with journalling for updates to records. It does not journal deletes – that’s where acts_as_paranoid comes in. That plugin will put a timestamp in the deleted_at column rather than delete the record, and it overrides the various find methods to ignore records with a non-null deleted_at column.
The 2 plugins don’t work together all that well without some modification, as described by Flinn Mueller here.
His solution solves part of the problem – deleting a record no longer deletes the versioned history of the record. But it still leaves the original record in the main table, with a deleted_at timestamp. I want to move that ‘deleted’ record into the versions table, with accurate deleted_at timestamp.
Here’s how I modified Flinn’s code to do that:
module ActiveRecord
module Acts
module Versioned
module ClassMethods
def acts_as_paranoid_versioned(options = {})
acts_as_paranoid
acts_as_versioned options
# Override the destroy method. We want deleted records to end up in the versioned table,
# not in the non-versioned table.
self.class_eval do
def destroy()
transaction {
destroy_with_callbacks # call the acts_as_paranoid delete function
tmp = self.class.find_with_deleted(id) # get the 'deleted' object
tmp.save_version() # run it through acts_as_versioned's save_version()
tmp.destroy_without_callbacks! # and finally really destroy the original
}
end
end
# protect the versioned model
self.versioned_class.class_eval do
def self.delete_all(conditions = nil); return; end
end
end
end
end
end
end
This code goes in config/environment.rb. As you can see it’s a bit of a hack, but it works. If you have suggestions to do this more elegantly, by all means leave a comment.
I think it’s time someone merges acts_as_paranoid into acts_as_versioned. Maybe I’ll look at that one day when I have more time – for now this will do.
Pingback: Latest Bookmarks on Ma.gnolia.com at Ivan Enviroman
Pingback: All About Ruby
Pingback: acts_as_paranoid and acts_as_versioned on Rails 3 | Off you go... into the purple yonder!