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.
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 :pursue, :scared, and :idle.
class AI include SolidState state :pursue 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! :pursue 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
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).
A similar library to this is state-ology which uses the Mixology gem to pull Modules in and out of the system when you change states. Functionality wise it’s pretty much equal to solid_state, but I wanted to build it in pure Ruby.
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.
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.
Install via gems: gem install solid_state
Code hosted on Github: http://github.com/jasonroelofs/solid_state
Issues on Github: http://github.com/jasonroelofs/solid_state/issues