Skip to content
This repository


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

ActiveRecord (>=3.0) plugin which allows you to hide and restore records without actually deleting them.

tree: 3a9a45d160

Fetching latest commit…


Cannot retrieve the latest commit at this time

Octocat-spinner-32 lib
Octocat-spinner-32 test
Octocat-spinner-32 .gitignore
Octocat-spinner-32 Gemfile
Octocat-spinner-32 Gemfile.lock
Octocat-spinner-32 LICENSE
Octocat-spinner-32 README.markdown
Octocat-spinner-32 Rakefile
Octocat-spinner-32 init.rb
Octocat-spinner-32 rails3_acts_as_paranoid.gemspec


A simple plugin which hides records instead of deleting them, being able to recover them.


This plugin was inspired by acts_as_paranoid and acts_as_active.

While porting it to Rails 3, I decided to apply the ideas behind those plugins to an unified solution while removing a lot of the complexity found in them. I eventually ended up writing a new plugin from scratch.


You can enable ActsAsParanoid like this:

class Paranoiac < ActiveRecord::Base


You can also specify the name of the column to store it's deletion and the type of data it holds:

  • :column => 'deleted_at'
  • :type => 'time'

The values shown are the defaults. While column can be anything (as long as it exists in your database), type is restricted to "boolean" or "time".


If a record is deleted by ActsAsParanoid, it won't be retrieved when accessing the database. So, Paranoiac.all will not include the deleted_records. if you want to access them, you have 2 choices:

Paranoiac.only_deleted # retrieves the deleted records
Paranoiac.with_deleted # retrieves all records, deleted or not

Real deletion

In order to really delete a record, just use:


You can also definitively delete a record by calling destroy or delete_all on it twice. If a record was already deleted (hidden by ActsAsParanoid) and you delete it again, it will be removed from the database. Take this example:

Paranoiac.first.destroy # does NOT delete the first record, just hides it
Paranoiac.only_deleted.destroy # deletes the first record from the database


Recovery is easy. Just invoke recover on it, like this:

Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover

All associations marked as :dependent => :destroy are also recursively recovered. If you would like to disable this behavior, you can call recover with the recursive option:

Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover(:recursive => false)

If you would like to change the default behavior for a model, you can use the recover_dependent_associations option

class Paranoiac < ActiveRecord::Base
    acts_as_paranoid :recover_dependent_associations => false

By default when using timestamp fields to mark deletion, dependent records will be recovered if they were deleted within 5 seconds of the object upon which they depend. This restores the objects to the state before the recursive deletion without restoring other objects that were deleted earlier. This window can be changed with the dependent_recovery_window option

class Paranoiac < ActiveRecord::Base
    has_many :paranoids, :dependent => :destroy

class Paranoid < ActiveRecord::Base
    belongs_to :paranoic

    # Paranoid objects will be recovered alongside Paranoic objects 
    # if they were deleted within 1 minute of the Paranoic object
    acts_as_paranoid :dependent_recovery_window => 1.minute

or in the recover statement

Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover(:recovery_window => 30.seconds)


ActiveRecord's built-in uniqueness validation does not account for records deleted by ActsAsParanoid. If you want to check for uniqueness among non-deleted records only, use the macro validates_as_paranoid in your model. Then, instead of using validates_uniqueness_of, use validates_uniqueness_of_without_deleted. This will keep deleted records from counting against the uniqueness check.

class Paranoiac < ActiveRecord::Base
  validates_uniqueness_of_without_deleted :name

Paranoiac.create(:name => 'foo').destroy => 'foo').valid? #=> true


Once you retrieve data using with_deleted scope you can check deletion status using deleted? helper:

Paranoiac.create(:name => 'foo').destroy
Paranoiac.with_deleted.first.deleted? #=> true


Watch out for these caveats:

  • You cannot use default_scope in your model. It is possible to work around this caveat, but it's not pretty. Have a look at this article if you really need to have your own default scope.
  • You cannot use scopes named with_deleted and only_deleted


Copyright © 2010 Gonçalo Silva, released under the MIT license

Something went wrong with that request. Please try again.