acts_as_paranoid and acts_as_versioned on Rails 3

A few years ago, I described how to combine acts_as_paranoid and acts_as_versioned in order to make deleted records end up in your versioning tables.

In order to do the same thing under Rails 3, I had to make a few adjustments. First of all, you need the rails3_acts_as_paranoid gem, which is a total rewrite of acts_as_paranoid for rails 3. Add these lines to your Gemfile:

gem 'rails3_acts_as_paranoid'
gem 'acts_as_versioned'

Then put a file in config/initializers with these contents:

module ActiveRecord
  module Acts
    module Versioned
      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()
            with_transaction_returning_status do
              run_callbacks :destroy do
                # call the acts_as_paranoid delete function
                self.class.delete_all(:id => self.id)

                # get the 'deleted' object
                tmp = self.class.unscoped.find(id)

                # run it through the equivalent of acts_as_versioned's
                # save_version(). We used to call that function but it is a
                # noop when @saving_version is not set. That only gets done in
                # a protected function set_new_version(). Easier to just
                # replicate the meat of the save_version() function here.
                rev = tmp.class.versioned_class.new
                clone_versioned_model(tmp, rev)
                rev.send("#{tmp.class.version_column}=", send(tmp.class.version_column))
                rev.send("#{tmp.class.versioned_foreign_key}=", id)
                rev.save

                # and finally really destroy the original
                self.class.delete_all!(:id => self.id)
              end
            end
          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

I wonder if there is a more elegant way to achieve this…

Note: code updated at 2011-05-28 to make sure :dependent => :destroy on has_many associations does the right thing.

This entry was posted in Rails. Bookmark the permalink.
Add Comment Register

Leave a Reply