Use ActiveAdmin like a boss
Use ActiveAdmin like a boss
ActiveAdmin is widely used administration framework in Rails applications. This post explains how to hook more sophiscicated domain logic into it.
An ActiveAdmin resource can be generated to perform CRUD operations on the application's models. However, there are scenarios where:
- business logic is no longer a part of the model and it lives somewhere else, eg. service, aggregate
- it is not desired to remove the data, but instead set a status, eg. "Cancelled"
- admin need super powers which are not living in the ActiveRecord model (like in the first item)
Taking this into account, classic CRUD approach is simply not enough. Below, there's a short example extracted from sample Order management app
ActiveAdmin.register Order do
controller { actions :show, :index, :cancel }
member_action :cancel, method: %i[post] do
command_bus.(
Ordering::CancelOrder.new(order_id: Order.find(params[:id]).uid),
)
redirect_to admin_orders_path, notice: 'Order cancelled!'
end
action_item :cancel,
only: %i[show],
if: proc { 'Submitted' == resource.state } do
link_to 'Cancel order',
cancel_admin_order_path,
method: :post,
class: 'button',
data: {
confirm: 'Do you really want to hurt me?',
}
end
end
What's happening here:
controller { actions :show, :index, :cancel }
— only certain actions are allowed, no destroy, create nor update- custom
cancel
action is defined, which invokesOrdering::CancelOrder.new
command. In effect, we call acancel
method onOrder
aggregate and read model is being updated accordingly - to display a button to perform action
action_item
block is used; the button is rendered only when operation is available to perform — not all the Orders are cancellable. It's also a good pattern to not present to user operations which can't be performed
One can say — but it's just a status column update. Nope, that tiny column update is a result of business operation taking specific constraints into account. Moreover, data remain consistent because there's no longer a possibility to "just" update the column or many of them.
That updated column is a representation in read model which is not meant to deliver domain behaviour, only data for display
— Implementing Domain-Driven Design, Vaughn Vernon.
I find this useful, especially if one wants to avoid multiple checks and conditions (eg. IF statements) in code to determine whether certain actor (admin in our scenario) is capable of doing certain operation.