Soft Deletion, Nested Resource and Active Admin
Tags: rails, ruby, programming
Published: 2021-08-05 08:15:50

## TL;DR

*models/order_payments.rb*

```ruby
def deletable_from_admin
  !order.validated? && deleted_at.blank?
end

def destroy
  order.context == :update_from_admin_space ? update!(deleted_at: Time.current) : super
end
```

*admin/order.rb*

```ruby
controller do
  def update
    resource.context = :update_from_admin_space
    super
  end
end

f.inputs do
  f.has_many :order_payments, new_record: !order.payment_accepted?, allow_destroy: :deletable_from_admin do |order_payment|
    if order_payment.object.deleted_at.blank?
      order_payment.input :expected_at,
      ...
```

## Details

In my company, we are currently implementing a soft deletion strategy on some tables. The task seems easy: add a new field  `deleted_at` and fill it with an `update` whenever `destroy` has normally to be called.

However, when you are using a gem doing a lot of magic (hello Active Admin) and want to do something specific, things get more tricky.

<img src="https://media.giphy.com/media/WPRJfpqxa9RF6/giphy.gif">

So first I had to show the data. It was quite easy, just had to call my custom `has_many` instead of the real association. 

Then, came the edit page, the part containing forms...

After using Rails for years, there is still one thing I always have "difficulties" to play with: the nested resources/forms. Each time I have to play with it, I have to check the documentation again and again, like if it was the first time I'm doing it.

In that case of soft deletion, the main concern I had was to find a way to modify the magic of Rails about nested resource deletion. The easier thing seemed to monkey patch the destroy method in my model.

<img src="https://media.giphy.com/media/3o85xAYQLOhSrmINHO/giphy.gif">

```ruby
def destroy
  update(deleted_at: Time.current)
end
```

It worked, but I know deep inside that someday I would regret that change. I really needed this `destroy` new behaviour in the context of a deletion from Active Admin only. So I continued to check the code of the app to finally found an interesting line in *models/order_payments.rb*:

```ruby
controller do
  def update
    resource.context = :update_from_admin_space
    ...
```

<img src="https://media.giphy.com/media/3o6Mbj2w67HnPQKgcE/giphy.gif">

I just had to add a condition to my `destroy` method to use soft deletion only in the case of an update from the admin space. I'm still not fully satisfied, but it seems better than my initial solution.

```ruby
def destroy
  order.context == :update_from_admin_space ? update(deleted_at: Time.current) : super
end
```

So from there, my record was soft deleted as planned, and I was able to only show the data that were not softly deleted. Nevertheless, I had another trouble. When I tested the edit page, the soft deleted resources were still showing. So I added a condition to no show the soft deleted:

```ruby
f.inputs do
  f.has_many :order_payments, new_record: !order.payment_accepted?, allow_destroy: !order.validated? do |order_payment|
    if order_payment.object.deleted_at.blank?
      order_payment.input :expected_at,
      ...
```

But was actually not enough. Yes, my record wasn't showing, but that `allow_destroy` magically made the checkbox appear for each soft deleted record...

<img src="https://media.giphy.com/media/ywH9QD9OhPAUnZ13kJ/giphy.gif">

After some time, I checked the Active Admin documentation and a line sounded promising:
> It is possible to associate :allow_destroy with a string or a symbol, corresponding to the name of a child object’s method that will get called, or with a Proc object.

It was clear, I just had to move that `allow_destroy` condition somewhere and change the rule to return `false` whenever I'm dealing with a soft deleted record:

```ruby
def deletable_from_admin
  !order.validated? && deleted_at.blank?
end
```

After that, everything works as expected.

<img src="https://media.giphy.com/media/NEvPzZ8bd1V4Y/giphy.gif">

I know my solution is probably not the best, but it works. Did you also have this kind of problem? How did you solve it? I'm curious. 🙃