Soft Kill
Dealing with a record that has relation is sometimes tricky. For example, you deleted a post that have categories, when you try to access the category of that deleted post, it will have an error because it will find its parent, which is now gone. To solve this problem, we will use Soft Kill.
Table of contents
What is Soft Kill?
Soft kill is the means of overriding the destroy action and keeping the deleted record in the database, but is no longer visible to the user. This method is useful if you or another user accidentally delete something and want to recover it.
Creating Soft Kill
To make a soft delete method, first we need to add a deleted_at
attribute with type datetime
to posts.
root@0122:/usr/src/app# rails g migration add_deleted_at_to_posts deleted_at:datetime
invoke active_record
create db/migrate/xxxxxxxxxxxxxx_add_deleted_at_to_posts.rb
Add default value nil
before migrating the file.
# db/migrate/xxxxxxxxxxxxxx_add_deleted_at_to_posts.rb
class AddDeletedAtToPosts < ActiveRecord::Migration[7.0]
def change
- add_column :posts, :deleted_at, :datetime
+ add_column :posts, :deleted_at, :datetime, default: nil
end
end
root@0122:/usr/src/app# rails db:migrate
== xxxxxxxxxxxxxx AddDeletedAtToPosts: migrating ===========================
-- add_column(:posts, :deleted_at, :datetime, {:default=>nil})
-> 0.0071s
== xxxxxxxxxxxxxx AddDeletedAtToPosts: migrated (0.0073s) ==================
Override destroy
method in posts model.
# app/models/post.rb
class Post < ApplicationRecord
+ default_scope { where(deleted_at: nil) }
validates :title, presence: true
validates :content, presence: true
has_many :comments
has_many :post_category_ships
has_many :categories, through: :post_category_ships
+
+ def destroy
+ update(deleted_at: Time.now)
+ end
end
Read Carefully:
Instead of deleting the post record, we are overriding the destroy method to only update the
deleted_at
with current time of deletion. Thedeleted_at
attribute will serve as indicator if your comment is deleted or not.
Afterwards, we included a scope (default) for filtering the Post that have nil
deleted_at. default_scope
as its name implies is a method provided by ActiveRecord, which allows you to set a default scope for all operations done on a given model.
Let’s test our destroy method.
Reminder:
We will be using sandbox for this test. Sandbox is useful for testing out some code without changing any data. Type
rails console --sandbox
to enter the sandbox mode.
Loading development environment in sandbox (Rails 7.0.4)
Any modifications you make will be rolled back on exit
irb(main):001:0> post = Post.create(title: 'Testing Soft Kill', content: 'content for softkill')
TRANSACTION (0.2ms) SAVEPOINT active_record_1
Post Create (0.4ms) INSERT INTO `posts` (`title`, `content`, `created_at`, `updated_at`, `deleted_at`) VALUES ('Testing Soft Kill', 'content for softkill', 'xxxx-xx-xx xx:xx:xx.xxxxxx', 'xxxx-xx-xx xx:xx:xx.xxxxxx', NULL)
TRANSACTION (0.2ms) RELEASE SAVEPOINT active_record_1
irb(main):002:0> post.destroy
TRANSACTION (0.3ms) SAVEPOINT active_record_1
Post Update (1.0ms) UPDATE `posts` SET `posts`.`updated_at` = 'xxxx-xx-xx xx:xx:xx.xxxxxx', `posts`.`deleted_at` = 'xxxx-xx-xx xx:xx:xx.xxxxxx' WHERE `posts`.`id` = 51
TRANSACTION (0.2ms) RELEASE SAVEPOINT active_record_1
irb(main):003:0> post
=>
#<Post:0x00007fc8fcd38408
id: 51,
title: "Testing Soft Kill",
content: "content for softkill",
created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx,
updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx,
deleted_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>
Now we already integrated the soft kill in our application.