public
Description: [DORMANT] Make your classes stately.
Homepage:
Clone URL: git://github.com/jbarnette/stateful.git
name age message
file .autotest Thu Jul 03 13:31:55 -0700 2008 Autotestify, RSpecify, and unhoe. [jbarnette]
file .gitignore Wed Jul 09 20:13:53 -0700 2008 Ignore pkg. [jbarnette]
file README.rdoc Sun Jul 27 19:56:21 -0700 2008 Event listeners take a context object now. [jbarnette]
file Rakefile Fri Dec 26 13:15:47 -0800 2008 Stop being stupid about versions. [jbarnette]
directory lib/ Fri Dec 26 13:15:47 -0800 2008 Stop being stupid about versions. [jbarnette]
directory spec/ Sun Jul 27 19:56:21 -0700 2008 Event listeners take a context object now. [jbarnette]
file stateful.gemspec Fri Dec 26 13:15:47 -0800 2008 Stop being stupid about versions. [jbarnette]
README.rdoc

Stateful

  TODO for 1.0:
    - make machine.valid? :from => :to work
    - complete tracing plugin
    - ar/dm integration
    - complete timestamps plugin
    - turn these notes into real docs
    - flesh out Machine#to_dot
    - clean up, minimally document, and :nodoc: classes

Example

  class AClass

    # :start is optional, defaults to first state declared
    statefully(:start => :foo) do

      # can declare a bunch of states like this,
      states :foo, :bar, :baz, :bax

      # or one at a time
      state :angry
      state :angsty

      # note that you don't have to declare states at all if you don't want to:
      # they'll be auto-created the first time they're referenced.

      on :traffic do
        # an event must specify all the places it can be called. it can put
        # them in a 'move' line, like this,
        move :state_or_list => :dest

        # or it can specify that the event 'cycles', staying in the originating state
        stay :state_or_list
      end

      # events that are available on any state can be wildcarded with :ANY,
      # which is the only reserved state name. Everything else is fair game.

      on :ping do
        move :ANY => :pinged
      end
    end
  end

  # multiple 'statefully' blocks are totally okay.
  # you can even add them from outside the class!

  AClass.statefully do

    # you can listen for attempted state entry.

    entering :state_or_list do |ctx|
      # do some ruby stuff! Exceptions thrown in here will
      # keep the transition from occuring, i.e., the model's
      # current state will still be 'from'.

      # the context here gives you all sorts of stuff:

      ctx.model  # the target model,
      ctx.event  # the name of the event that was fired,
      ctx.to     # the destination state,
      ctx.from   # the source state, and
      ctx.extras # any additional keys you might've passed in!
    end

    # you can also listen for successful state entry. same context is available.

    entered :state_or_list do |ctx|
      # more ruby stuff! You can throw exceptions in here, of course,
      # but it won't change the model's current state.
    end

    # listening for attempted state exit:

    exiting :state_or_list do |ctx|
      # awesome ruby stuff! throwing exceptions in here vetoes
      # the transition.
    end
  end

  AClass.statefully do

    # it's possible to listen to events fire, too!

    firing :event_or_list do |ctx|
      # throwing exceptions here will veto the transition
    end

    # or after they've successfully fired

    fired :event_or_list do |ctx|
      # throwing here does squat
    end
  end

  # The order of listeners/events: any marked with an 'if' can
  # be vetoed by throwing an exception.

  # (an event is triggered, e.g., model.finish!)
  #   - if all matching 'event firing'
  #     - if all matching 'state exiting'
  #       - if all matching 'state entering'
  #         - STATE CHANGE PERSISTED
  #         - all matching 'state entered'
  #         - all matching 'event fired'

  # calls to 'statefully' always return the Stateful::StateMachine instance
  # for the class, so reflection is pretty easy.

  sm = AClass.statefully
  states = sm.states # instances of Stateful::State

  sm.valid? :foo => :bar # => false, or whatever

  # If the target model class is a descendant of ActiveRecord::Base,
  # Statefully will automatically mix in a DB-aware persister.
  # otherwise, it's just an attr_accessor called 'current_state'.

  # Want to see what's going on?
  # include Stateful::Tracing
  #
  # it'll use the Rails logging bits if they're around, etc, etc.

  # Need to audit your state moves?
  # include Stateful::Timestamps
  #
  # it'll set attributes for state_at and event_fired_at if respond_to?

  # On any of the state (entering, entered, exiting) or event (firing, fired)
  # listeners, not providing a state or event list means you'll hear about 'em all.

  AClass.statefully do
    entering { }
    entered { }
    exiting { }
    firing { }
    fired { }
  end

  # want to make a diagram? there's really really crude support for generating
  # a graphviz file built in:

  File.open("machine.dot", "wb") do |f|
    f.write(AClass.statefully.to_dot)
  end

Installing

  # FIXME: 1.0 isn't released yet
  [sudo] gem install stateful

Installing Development Versions

We periodically update Stateful’s gemspec on Github. Rather than installing the official releases (hosted on RubyForge), you can track the development version. Development versions will have a timestamped version number, like 1.0.0.200807060242.

  [sudo] gem sources -a http://gems.github.com   # if you haven't already
  [sudo] gem install jbarnette-stateful

License

Copyright 2008 John Barnette

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.