Soft Deletion, Nested Resource and Active Admin

TL;DR

models/order_payments.rb

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

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.

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.

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:

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

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.

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:

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

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:

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

After that, everything works as expected.

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. 🙃

39