When handling state changes or object and logic condition changes, one of the most useful gems is aasm. However, after many years I find that it’s often under-utilised and even misused.
It’s really easy to grab the aasm gem and begin implementing the ActiveRecord methodology. This implementation is effective if the state and its dependencies are only models and providing you are not creating circular dependencies.
To get your head in the game here’s a quick example we came across that required us to implement something new:
- We have a booking for a room when the user first begins the booking process we give the booking the state: draft.
- As the user progresses, the date is confirmed and the state is changed to pre-reserved.
- Once the payment has been received the state should be changed to booked.
- Finally, when the booking has been finalised, the status will be fulfilled.
Of course, we might have more transitions between those cases, but for this scenario, four will suffice.
If we go with the normal Active Record style, our code might look something like this:
Even when the previous approach is quick and simpler. There are a few cons:
- Booking model is prone to becoming a ‘god object’.
- Single responsibility principle is being contrevend. After changing states we are adding business logic. Booking is responsible for persistency, changing states (we can consider this as persistency if we don’t want to be strict), sending emails, and releasing payments.
- Testing will become harder, with more items to stub.
- Additionally, the dependency direction is wrong, entities now be aware of other services.
A simpler approach would be using the state machine as a service, like this:
To achieve this implementation we only need to be aware of 2 sections. Everything else is your own code:
And we need to make our changes persistent:
Considering this approach we will get two huge benefits:
- Booking model is only responsible for data persistency
- This StateService is our actual State machine and the dependency direction is now as it should be. The service no longer requires an understanding of other services and the booking model won’t have awareness of emails or payments.