Rails for beginners : How to make a 'soft delete' ?
Deleting something from your database is very common and handled very easily by rails as part of a CRUD action. In some cases, it is useful to create a “soft delete”, meaning that the deleted element will still exist in the database but won’t appear to the user. This method is useful if you or a user accidentally delete something and would like to retrieve it.
In this article, we’re going to work through an easy way to create an ‘Archive’ method and hide it from a user when they use it.
Let’s start with a fairly easy object.
First thing let’s create our book model. Let’s say a book has an author and a number of pages
rails g model Book author:string pages:integer
You should have something like this: book.rb
class Book < ApplicationRecord
end
Now we are going to add to our routes.rb
resources :books
Now that we have our model and the different routes associated with it, we are going to generate our controller
rails g controller Books
Now we are going to fill up that controller with our simple CRUD actions.
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@book = Book.find(params[:id])
end
def new
@book = book.new
end
def create
@book = Book.new(book_params)
@book.save
redirect_to book_path(@book)
end
def edit
@book = Book.find(params[:id])
end
def update
@book = Book.find(params[:id])
@book.update(book_params)
redirect_to book_path(@book)
end
def destroy
@book = Book.find(params[:id])
@book.destroy
redirect_to books_path :id
end
private
def book_params
params.require(:book).permit(:author, :pages)
end
end
At this point you should be able to operate your CRUD actions; create, view, edit, update and destroy any books from your database. Now we’re getting into the interesting part. The destroy method is irreversible, so you might want to find a way to create a “soft delete” meaning that your book will still exist in the database but will be hidden from the user.
The first step is to create an additional ‘status’ column in the db for books. In order to do this let’s run a migration.
rails g migration AddStatusToBooks status:string
But wait up, before you run rails db:migrate we’re going to add some important things to our migration file
class AddStatusToBooks < ActiveRecord::Migration[6.0]
def change
add_column :books, :status, :string, default: 'active', null: false
end
end
Adding this will make the book ‘active’ by default when creating a new record in your db.
We can now run rails db:migrate
Once the migration is complete, return to your Book.rb model and add some scopes
class Book < ApplicationRecord
def self.active
where(status:'active')
end
def self.archived
where(status: 'archived')
end
def active?
status == "active"
end
def archived?
status == "archived"
end
end
These scopes can be used in your views to access the active (or the archived books) So if you wanted to show all the books in your index that are active, you would do something in those lines
Index.html.erb
<% @books.archived.each do |book| %>
<%= book.author %>
<%= book.pages %>
<% end %>
By using the self.active scope that we have defined in our model, we are able to only select the books that have the ‘active’ status. None of our books have that status because we have not given the ability to archive for a user.
Let’s go back to our controller and add an archive method
def archive
@book = Book.find(params[:id])
@book.update(status: 'archived')
redirect_to :books, notice: 'Successfully Archived'
end
In order to get this to work, we need modify routes.rb and add a route to the action.
resources :books do
put :archive, on: :member
end
If your run rails routes | grep book in your terminal you should see a new route in addition to our previous CRUD ones.
Let’s go back to out index.html.erb
<% @books.active.each do |book| %>
<%= book.author %>
<%= book.pages %>
<%= link_to 'Delete', [:archive, book], method: :put %>
<% end %>
And there you go ! If a user clicks on Delete, the book will be archived and it will still be accessible in the database ! If you have an admin side for example you can use the book.archived to see all the books.
BONUS : If you are having an admin side and you want to restore the book you can !
Go to your controller and add a restore method
def restore
@book.update(status: 'active')
redirect_to :books, notice: 'Successfully restored'
end
Go to your routes.rb and add the restore path
resources :books do
put :archive, on: :member
put :restore, on: :member
end
In your index.html.erb
<% @books.archived.each do |book| %>
<%= book.author %>
<%= book.pages %>
<%= link_to 'restore', [:restore, book], method: :put %>
<% end %>
You can also use
<% @books.each do |book| %>
<%= book.author %>
<%= book.pages %>
<% if book.archived? %>
<%= link_to 'Restore', [:restore, book], method: :put %>
<% end %>
<% if book.active? %>
<%= link_to 'Delete', [:archive, book], method: :put %>
<% end %>
<% end %>
Here you go ! hope you found this little tutorial useful !
Have a project? Get in touch and we can talk it through.