GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

public
Description: Rails plugin that provides the ability to soft delete models
Clone URL: git://github.com/ajh/acts_as_soft_deletable.git
ajh (author)
Mon May 19 12:51:45 -0700 2008
commit  0ff304f767832f8dd63d23e1e907b104e4b9efee
tree    18039f17bc54f09da4fd9de83b82e5877d21437e
parent  e0f394637eb60cda51a67e6ed3bee0134a8cb212
name age message
file .gitignore Mon May 19 12:51:45 -0700 2008 add ignore file. [ajh]
file COPYING Thu May 15 14:42:07 -0700 2008 Add GPL license and copyright notice. [ajh]
file README Mon May 19 11:15:25 -0700 2008 Update the acts_as_paranoid comparison and the ... [ajh]
file Rakefile Thu Mar 13 12:32:00 -0700 2008 make even more friendly to autotest [(no author)]
file init.rb Thu Mar 13 14:52:16 -0700 2008 add test helper and use them internally. Update... [(no author)]
directory lib/ Mon May 19 11:15:25 -0700 2008 Update the acts_as_paranoid comparison and the ... [ajh]
directory test/ Thu Mar 13 14:52:16 -0700 2008 add test helper and use them internally. Update... [(no author)]
README
Copyright (C) 2008 Substantial and Andy Hartford <hartforda @ gmail.com>

=== acts_as_soft_deletable ===

This plugin provides the ability to soft delete ActiveRecord models. When 
models are destroyed, they will be archived into a special deleted table. 
They can later be restored easily.

<pre>

class Artist < ActiveRecord::Base
  acts_as_soft_deletable # This will wrap the destroy method to provide soft delete 
                         # support and create a new ActiveRecord class called Artist::Deleted
end

model = Artist.find(34)
model.destroy   # removes row from artists table, and adds a row to 
                # deleted_artists table

deleted = Artist::Deleted.find(34)
deleted.undestroy!  # adds the row back to the artists table, and removes 
                    # if from the deleted_artists table

restored = Artist.find(34) # The artist is restored with all the same 
                           # information. The updated_at column will be 
                           # Time.now if it exists.

</pre>

=== Compare to acts_as_paranoid ===

Acts_as_paranoid takes the approach of using a deleted_at flag in the models table. If the deleted_at column has a 
value, the row is considered 'deleted'. The problem with this approach is that all finds on the model have to exclude 
'deleted' rows. This turns out be be a challenge. Acts_as_paranoid patches the ActiveRecord internals to accomplish 
this, but it is fragile and could break with future changes to ActiveRecord. Also, some of the more exotic finds 
currently don't work (has_many :through with polymorphism as of March 2008) and supporting them means running on an 
upgrade treadmill to keep up with the evolution of ActiveRecord.

This plugin avoids these problems by allowing the row to be deleted and archiving it into another table. The behavior of 
ActiveRecord::Base#Find doesn't have to change which should mean that this plugin is more immune to breaking due to 
ActiveRecord development. Queries on the live table will also be faster in the case of lots of deleted rows, because 
they will be in a seperate table. The tradeoff is that the deleted table needs to be maintained along with the live 
table. For example, if the artists table adds a column in a future migration, then the deleted_artists table needs that 
column as well.

A migration helper is available (see below) that will help keep the deleted table in sync. Also, a unit test helper is 
provided (again, see below) which adds unit tests to the model ensuring soft delete is working. If this is used, the 
test will fail if the deleted table gets out of sync.

=== Setup ===

---+ Model

Any ActiveRecord class that wants the soft delete functionality should add
the following line to their class definition:

<pre>
class SomeModel < ActiveRecord::Base
  acts_as_soft_deletable
  ...
</pre>

---+ Migration

and setup the deleted table with the following migration:

<pre>
class AddActsAsSoftDeletable < ActiveRecord::Migration
  def self.up
    SomeModel::Deleted.create_table
  end

  def self.down
    SomeModel::Deleted.drop_table
  end
end
</pre>

Any changes to the original table (such as adding a column) should be reflected in the deleted table. Use the 
update_columns method:

<pre>
class AddSkuColumn < ActiveRecord::Migration
  def self.up
    add_column 'items', 'sku', :string
    Item::Deleted.update_columns # will add sku column
  end

  def self.down
    remove_column 'items', 'sku'
    Item::Deleted.update_columns # will remove sku column
  end
end
</pre>

Note that update_columns will happily delete columns if asked which will lose data forever. I'm not sure if this 
behavior is fine or horrifying, but it's definitely something to keep in mind. I'd appreciate feedback on this.

---+ Unit tests

A model's soft delete capabilities can be easily unit tested by using this provided assert:

<pre>
  def test_soft_delete_works
    # will run the model through a destroy and undestroy while making sure all values were saved
    assert_model_soft_deletes( items(:radar_detector) )
  end
</pre>

This was developed with Test::Unit in mind. Not sure how well it works with rspec.

=== Thanks ===

Substantial, my employer for letting me release this
acts_as_paranoid and technoweenie, for a plugin I've got good years of use out of
acts_as_versioned, who's approach influenced this plugin
Danimal, for the feedback on rubyonrails-talk