Jan 2011

How the Immortal gem was born to replace acts_as_paranoid

A while ago, Saimon and I started upgrading Teambox to Rails 3. Once we got a bootable application (more on that in another post) the first big problem we faced was acts_as_paranoid. Its internals are a mess, so there was no chance we could just fix that for Rails 3. We decided we would just write a brand new gem that shared the API as much as possible.

The main goals were: implement almost the whole API from acts_as_paranoid (this way the impact of replacing it in Teambox would be minimal) and make it as small and lightweight as possible. We are true BDD believers, so we started writing a spec for what we needed. After that, writing the code was quite easy.

In our way, we changed some assumptions from acts_as_paranoid, such as the need of storing when a row was deleted. Because of that, we no longer needed a deleted_at field but a boolean deleted field. This change demands a migration:

class MigrateParanoidFields < ActiveRecord::Migration
  def self.up
    add_column :users, :deleted, :boolean
    add_index :users, [:deleted]
    User.update_all ["deleted = ?", true], "deleted_at IS NOT NULL"
    remove_column :users, :deleted_at

  def self.down
    add_column :users, :deleted_at, :datetime
    add_index :users, [:deleted_at]
    User.update_all ["deleted_at = ?",], ["deleted = ?", true]
    remove_column :users, :deleted

While doing all this, at some point I regretted changing the deleted field to a boolean, because it looks like every relational database out there decides to implement (or not) their own boolean data type. This is where Arel comes handy, because you can just do something like this:

default_scope where(arel_table[:deleted].eq(nil).or(arel_table[:deleted].eq(false)))

Although there has been discussions about the convenience of using default_scope for anything other than order, it just feels right to do it in this use case.

To sum up: the code is available at github or just as a Ruby Gem. Please feel free to contribute, Charles already did it!