svn://rubyforge.org/var/svn/phillyonrails/acts_as_restorable/trunk
During the last Philly on Rails Pub Nite, the topic came up of deleting records. It seems as though the general consensus is that models that are too important to Just-Delete, should probably have a deleted column...and every piece of logic everywhere else in your system will be built around this deleted flag.
Apparently, acts_as_paranoid does a reasonable job of getting around this. I'm (surprisingly enough) a little more partial to my acts_as_filterable implementation...as it seems as less of a HackLayer dealing with deleted flags.
Anyway, I've inherited a pretty large code base, where the convention was The Deleted Flag. Damn never every model implements it. "Even joining tables?" you ask. Even joining tables. That's where acts_as_filterable came from.
So, I've gotten the chance to re-write this beast. I wanted deleted rows to be out of my uber-clean database. Out. As in not there. But...it was kind of nice having sensitive data still being alive somewhere. I, ideally, wanted rows nuked from my model, but somehow "Not Dead." I've illustrated this thought in the following picture:

So, here's how she works: You destroy a row, it "Goes away." By which I mean, it gets serialized (along with all its dependencies) to XML, and put in another database with a timestamps, and who deleted it. Thanks to Plugin a Week's Routing Plugin, there's a simple controller to look at records recently nuked, and pull them back in to the live database.
Sound like a bit much? Possibly. Having a deleted column in your important models, and building code around that? That's just gross. That needs to stop. Here's some tests.
First ye test models:
class RestorableModel < ActiveRecord::Base
acts_as_restorable
has_many :restorable_dependents, :dependent => :destroy
end
class RestorableDependent < ActiveRecord::Base
belongs_to :restorable_model
end
class ModelWithDatatype < ActiveRecord::Base
acts_as_restorable
end
class UnrestorableModel < ActiveRecord::Base; end
And some test cases:
def test_simple_backup
assert_equal 0, DeletedRecord.find(:all).size
assert_equal 0, RestorableModel.find(:all).size
good_record = RestorableModel.create(:name => 'Good Record')
assert_equal 1, RestorableModel.find(:all).size
good_record.destroy
assert_equal 0, RestorableModel.find(:all).size
assert_equal 1, DeletedRecord.find(:all).size
end
def test_alternate_data_types
time_stamp = Time.now
mwd = ModelWithDatatype.create(:int => 1, :bool => false, :tmstp => time_stamp)
mwd.destroy
assert_equal 1, DeletedRecord.find(:all).size
rec = DeletedRecord.find(:first).restore
assert_equal 1, rec.int
assert_equal false, rec.bool
assert rec.tmstp.is_a?(Time)
end
def test_dependents
r = RestorableModel.create(:name => 'Parent Record')
d = RestorableDependent.new
r.restorable_dependents << d
assert_equal 1, RestorableDependent.find(:all).size
assert_equal 1, RestorableModel.find(:all).size
assert_equal 1, r.restorable_dependents.size
assert r.destroy
assert_equal 0, RestorableDependent.find(:all).size
assert_equal 0, RestorableModel.find(:all).size
end
So, yeah. I'm still sorting out where to host this crap. I'll get back to you real soon.
svn://rubyforge.org/var/svn/phillyonrails/acts_as_restorable/trunk
