Acts As Soft Delete By Field

I’ve had a bit of spare time recently, so I thought it was about time that I created a public gem. I thought about my code and decided that my soft delete system would be a good place to start. I’d created it about three years ago and it’s been used in a couple of corporate projects. It is also fairly simple and self contained.

It had been created in a somewhat pragmatic way, using the simplest way that I could find to implement it. That was to create the functionality in an ActiveRecord::Base abstract class, and then have all the models that needed this functionality, inherit this class. I’ve made the code available as SoftDeleteableBase.

This approach works fine and is very easy to implement. The problem is that it does not scale very well. That is, if you need to add additional functionality in the same way, the new abstact class has to inherit from the original.

A much better way to add functionality to a model is by mixing in via a module. Having recently read Elequent Ruby by Russ Olsen (my favourite Ruby author), and Metaprogramming Ruby by Paolo Perrotta (the two books complement each other very well), I felt I should rewrite SoftDeletableBase as an ‘Act as’ plugin.

The result was ActsAsSoftDeleteByField. A bit verbose, but as ActsAsSoftDeletable already existed I needed to differentiate my plugin from that. ActsAsSoftDeletable moves deleted items into another table, which was not been the functionality I wanted. My system uses a field in the model’s table to flag when a record is deleted. A simple solution, but ActsAsSoftDeletable maybe more suitable for many projects.

The two books gave me the knowledge I needed to understand how the methods used to create the mixin can be used. However, I found it very useful to look at other Acts As plugins such as acts_as_rateable, and acts_as_nested_set, to see how to arrange the code. The Ruby on Rails Guide was also useful.

Loading instance and class methods is actually fairly straight forward. Most of the work is done by this bit of code:

def self.included(base)
   base.send :extend, ClassMethods
 end

Getting the code to load properly was more difficult. It took me a little fiddling to get the init.rb file to work correctly. A lot of examples use class_eval here, but as I know the class names, I chose to use the simpler technique of monkey patching:

$:.unshift "#{File.dirname(__FILE__)}/lib"
require 'soft_delete_by_field'

class ActiveSupport::TestCase
  include ActsAsSoftDeleteByFieldAssertions
end

class ActiveRecord::Base
  include ActiveRecord::Acts::SoftDeleteByField
end

I used a set of assertions I’d used to test SoftDeleteableBase to test the new mixin, and included this code in the new plugin. That made it a lot easier to check the functionality. I created a new Rails app, and generated a Thing model. I then applied acts_as_soft_delete_by_field to thing, made sure it had a :deleted_at field, and ran the tests.

The next thing I did was to extend the functionality so that an alternative field could be used to store the deleted information. A few modifications and that was all working.

The last step was to create the gem. Russ Olsen’s book gave me the basics. The main thing that caused me problems was again initiating the script.

The main problem is that many of the examples on the web use the old system relying on /rails/init.rb, but I couldn’t get this to work. In the end, I realised that a file with the gem name was always loaded, and therefore that would be the place to put the hook script. So acts_as_soft_delete_by_field.rb was created and I put a hook script there. With that in place everything started working a treat.

The end result was the acts_as_soft_delete_by_field gem!

This entry was posted in Ruby. Bookmark the permalink.