I want to have a model where I need to soft delete a record and not show them in the find or any other conditions while searching.
I want to retain the model without deleting the record. How to go about this?
I want to have a model where I need to soft delete a record and not show them in the find or any other conditions while searching.
I want to retain the model without deleting the record. How to go about this?
Just use a concern in rails 4
Example here
module SoftDeletable
extend ActiveSupport::Concern
included do
default_scope { where(is_deleted: false) }
scope :only_deleted, -> { unscope(where: :is_deleted).where(is_deleted: true) }
end
def delete
update_column :is_deleted, true if has_attribute? :is_deleted
end
def destroy;
callbacks_result = transaction do
run_callbacks(:destroy) do
delete
end
end
callbacks_result ? self : false
end
def self.included(klazz)
klazz.extend Callbacks
end
module Callbacks
def self.extended(klazz)
klazz.define_callbacks :restore
klazz.define_singleton_method("before_restore") do |*args, &block|
set_callback(:restore, :before, *args, &block)
end
klazz.define_singleton_method("around_restore") do |*args, &block|
set_callback(:restore, :around, *args, &block)
end
klazz.define_singleton_method("after_restore") do |*args, &block|
set_callback(:restore, :after, *args, &block)
end
end
end
def restore!(opts = {})
self.class.transaction do
run_callbacks(:restore) do
update_column :is_deleted, false
restore_associated_records if opts[:recursive]
end
end
self
end
alias :restore :restore!
def restore_associated_records
destroyed_associations = self.class.reflect_on_all_associations.select do |association|
association.options[:dependent] == :destroy
end
destroyed_associations.each do |association|
association_data = send(association.name)
unless association_data.nil?
if association_data.is_deleted?
if association.collection?
association_data.only_deleted.each { |record| record.restore(recursive: true) }
else
association_data.restore(recursive: true)
end
end
end
if association_data.nil? && association.macro.to_s == 'has_one'
association_class_name = association.options[:class_name].present? ? association.options[:class_name] : association.name.to_s.camelize
association_foreign_key = association.options[:foreign_key].present? ? association.options[:foreign_key] : "#{self.class.name.to_s.underscore}_id"
Object.const_get(association_class_name).only_deleted.where(association_foreign_key, self.id).first.try(:restore, recursive: true)
end
end
clear_association_cache if destroyed_associations.present?
end
end
A rails concern to add soft deletes.
Very simple and flexible way to customise/ change
(You can change the delete column to be a timestamp and change the methods to call ActiveRecord touch ).
Best where you want to control code not have gems for simple tasks.
In your Tables add a boolean column is_deletable
class AddDeletedAtToUsers < ActiveRecord::Migration
def change
add_column :users, :is_deleted, :boolean
end
end
class User < ActiveRecord::Base
has_many :user_details, dependent: :destroy
include SoftDeletable
end
User.only_deleted
User.first.destroy
User.first.restore
User.first.restore(recursive: true)
Note: Focus Using update_column or touch if you decide to use a timestamp column.
If you are using rails <= 3.x (this example also use a DateTime field instead boolean), there are some differences:
module SoftDeletable
extend ActiveSupport::Concern
included do
default_scope { where(deleted_at: nil }
# In Rails <= 3.x to use only_deleted, do something like 'data = Model.unscoped.only_deleted'
scope :only_deleted, -> { unscoped.where(table_name+'.deleted_at IS NOT NULL') }
end
def delete
update_column :deleted_at, DateTime.now if has_attribute? :deleted_at
end
# ... ... ...
# ... OTHERS IMPLEMENTATIONS ...
# ... ... ...
def restore!(opts = {})
self.class.transaction do
run_callbacks(:restore) do
# Remove default_scope. "UPDATE ... WHERE (deleted_at IS NULL)"
self.class.send(:unscoped) do
update_column :deleted_at, nil
restore_associated_records if opts[:recursive]
end
end
end
self
end
alias :restore :restore!
def restore_associated_records
destroyed_associations = self.class.reflect_on_all_associations.select do |association|
association.options[:dependent] == :destroy
end
destroyed_associations.each do |association|
association_data = send(association.name)
unless association_data.nil?
if association_data.deleted_at?
if association.collection?
association_data.only_deleted.each { |record| record.restore(recursive: true) }
else
association_data.restore(recursive: true)
end
end
end
if association_data.nil? && association.macro.to_s == 'has_one'
association_class_name = association.options[:class_name].present? ? association.options[:class_name] : association.name.to_s.camelize
association_foreign_key = association.options[:foreign_key].present? ? association.options[:foreign_key] : "#{self.class.name.to_s.underscore}_id"
Object.const_get(association_class_name).only_deleted.where(association_foreign_key, self.id).first.try(:restore, recursive: true)
end
end
clear_association_cache if destroyed_associations.present?
end
end
In your Tables add a DateTime column deleted_at
class AddDeletedAtToUsers < ActiveRecord::Migration
def change
add_column :users, :deleted_at, :datetime
end
end
Try this gem : https://github.com/technoweenie/acts_as_paranoid - ActiveRecord plugin allowing you to hide and restore records without actually deleting them
Just add a boolean field called deleted
or something to that effect. When you soft delete the record just set that field to true.
When doing a find just add that as a condition (or make a scope for it).
The default_scope
functionality in ActiveRecord 3 makes this easy, but personally, I favor the wide variety of standard solutions that can be dropped into the project. acts_as_archive
in particular is the best fit for most of my projects, since it moves infrequently-accessed deleted records to a separate table, allowing the base table to stay small and in the database server's RAM.
Depending on your needs, you may also want to consider versioning instead of soft deletion.
You can define a module like this
module ActiveRecordScope
def self.included(base)
base.scope :not_deleted, -> { base.where(deleted: false) }
base.send(:default_scope) { base.not_deleted }
base.scope :only_deleted, -> { base.unscope(where: :deleted).where(deleted: true) }
def delete
update deleted: true
end
def recover
update deleted: false
end
end
end
Then in your class, you can write something like:
class User < ActiveRecord::Base
include ActiveRecordScope
end
So you have both soft delete and recover.
You call user.delete
to soft delete an user. Then you can call user.recover
to set the deleted back to false again, and recover it.
Have a look at rails3_acts_as_paranoid.
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.
Usage:
class Paranoiac < ActiveRecord::Base
acts_as_paranoid
scope :pretty, where(:pretty => true)
end
Paranoiac.create(:pretty => true)
Paranoiac.pretty.count #=> 1
Paranoiac.only_deleted.count #=> 0
Paranoiac.pretty.only_deleted.count #=> 0
Paranoiac.first.destroy
Paranoiac.pretty.count #=> 0
Paranoiac.only_deleted.count #=> 1
Paranoiac.pretty.only_deleted.count #=> 1
If you use Rails4, Try this gem : https://github.com/alfa-jpn/kakurenbo
An association function of Kakurenbo is better than other gems.
For Rails 4 don't use acts_as_paranoid (buggy for Rails 4), use paranoia. All you have to do is add a deleted_at timestamp column and include acts_as_paranoia in the model.
From there, just call destroy
on the object and all other ActiveRecord relations and most other methods(like :count) automatically exclude the soft_deleted records.
i wont use a default scope cause if i want to get all the records, i need to ignore it using "with_exclusive_scope" which in turn is messy. the would go by adding a 'deleted' boolean field which is set when the record is deleted. Also, would have added scopes to get the data as per the condition.
checkout Overriding a Rails default_scope and Rails: Why is with_exclusive_scope protected? Any good practice on how to use it?
Add one column say status in your table and on deletion of records update the value of column status to inactive. and while fetching the records, add condition status != "inactive" in the query.