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?
If you only need a soft delete (no callbacks and no associations to delete), you can just leave out the define_callbacks line. After this, I created a new method for deleting a record. This will just update the deleted_at of a record with the current time. If you only need soft delete, you're pretty much done.
A common way to implement soft delete is to add a field that will indicate whether data has been deleted or not. This SQL command will permanently remove the product with id=1 from the table in the database.
Hard vs soft deletesA “hard” delete is when rows are deleted using DELETE FROM table WHERE ... A “soft” delete is when rows are deleted using UPDATE table SET deleted_at = now() WHERE ...
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
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With