How to Use State Machine

| Comments

A few weeks ago, I was introduced to an incredible gem called State Machine (you can read their docs here). The gem keeps track of the status of a specific object and respeonds to different inputs to alter that state. The example that I’ve understood the most is in purchasing something (I’m going to use a pack of gum in this post).

The gem breaks down into four different parts:

  1. State- the status of the object. All states are predefined in the class. All objects start in the initial state. The first step to buying a pack of gum is to select it. So the pack of gum would move from initial to selected.

  2. Transition- the movement from one state to another. A pack of gum would transition from the initial state to the selected, and then again transition to the queued state when the user is in line to purchase it.

  3. Event- The invocation of a state. A pack of gum would transition from the initial state to the selected, by way of an event. Another event would trigger the queued state.

  4. Action- State Machine can trigger different methods before, during, and after transitions.

So let’s further elaborate on this example of buying a pack of gum.

What states would there be?

  • Selected - I deciced I want zebra gum, so now I’m holding it
  • Queued - waiting in line to purchase
  • Purchased - handing money to cashier
  • Owned - I own and can do whatever I want with the gum
  • Trash- After I chew the gum, it’s garbage
  • Here is an example of a class set up with State Machine, and then I’ll go through and explain each step.

      class Gum
        attr_accessor :flavor, :price, :state
    
        state_machine :initial => :selected do
    
          before_transition :on => purchased :do => :demand_money
          after_transition :on => owned, :do => :be_chewed
    
    
          event :queue do
            transition :selected  => :queued_for_purchasing
          end
    
          event :purchase
            transition :queued_for_purchasing => :purchased
          end
    
          event :ownership
            transition :purchased => :owned
          end
    
          event :trash do
            transition :owned => :trash
          end
    
    
          state :queued do
            def is_queued?
              return true
            end
          end
    
          state :purchased do
            def is_purchased?
              return true
            end
          end
    
          state :owned do
            def is_owned?
              return true
            end
          end
    
          state :trash do
            validate { |gum| gum.trash? }
            def is_trash?
              return true
            end
          end
    
        end
    
        def demand_money
          #demand money here
        end
    
        def be_chewed
          # method to prepare piece of gum to be chewed
        end
    
    
      end
    

    So how does this work?

    State Machine first gets included in your Gemfile.

    From there, the set up is all done from inside the class where the objects change states. All objects start in the initial state, which is defined with when State Machine is invoked:

      state_machine :initial => :selected do
    

    Next, the states must be defined:

      state :queued do
        def is_queued?
          return true
        end
      end
    
      state :purchased do
        def is_purchased?
          return true
        end
      end
    
      state :owned do
        def is_owned?
          return true
        end
      end
    
      state :trash do
        validate { |gum| gum.trash? }
        def is_trash?
          return true
        end
      end
    

    Following, the events must be defined:

      event :queue do
        transition :selected  => :queued_for_purchasing
      end
    
      event :purchase
        transition :queued_for_purchasing => :purchased
      end
    
      event :ownership
        transition :purchased => :owned
      end
    
      event :trash do
        transition :owned => :trash
      end
    

    Here, the events specify transitions between states. These events can be triggered in the command line as well, which would trigger new states. The state is saved as an attribute of an instance of the class.

    Then, the actions to be called must be defined:

       def demand_money
          #demand money here
        end
    
      def be_chewed
        # method to prepare piece of gum to be chewed
      end
    

    Finally, the actions must know to be called during transitions:

      before_transition :on => purchased :do => :demand_money
      after_transition :on => owned, :do => :be_chewed
    

    So, to test the flow of all of this in command line, there is a handy method fire_events. That method looks like this:

      gum = Gum.first
      gum.state #=> selected
      gum.fire_events(:queue) #this transitions the gum from selected to queued_for_purchase
      gum.state #=> queued_for_purchase
    

    The only thing to know is that you cannot rollback states through triggering events. You can however explictitely redefine the state (gum.state = "selected") and from there retrigger the events.

    Enjoy!

    Comments