It's my first time using AASM and I'm wondering how I might be able to achieve a substate implementation. Pure example as I'm learning through this. Let's say we're tracking the state of a patient as they move through a hospital. Here's an imaginary list of states & substates:
state :entered, initial: true
substate :in_lounge
substate :in_smoking_area
substate :in_restroom
state :with_doctor
substate :checked_vitals
substate :received_consultation
state :checkout
substate :payment_pending
substate :payment_success
substate :payment_error
state :complete
2 rules:
- major
states
generally move forward linearly, but backtracking is occasionally possible - within a given
state
, only those relatedsubstates
are available, though they are not linear, and can move back and forth
For example, once you've entered a hospital you can wait anywhere, the substates are like locations. However, once you're with_doctor
you can no longer be in the lounge, smoking area, or restroom, unless for some reason the doctor sends you back to entered
.
My current imagined, untested solution (some of the syntax may not be perfect, but the idea carries) is to use 2 state machines:
- Primary keeps track of the main
state
and isn't updated directly, it functions purely as a guard - Secondary is directly updated, callbacks on secondary state are used to update primary state
For example:
aasm(:primary, column:"primary_state") do
state :primary_entered, initial: true
state :primary_with_doctor
state :primary_checkout
state :primary_complete
event :to_primary_with_doctor do
transitions from: :primary_entered, to: :primary_with_doctor
end
end
aasm(:secondary, column:"secondary_state") do
state :entered, initial: true
state :in_lounge
state :in_smoking_area
state :in_restroom
state :with_doctor
... # this example only works with substates for first state
[:in_smoking_area, :in_lounge, :in_restroom].each do |this_state|
event :"to_#{this_state}" do
transitions to: this_state, guard: :before_primary_with_doctor
end
# by not specifying a from, this captures notion that any movements in any direction between this set of states is allowable, as long as the guard is met
end
event :to_with_doctor do
transitions to: :with_doctor, guard: :before_primary_with_doctor, success: :set_primary_state("with_doctor")
# secondary state machine has a redundant state, that updates the primary state, allowing primary state to only be used as guards and not to be directly manipulated by the system
end
end
def before_primary_with_doctor
return self.aasm(:primary).current == :entered
# eventually I'm thinking about making primary state enums so I can mathematically do something like...
# self.aasm(:primary).current <= :with_doctor
# obviously syntax not right, but that's how I'm thinking of using this
end
def set_primary_state(state)
self.aasm(:primary).fire(:"to_primary_#{state}")
end
First time using this gem though, so wondering if this is super hacky or if there are other more conventional ways of doing this.