Hiding data in Rails with default_scope

One of active record’s less well known features is the default_scope class method. I've just added support for deleting cards to The Agile Planner. Rather than destroy users’ data immediately when they click the delete button, I wanted to give them an opportunity to recover from mistakes, but I didn't have time to implement a full undo system right now.

My solution was to hide cards from the user interface after they've been deleted, and to write a cron job that will periodically remove old deleted cards from the database.

Yes, this is a bit basic, but it's not worth me adding full undo/redo support yet, as there are plenty of more important things to work on. If a user deletes a card by mistake, and they really need the data that's on it, they can always ask me to restore it from backup. Rather than digging through my backups I'll just be able to toggle a setting on the card to bring it back to life. And you never know, accidental deletion might be a very rare event. I'll improve it if I need to.

The relevant sections of my ActiveRecord models look like this:

class Iteration < ActiveRecord::Base
  has_many :cards

class Card < ActiveRecord::Base
  belongs_to :iteration
  default_scope where(deleted: false)

So to find all the cards that belong to an iteration and haven't been deleted I can just use the cards scope. It'll filter all the deleted cards out automatically:

iteration.cards  # <- no deleted cards here

If you want to get at the deleted cards, you can use the unscoped method to remove the default scope.

Card.unscoped { iteration.cards }  # <- also returns deleted cards

It's such a simple approach that it took me less than an hour to develop and deploy the deletion of cards into production.

A warning

This isn't something that I'd advocate using very often. Opportunities for misleading yourself and your co-workers are legion, but when it's a good fit for a problem like this it comes in very handy. I really didn't want to trawl round the app to find all of the methods that iterate over cards and manually filter out the deleted ones.

I love feedback and questions -- please say hello on Twitter or leave a comment.