A Look into Discard
Tags: ruby, rails, programming
Published: 2019-09-02 12:41:05

---
title: A Look into Discard
published: true
description: A look into Discard and how it differs from other gems for soft delete.
tags: [Ruby, Ruby on Rails, Programming]
---
_Originally published on [ali-ilman.com/blog](ali-ilman.com/blog)_

Sometimes, we want to soft delete specific records in our app.
What is a soft delete?
>A soft delete is a term for flagging a record in the DB as deleted. A use case for this is when you want it to be hidden from the public but visible to the admin for record purposes.

In the world of Rails, there are various ways you can do that. You can write your own soft delete feature or you can use a gem such as discard.

## A Look Into Discard
[Discard](https://github.com/jhawthorn/discard) is a gem created by John Hawthorn, describing the gem as _soft deletes for ActiveRecord done right_. The gem is basically a _simple ActiveRecord mixin to add conventions for flagging records as discarded_.

#### Setup
For this example, I'll create an app where there are authors, books and publishers tables.
An `Author` can have many `Books`, a `Publisher` can have many `Authors`, and a `Publisher` can have many `Books` through `Authors`.
```ruby
class Author < ApplicationRecord
  belongs_to :publisher, optional: true
  has_many :books, dependent: :nullify
end

class Book < ApplicationRecord
  belongs_to :author
end

class Publisher < ApplicationRecord
  has_many :authors, dependent: :nullify
  has_many :books, through: :authors
end
```
Add this into your gemfile and then run `bundle`.
```ruby
gem 'discard', '~> 1.0'
```
For the record that you want to be discardable, in our case, `Author`, include discard in the author model like so.
```ruby
class Author < ApplicationRecord
  include Discard::Model
end
```
Then, you generate a migration to add the relevant attributes to the _authors_ table.
```
rails generate migration add_discarded_at_to_authors discarded_at:datetime:index
```
#### How to discard a record
```ruby
author = Author.first
author.discard # => true
author.discarded_at # => Sun, 01 Sep 2019 10:48:31 UTC +00:00
author.discarded? # => true
```
#### How to undiscard a record
```ruby
author = Author.first
author.undiscard # => true
author.discarded_at # => nil
author.discarded? # => false
```
#### Accessing records
Discard adds the following 4 methods for the discardable record.
```ruby
Author.with_discarded # an alias for Author.all
Author.kept # returns a collection of undiscarded authors
Author.discarded # returns a collection of discarded authors
Author.undiscarded # an alias for Author.kept
```
#### Callbacks?!?!?!
Discard adds `before_`, `around_` and `after_` callbacks for you to utilise if needed.
```ruby
class Author < ApplicationRecord
  ...
  before_discard { puts 'Hallo before_discard!' }
  after_discard { puts 'after_discard!' }

  before_undiscard { puts 'Waving at you before_undiscard...' }
  after_undiscard { puts 'Yay after_undiscard!' }
  ...
end
> Author.first.discard
# other logs
Hallo before_discard!
# other logs
after_discard!
=> true

> Author.first.undiscard
# other logs
Waving at you before_undiscard...
# other logs
Yay after_undiscard!
=> true
```
Unfortunately though, the `around_` callbacks don't seem to work. An example would be, `Author.first.discard`. It'll query for the author and run the code in `around_discard` but it won't discard the author. It may be a bug within the gem. 🤔

#### Summary
Discard removes the magic that comes with alternatives such as [paranoia](https://github.com/rubysherpas/paranoia) and [acts_as_paranoid](https://github.com/ActsAsParanoid/acts_as_paranoid).

I've used paranoia on a few projects, and one of them's a project of a decent size. I remember initially making most models _paranoid_. We encountered issues, one of them being a `has_many dependent: :destroy` association. Those dependent records needed to be paranoid as well, otherwise the records will be destroyed for good. 😱 Another issue is that, although I can't exactly call it, one of the records has a has_many_ association and it was searching for a soft-deleted record using an `id` that still exist within the collection, but the said record with the existing `id` couldn't be found! 🤯

One might argue that discard goes against _convention over configuration_, which is one of the principles of Rails, and can add verbosity to the codebase. An example would be, you'd have to keep on adding `.kept` to a model or an association to access a collection of undiscarded records. But personally, it's easy to setup and use. Plus unlike the other two gems, discard doesn't change the model's default_scope. I suggest you to go with discard to avoid possible long-term pain. 😉

You can check out the code for the app above [here](https://github.com/thealiilman/loeschen) where you can select different branches to view the implementation with [discard](https://github.com/thealiilman/loeschen/tree/feature/soft-delete-with-discard) or [paranoia](https://github.com/thealiilman/loeschen/tree/feature/soft-delete-with-paranoia).