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. 🙃
Top comments (0)