In this post we will examine metaprogramming ruby to integrate both ActiveAdmin State Machine libraries elegantly, by providing a DSL (Domain Specific Language) to our ActiveAdmin resource configuration.
Why use metaprogramming?
As a developer, I love to simplify complex problems and produce modular, reusable code. Providing a simple integration between ActiveAdmin and StateMachine is a natural extension to my workflow. (Read more about Metaprogramming ruby from Lotus).
Why would we want to create a gem adding even more DSL to ActiveAdmin? Let's consider the scenario of managing some content, say a blog
Post can be in several states, going from draft, to published, and to archived.
This state machine is exactly why we would want to use the state_machine gem itself, which defines the logic and workflow that the
Post goes through in our domain. However, we end up writing a lot of controller code to implement the functionality to move the
Post through the workflow.
- We need to ensure the user is authorized to move the
Postto the next step in the workflow.
- We write a lot of conditional UI to determine what actions are indeed available to move the
- The controller actions turn out to be pretty cookie-cutter after a few, and there is our hint to refactor!
Example State Machine
Here is our example
Post class, with workflow outlined in the State Machine.
In the simplest example of creating a controller action, we have exposed a new method
state_action that appears to be just another ActiveAdmin DSL method at our disposal.
We see that we can pass blocks for further define the controller action as we see fit, but most of our configuration is handled with a few options and sane defaults of I18n translations.
The reason we can get by with this is because state_machine exposes the following methods on our
p = Post.new p.can_publish? p.publish!
With those dynamic methods defined, our controller action that `state_action` creates for us starts to come into focus:
The full finished implementation that creates the controller action follows below. There are a few fun things to note:
- We make use of ActiveAdmin's
member_actionto specify a code block for the controller action body, but also provide a HTTP verb which translates into the routing system.
- We allow the user to pass lambdas for several options so that they can determine a I18n string or message in a view context, but the defaults are very sensible and helpful.
- ActiveAdmin is built itself on top of InheritedResources, which provides a lot of the subsystem that you might not be familiar with, such as
- We're additionally defining an action item for the state, such as "Publish". In ActiveAdmin, an action item is a button that is shown when viewing the resource.
I wanted to introduce this gem as a super convenient way to integrate state_machine into your ActiveAdmin project. The UI for moving the resource through the state machine is completely generated from the state machine itself, and everything respects your CanCan or Pundit definition (or custom ActiveAdmin authorization adapter).
Be sure to take a look at the project README more in depth for full example usage, including the fact that this gem is helpful even without state_machine!