Solid State – Stateful Ruby objects with a twist

Most stateful libraries that exist for Ruby deal with keeping track of a state variable, which is a symbol or string stating what state said object is currently in. This works well with libraries like ActiveRecord where you’re usually simply interested in data. But what if you want to change the functionality according to what state the object is in? With tools like ActsAsStateMachine, you’ll still need to pepper your methods with checks on which state the object is in:


  if state == :this
  elsif state == :that
  else
    ...
  end

Enter solid_state. The Ruby state machine library that lets you define state-specific functionality. But enough yammering, nothing can describe a system like a simple example.

Please note: this library is not a full state machine. See the Notes section below.

Example

Lets say you have a simple AI that needs to act differently according to what state it’s currently in. We’ll define the states :persue, :scared, and :idle.


  class AI
    include SolidState

    state :persue do
      def update
        # Chase the target!
      end
    end

    state :scared do
      def update
        # Run away from the target!
      end
    end

    state :idle do
      def update
        # Look for a new target
      end
    end

    starting_state :idle
  end

This one example shows almost the entirity of solid_state’s simple API. Include the SolidState module, define your states, and optionally set a starting state. To using this class is simple:


  ai = AI.new
  ai.current_state          # => :idle
  ai.update                 # => looking for a target...

  # Target found!
  ai.change_state! :persue
  ai.update                 # => Rawr! Chasing target

  # I'm hurt!
  ai.change_state! :scared
  ai.update                 # => Run away and find help!

  ai.change_state! :dead    # => InvalidStateError ... aw

This also works seemlessly with subclasses. For example:


  class Scavenger < AI

    state :scavange do
      def update
        # Scavange!
      end
    end

  end

  ai = Scavanger.new
  ai.current_state            # => :idle
  ai.change_state! :scavange
  ai.update                   # => Scavaging!

  a.change_state! :idle
  a.update

Notes

To be fair, other state machine libraries do offer this functionality. I’m do this to be a learning experience and to make as bare-bones a state machine system as I can, for when you don’t need a full state machine (transitions, validation, transition direction enforcement, etc).

If you’re looking for a fully comprehensive state machine library, state_machine is the most detailed I’ve found yet and probably can do anything you need to do.

Possible Issues

Individual states are implemented underneath as inner Classes that subclass the current Class. This means they get access to all public and protected methods in the outer Class, but at the same time if there are state methods with the same name as methods on the outer class, the state methods will never get called.

Project Info

Install via gems: gem install solid_state —source http://gemcutter.org

Code hosted on Github: http://github.com/jameskilton/solid_state

Issues on Github: http://github.com/jameskilton/solid_state/issues