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.
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 => "rails3"
# or
gem "paranoia", :github => "radar/paranoia", :branch => "rails4"
Then run:
bundle install
Updating is as simple as bundle update paranoia
.
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
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 include associated soft-deleted objects, you can (un)scope the association:
class Person < ActiveRecord::Base
belongs_to :group, -> { with_deleted }
end
Person.includes(:group).all
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)
# or
client.restore
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)
# or
client.restore(: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.
Beware that you should adapt all your indexes for them to work as fast as previously. For example,
add_index :clients, :group_id
add_index :clients, [:group_id, :other_id]
should be replaced with
add_index :clients, :group_id, where: "deleted_at IS NULL"
add_index :clients, [:group_id, :other_id], where: "deleted_at IS NULL"
Of course, this is not necessary for the indexes you always use in association with with_deleted
or only_deleted
.
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.
This gem is released under the MIT license.