The hard truth about soft deletion

Sally Hall

Recently, while porting code from C# to Rails, we found that in the existing app, most tables had a deleted_at column. We wanted to make sure we were being thoughtful about what we brought over into the Rails app, so this led to a conversation about soft deletion. I am lucky enough to be working with people who don’t accept “well we’ve always done it that way” as a reason to do something, so we dug into why we might want to implement soft deletion and what the tradeoffs would be.

What is soft deletion?

Soft deletion is a way of marking records as “deleted” and removing them from the user’s experience without actually destroying the record. This makes it possible to restore accidentally deleted records and also preserves history. If you’re working in a domain where there are legal requirements to maintain records for a certain amount of time, you may use soft delete to allow users to remove records from their workflow while maintaining them for compliance. Soft delete can also come in handy when a user workflow is likely to involve users wanting to “undo” a delete, like restoring a deleted branch on GitHub.

What are the downsides?

When working on a codebase that uses soft deletion, developers need to remember that not all records in the database should be included in queries or displayed. There are several soft deletion gems that can help with this when you’re using Active Record, but if you need to query the database with raw SQL or if you’re accessing the data through a non-Rails app, you’ll have to remember to manually exclude the deleted records. I’ve never worked on a codebase that included “everybody remember to always do X” where everybody actually always remembered that. We’ve got a lot in our brains and I haven’t found the brain setting yet that prioritizes quirks about a particular codebase over Gilmore Girls quotes.

This gets trickier with dependent records. With regular deletion, you can create associations with dependent: :destroy and be confident that when you delete a record, all its associated records are also deleted. When using a gem like paranoia, you have to be careful that associated records are also marked for soft deletion. If you delete a parent record that is marked for soft deletion, but it has dependent records that are not, those records will be fully deleted, which may not be your intention. Let’s add this to the list of things developers need to remember to do when using soft deletion.

It also complicates indexes. To keep the speed advantage of your existing indexes, you’ll need to make a partial index if your database supports it, and scope the index to only include non-deleted records. You’ll also need to update uniqueness constraints to exclude deleted records. And maybe also think of how to handle when you want to un-delete a record that conflicts with an existing record. Cool, I definitely won’t forget to do that.

What should I do instead?

This depends on why you’re considering soft deletion in the first place. Is there a stakeholder who has concerns about losing important data? Maybe reliable, regular backups are enough to ease their mind. Do you have user complaints about unintentionally deleting things and wanting to restore them? Consider whether your UI could be clearer to prevent unintentional deletion. Is it just the way you have always done things? Well, you can certainly keep doing it. But it might be worth spending some time comparing the bugs and customer support issues you deal with related to soft deletion to the discomfort of trying something new. Do you just like remembering to do a lot of little extra things every time you write code? Maybe you can rent that brain space to help me remember where I parked, instead.

About thoughtbot

We've been helping engineering teams deliver exceptional products for over 20 years. Our designers, developers, and product managers work closely with teams to solve your toughest software challenges through collaborative design and development. Learn more about us.