Paranoia

Paranoia is a re-implementation of acts_as_paranoid for Rails 3 and Rails 4, using much, much, much less code.

You would use either plugin / gem if you wished that when you called destroy on an Active Record object that it didn't actually destroy it, but just hide the record. Paranoia does this by setting a deleted_at field to the current time when you destroy a record, and hides it by scoping all queries on your model to only include records which do not have a deleted_at field.

If you wish to actually destroy an object you may call really_destroy!. WARNING: This will also really destroy all dependent: destroy records, so please aim this method away from face when using.

If a record has has_many associations defined AND those associations have dependent: :destroy set on them, then they will also be soft-deleted if acts_as_paranoid is set, otherwise the normal destroy will be called.

Installation & Usage

For Rails 3, please use version 1 of Paranoia:

gem "paranoia", "~> 1.0"

For Rails 4, please use version 2 of Paranoia:

gem "paranoia", "~> 2.0"

Of course you can install this from GitHub as well:

gem "paranoia", :github => "radar/paranoia", :branch => "master"
# or
gem "paranoia", :github => "radar/paranoia", :branch => "rails4"

Then run:

bundle install

Updating is as simple as bundle update paranoia.

Run your migrations for the desired models

Run:

rails generate migration AddDeletedAtToClients deleted_at:datetime:index

and now you have a migration

class AddDeletedAtToClients < ActiveRecord::Migration
  def change
    add_column :clients, :deleted_at, :datetime
    add_index :clients, :deleted_at
  end
end

Usage

In your model:

class Client < ActiveRecord::Base
  acts_as_paranoid

  # ...
end

Hey presto, it's there! Calling destroy will now set the deleted_at column:

>> client.deleted_at
# => nil
>> client.destroy
# => client
>> client.deleted_at
# => [current timestamp]

If you really want it gone gone, call really_destroy!:

>> client.deleted_at
# => nil
>> client.really_destroy!
# => client

If you want a method to be called on destroy, simply provide a before_destroy callback:

class Client < ActiveRecord::Base
  acts_as_paranoid

  before_destroy :some_method

  def some_method
    # do stuff
  end

  # ...
end

If you want to use a column other than deleted_at, you can pass it as an option:

class Client < ActiveRecord::Base
  acts_as_paranoid column: :destroyed_at

  ...
end

If you want to access soft-deleted associations, override the getter method:

def product
  Product.unscoped { super }
end

If you want to find all records, even those which are deleted:

Client.with_deleted

If you want to find only the deleted records:

Client.only_deleted

If you want to check if a record is soft-deleted:

client.paranoia_destroyed?
# or
client.deleted?

If you want to restore a record:

Client.restore(id)

If you want to restore a whole bunch of records:

Client.restore([id1, id2, ..., idN])

If you want to restore a record and their dependently destroyed associated records:

Client.restore(id, :recursive => true)

If you want callbacks to trigger before a restore:

before_restore :callback_name_goes_here

For more information, please look at the tests.

Acts As Paranoid Migration

You can replace the older acts_as_paranoid methods as follows:

Old Syntax New Syntax
find_with_deleted(:all) Client.with_deleted
find_with_deleted(:first) Client.with_deleted.first
find_with_deleted(id) Client.with_deleted.find(id)

The recover method in acts_as_paranoid runs update callbacks. Paranoia's restore method does not do this.

Support for Unique Keys with Null Values

Most databases ignore null columns when it comes to resolving unique index constraints. This means unique constraints that involve nullable columns may be problematic. Instead of using NULL to represent a not-deleted row, you can pick a value that you want paranoia to mean not deleted. Note that you can/should now apply a NOT NULL constraint to your deleted_at column.

Per model:

# pick some value
acts_as_paranoid sentinel_value: DateTime.new(0)

or globally in a rails initializer, e.g. config/initializer/paranoia.rb

Paranoia.default_sentinel_value = DateTime.new(0)

License

This gem is released under the MIT license.