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
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
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
<% @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.