Wednesday, January 05, 2011

Deleting objects in Rails - using deleted_at

I wanted to allow a user to delete an object, but also to keep the object in the database so other elements could continue to refer to it. Basically, I didn't want ActiveRecord to destroy the object, just hide it. Forever.

This was in a content management project where a site may have many pages. Users were able to delete the page, and I wanted to keep the "activity log" complete. So I could refer to the page, but not show it. "Jim deleted the page 'welcome'", for example. Where welcome is the

The page name was unique and I didn't want to do anything too complicated to ensure the names were unique - if there was an existing name already, don't allow another. But, if a deleted page had the name 'Welcome', I didn't want to see a validation error.

This is an easy situation to handle...

First, add a mark_deleted method in the Model.

def mark_deleted
self.deleted_at =

def mark_deleted!

For the name, set the uniqueness validation to have a scope

validates_uniqueness_of :name, :scope => [:parent_id, :deleted_at]

You'll need to add the deleted_at column via a migration

class AddDeletedAtToObject < ActiveRecord::Migration
def self.up
add_column :objects, :deleted_at, :datetime

def self.down
add_column :objects, :deleted_at

When you call the 'destroy' method in the controller, instead of object.destroy!, call object.mark_deleted!

And when referring to parent.objects, call parent.objects.not_deleted, instead.

scope :not_deleted, where('objects.deleted_at is null')

1 comment:

  1. On issue I had with this approach (using rails3_acts_as_paranoid, but it's the same principle) is that you have to way to getting rid of associations. I have a complex model with associations all around, and I really cannot afford to mess it up. So the only way to remove users in my case is: user.destroy. Calling user.mark_deleted! ends up in a lot of "broken links" (from contacts, likes, etc).

    One way would be to have all liked model act[ing]_as_paranoid, but I don't know if destroy works in this case... I am interesed if you have a fix for that