Skip to content

Repository files navigation

Build StatusCode Climateendorse

state_pattern

A Ruby state pattern implementation.

This library intentionally follows the classic state pattern implementation (no mixins, classical delegation to simple state classes, etc.) believing that it increases flexibility (internal DSL constraints vs plain object oriented Ruby power), simplicity and clarity.

The gem is ready for Rails active record integration (see below and the examples folder).

Usage and functionality summary

  • Define the set of states you want your stateful object to have by creating a class for each state and inheriting from StatePattern:State.
  • All public methods defined in this state classes, except enter and exit (see below), are then available to the stateful object and their behaviour will depend on the current state .
  • If this automatic delegation to the current state public methods is not enough for your stateful object then you can just reopen the method and use super whenever you want to call the state implementation.
  • Inside each state instance you can access the stateful object through the +stateful+ method.
  • Inside each state instance you can access the previous state through the +previous_state+ method.
  • Define enter or exit methods to hook any behaviour you want to execute whenever the stateful object enters or exits the state.
  • An event is just a method that calls transition_to at some point.
  • If you want guards for some event just use plain old ifs before your transition_to.
  • In the stateful object you must set_initial_state.

Examples

So here's a simple example that mimics a traffic semaphore

require'state_pattern'classStop < StatePattern::Statedefnextsleep3transition_to(Go)enddefcolor"Red"endendclassGo < StatePattern::Statedefnextsleep2transition_to(Caution)enddefcolor"Green"endendclassCaution < StatePattern::Statedefnextsleep1transition_to(Stop)enddefcolor"Amber"endendclassTrafficSemaphoreincludeStatePatternset_initial_stateStopendsemaphore=TrafficSemaphore.newloopdoputssemaphore.colorsemaphore.nextend

Let's now use one nice example from the AASM documentation and translate it to state_pattern.

require'state_pattern'classDating < StatePattern::Statedefget_intimatetransition_to(Intimate)ifstateful.drunk?enddefget_marriedtransition_to(Married)ifstateful.willing_to_give_up_manhood?enddefenterstateful.make_happyenddefexitstateful.make_depressedendendclassIntimate < StatePattern::Statedefget_marriedtransition_to(Married)ifstateful.willing_to_give_up_manhood?enddefenterstateful.make_very_happyenddefexitstateful.never_speak_againendendclassMarried < StatePattern::Statedefenterstateful.give_up_intimacyenddefexitstateful.buy_exotic_car_and_wear_a_comboverendendclassRelationshipincludeStatePatternset_initial_stateDatingdefdrunk?;@drunk;enddefwilling_to_give_up_manhood?;@give_up_manhood;enddefmake_happy;enddefmake_depressed;enddefmake_very_happy;enddefnever_speak_again;enddefgive_up_intimacy;enddefbuy_exotic_car_and_wear_a_combover;endend

Enter and exit hooks

Inside your state classes, any code that you put inside the enter method will be executed when the state is instantiated. You can also use the exit hook which is triggered when a successful transition to another state takes place.

Overriding automatic delegation

If the automatic delegation to the current state public methods is not enough for your stateful object then you can just reopen the method and use super whenever you want to call the state implementation.

classTrafficSemaphoreincludeStatePatternset_initial_stateStopdefcolor# some great code here# now we call the current state implementationsuper# more cool hacking hereendend

Rails

To use the state pattern in your Rails models you need to:

  • Add a state column for your model table of type string
  • Include StatePattern::ActiveRecord in your model file
  • Use the state pattern as you would do in a plain Ruby class as shown above

Please see the examples folder for a Rails 3 example.

Example

Remember to put each class in its correct file following Rails naming conventions.

moduleBlogStates#we can put common state behaviour into a base state class or we could have implemented it inside the model with methods that call super, your choiceclassStateBase < StatePattern::Statedefsubmit!enddefpublish!enddefreject!transition_to(Rejected)stateful.save!enddefverify!endendclassPublished < StateBaseendclassPending < StateBasedefpublish!transition_to(Published)ifstateful.valid?stateful.save!endendclassUnverified < StateBasedefsubmit!ifstateful.submitter.manager?ifstateful.profile_complete?transition_to(Published)elsetransition_to(Pending)endstateful.save!endenddefverify!transition_to(Pending)stateful.save!endendclassRejected < StateBasedefpublish!transition_to(Published)ifstateful.valid?stateful.save!enddefenterNotifier.notify_blog_owner(stateful)endendendclassBlog < ActiveRecord::BaseincludeStatePattern::ActiveRecordset_initial_stateUnverified...end

The state attribute

By default StatePattern::ActiveRecord expects a column named state in the model. If you prefer to use another attribute do:

set_state_attribute:state_column

How do I decide? state_pattern or AASM?

  • Lot of state dependent behavior? Lot of conditional logic depending on the state? => state_pattern
  • Not much state dependent behavior? => AASM

Thanks

Installation

gem install state_pattern

To run the tests bundle and then bundle exec rake

Collaborate

http://github.com/dcadenas/state_pattern

Copyright

Copyright (c) 2009 Daniel Cadenas. See LICENSE for details.

About

A Ruby state pattern implementation

Resources

Stars

30 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages