Rails AASM gem, how could you achieve substate?

258 views Asked by At

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 related substates 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.

0

There are 0 answers