acts_as_paranoid_versioned or acts_as_versioned + acts_as_paranoid
I know what you are thinking, and yes this is the best title I could come up with: acts_as_paranoid_versioned does the trick.
A question to the PhillyOnRails mailing list prompted me to get around to this one.
acts_as_versioned and acts_as_paranoid are two really great plugins that make enterprisey|auditable|accountable rails based projects much easier.
acts_as_versioned stores changes made to records in a separate table for historic purposed, think wiki entries, order processing, cms pages, inventory, etc.
acts_as_paranoid overrides the destroy and finder methods in order to “delete” object but keep data around in the database. This is perfect for wiki entries, order processing, cms pages, inventory, etc.
Problem
Unfortunately using both plugins causes version records to be destroyed even though the base model keep the record around (setting deleted_at).
The problem with using them both lies in acts_as_versioned’s implementation. acts_as_versioned dynamically creates a versioned class then adds has_many in your base model. The plugin naturally relies on rails to handle cleanup for an object with :dependent => :delete_all. I say naturally because you really wouldn’t want old versions of a destroyed object hanging around… unless of course you did want old versions of a destroyed object hanging around.
“I want to use both acts_as_versioned and acts_as_paranoid to create an audit trail!”
So here’s the fix…
Code
Add this to config/environment.rb
module ActiveRecord
module Acts
module Versioned
module ClassMethods
def acts_as_paranoid_versioned
acts_as_paranoid
acts_as_versioned
# protect the versioned model
self.versioned_class.class_eval do
def self.delete_all(conditions = nil); return; end
end
end
end
end
end
end
Then instead of calling both acts_as_versioned and acts_as_paranoid call acts_as_paranoid_versioned in your model.
class Example < ActiveRecord::Base
acts_as_paranoid_versioned
end
Have your cake and eat it too
“Yay, it works!”
With this setup, the versioned record doesn’t pick up deleted_at. If you want to do that or add any other special functionality, change
def self.delete_all(conditions = nil); return; end
to
def self.delete_all(conditions = nil)
# foo
end
Recommendations for acts_as_versioned
Make version_association_options available to override. At current, version_association_options gets set then class_eval puts it into action creating the callback for before_destroy. If version_association_options were placed outside the acts_as_versioned class method (it’s own method or attr) it would be much easier to override the delete_all behavior. For example: self.version_association_options.delete(:dependent)

