Rails Soft Delete with Paranoia gem

Sometimes, we need to delete records logically but not physically. Paranoia gem allows us to soft delete records. Records are marked as deleted by a column in a table. Article helps understand how to soft delete Records in Rails with Paranoia gem.

Install gem

Add the following line to the Gemfile.

Rails 3

gem "paranoia", github: "rubysherpas/paranoia", branch: "rails3"

Rails 4

gem "paranoia", github: "rubysherpas/paranoia", branch: "rails4"

Rails 5

gem "paranoia", github: "rubysherpas/paranoia", branch: "rails5"
Bundle install

Perform bundle install in order to use the gem in the Rails project.

bundle install
Add deleted_at to Model [Migration]

Let’s say, we have a model with name Post. In order to use soft delete feature with Post model, we would have to add a column to the posts table.

The migration can be added with Rails generator as given below.

bin/rails generate migration AddDeletedAtToClients deleted_at:datetime:index

Run the migration.

bundle exec rails db:migrate
Model changes

Inform the model to use soft delete feature by adding acts_as_paranoid to the model.

Example,

class Post < ApplicationRecord
  acts_as_paranoid
end
Test soft delete

Now, when we destroy any Post, the record will be soft deleted.

post = Post.first
post.destroy

When we perform destroy, it sets the deleted_at column. The SQL generated is shown below.

SQL (0.7ms)  UPDATE "posts" SET deleted_at = '2018-02-21 16:27:46.017491' WHERE "posts"."deleted_at" IS NULL AND "posts"."id" = $1  [["id", 185]]

The value of deleted_at column is set to the currrent time.

Really destroy

If we want to really destroy the record, and not perform the soft delete, we can use really_destroy! method provided by the gem.

post = Post.first
post.really_destroy!

This will fire up SQL to delete the record as given below.

SQL (0.4ms)  DELETE FROM "opinions" WHERE "opinions"."id" = $1  [["id", 184]]
Scopes provided by Paranoia

Paranoia gem provides a few scopes in order to fetch the deleted records.

  • with_deleted : This scope can be used if we want to search through deleted records as well.
Post.with_deleted.where(user_id: 1)
  • without_deleted: This scope can be used if we don’t want deleted records to be considered when querying.
Post.without_deleted.where(user_id: 1)
  • unscoped: Rails provides this one to exclude all the scopes applied to ActiveRecord relation when querying.
Instance methods
  • deleted? : Returns true / false. Paranoia exposes this method to find out if record is deleted.
  • paranoia_destroyed? : Returns true / false. Paranoia exposes this method to find out if record is deleted.
  • restore(ids) : Accepts id to be restored. This marks deleted_at column nil.

Summary

This comes pretty handy when we really do not want to hard delete records. This can be helpful for auditing. audited can be helpful in keeping audit of the model records. One of the disadvantage of using paranoia soft delete feature is, we need to take care of all the places when raw SQL is used. As paranoia adds default scope that gets only the records which have deleted_at set to null.