acts_as_paranoid_versioned or acts_as_versioned + acts_as_paranoid

Posted by acts_as_flinn Thu, 15 Feb 2007 07:04:00 GMT

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)

Comments

Leave a response

Comments


ss_blog_claim=746d258dc975cb7923cc57154dbf1d71