public
Description: A rich DSL for describing state, and state-related behaviour, in your ruby classes / models.
Homepage: nil
Clone URL: git://github.com/davidlee/state-fu.git
name age message
file .gitignore Tue Aug 04 17:36:40 -0700 2009 Version bump to 0.4.0 [davidlee]
file LICENSE Wed Mar 25 06:53:58 -0700 2009 more or less completed the renaming and a littl... [David Lee]
file README.textile Mon Oct 12 00:03:54 -0700 2009 Oops - update the README to better reflect the ... [davidlee]
file Rakefile Sat Oct 17 05:07:37 -0700 2009 remove debugger from spec.opts; comments [davidlee]
file VERSION.yml Sat Oct 17 19:21:29 -0700 2009 Version bump to 0.13.1 [davidlee]
file WORKFLOW.textile Wed Jul 15 17:15:06 -0700 2009 textilised WORKFLOW.textile [davidlee]
file cucumber.yml Sat Jun 20 19:59:52 -0700 2009 Added cucumber feature for methods defined on i... [davidlee]
file example.rb Tue Aug 04 17:36:40 -0700 2009 Version bump to 0.4.0 [davidlee]
file extend_temporarily_spec.rb Sun Aug 23 20:41:22 -0700 2009 Huge rewrite of StateFu's internals for far mor... [davidlee]
directory features/ Sun Aug 23 20:41:22 -0700 2009 Huge rewrite of StateFu's internals for far mor... [davidlee]
file init.rb Tue Aug 04 17:36:40 -0700 2009 Version bump to 0.4.0 [davidlee]
directory lib/ Sat Oct 17 19:14:20 -0700 2009 Make ActiveRecord::Base set the state field bef... [davidlee]
directory log/ Sun Aug 23 20:41:22 -0700 2009 Huge rewrite of StateFu's internals for far mor... [davidlee]
directory spec/ Wed Nov 11 21:54:09 -0800 2009 Bugfix for stupid message when ruby double (rr)... [davidlee]
file state-fu.gemspec Sat Oct 17 19:21:36 -0700 2009 Regenerated gemspec for version 0.13.1 [davidlee]
file todo.org Sat Oct 17 00:32:14 -0700 2009 Rename Logger to Logging; refactor Logging; app... [davidlee]
README.textile

StateFu

What is it?

StateFu is another Ruby state machine.

What is a state machine?

Finite state machines are a model for program behaviour; like
object-oriented programming, they provide an abstract way to think
about a domain.

In a finite state machine, there are a number of discrete states. Only
one state may be occupied at any given time (hence the “finite”).

States are linked together by events, and there are rules which govern
when or how transitions between states can occur. Actions may be fired
on entry to or exit from a state, or when a certain transition occurs.

Why is StateFu different to the other twenty state machines for Ruby?

State machines are potentially a powerful way to simplify and
structure a lot of problems. They can be used to:

  • succinctly define the grammar of a networking protocol or a
    configuration DSL
  • clearly and compactly describe complex logic, which might otherwise
    be difficult to understand by playing “follow the rabbit” through
    methods thick with implementation details
  • serialize and process multiple revisions of changing business rules
  • provide an abstract representation of program or domain behaviour
    which can be introspected, edited, queried and executed on the fly,
    in a high-level and human-readable format
  • provide a straightforward and easy way to record and validate
    “status” information, especially when there are rules governing
    when and how it can be updated
  • reduce proliferation of classes and modules, or easily define and
    control functionally related groups of objects, by defining
    behaviours on interacting components of a state machine
  • elegantly implement simple building blocks like stacks / queues,
    parsers, schedulers, automata, etc

StateFu was written from the ground up with one goal in mind: to be
over-engineered. It is designed to make truly ambitious use of state
machines not only viable, but strongly advantageous in many situations.

It is designed in the very opposite vein to the intentional minimalism
of most ruby state machine projects; it is tasked with taking on a
great deal of complexity and functionality, and abstracting it behind
a nice DSL, so that the code which you have to maintain is shorter and
clearer.

StateFu allows you to:

  • give a class any number of machines
  • define behaviours on state entry / exit; before, after or during
    execution of a particular event; or before / after every transition
    in a given machine
  • give any object own its own private, “singleton” machines,
    which are unique to that object and modifiable at runtime.
  • create events with any number of origin or target states
  • define and query guard conditions / transition requirements,
    to establish rules about when a transition is valid
  • use powerful reflection and logging capabilities to easily expose
    and debug the operation of your machines
  • automatically and unobtrusively define methods for querying each
    state and event, and for firing transitions
  • easily find out which transitions are valid at any given time
  • generate descriptive, contextual messages when a transition is
    invalid
  • halt a transition during execution
  • easily extend StateFu’s DSL to match the problem domain
  • fire transitions with a payload of arguments and program context,
    which is available to guard conditions, event hooks, and
    requirement messages when they are evaluated
  • use a lovely, simple and flexible API which gives you plenty of
    choices about how to describe your problem domain; choose (or
    build) a programming style which suits the task at hand from an
    expressive range of options
  • store arbitrary meta-data on any component of StateFu – a simple
    but extremely powerful tool for integration with almost anything.
  • flexible and helpful logging out of the box – will use the Rails
    logger if you’re in a Rails project, or standalone logging to
    STDOUT or a file. Configurable loglevel and message prefixes help
    StateFu be a good citizen in a shared application log.
  • automatically generate diagrams of state machines / workflows with graphviz
  • use an ActiveRecord field for state persistence, or a regular
    attribute – or use both, on the same class, for different
    machines. If an appropriate ActiveRecord field exists for a
    machine, it will be used. Otherwise, an attr_accessor will be used
    (and created, if necessary).
  • customising the persistence mechanism (eg to use a Rails session,
    or a text file, or your choice of ORM) is usually as easy as
    defining a getter and setter method for the persistence field, and
    a rule about when to use it. If you want to use StateFu with a
    persistence mechanism which is not yet supported, send me a message.
  • StateFu is fast, lightweight and useful enough to use in any ruby
    project – works with Rails but does not require it.

Still not sold?

StateFu is forged from a reassuringly dense but unidentifiable metal
which comes only from the rarest of meteorites, and it ticks when you
hold it up to your ear.1

It is elegant, powerful and transparent enough that you can use
it to drive substantial parts of your application, and actually want
to do so.

It is designed as a library for authors, as well as users, of
libraries: StateFu goes to great lengths to impose very few limits on
your ability to introspect, manipulate and extend the core features.

It is also delightfully elegant and easy to use for simple things:



  class Document < ActiveRecord::Base
    include StateFu

    def update_rss
      puts "new feed!"
      # ... do something here
    end

    machine( :status ) do
      state :draft do
        event :publish, :to => :published
      end

      state :published do
        on_entry :update_rss
        requires :author  # a database column
      end

      event :delete, :from => :ALL, :to => :deleted do
        execute :destroy
      end

      # save all states once transition is complete.
      # this wants to be last, as it iterates over each state which is
      # already defined.
      states do
        accepted { save! }
      end
    end
  end

  my_doc = Document.new

  my_doc.status                          # returns a StateFu::Binding, which lets us access the 'Fu
  my_doc.status.state     => 'draft'     # if this wasn't already a database column or attribute, an
                                         # attribute has been created to keep track of the state
  my_doc.status.name      => :draft      # the name of the current_state (defaults to the first defined)
  my_doc.status.publish!                 # raised =>  StateFu::RequirementError: [:author]
                                         # the author requirement prevented the transition
  my_doc.status.name      => :draft      # see? still a draft.
  my_doc.author = "Susan"                # so let's satisfy it ...
  my_doc.publish!                        # and try again.
  "new feed!"                            # aha - our event hook fires!
  my_doc.status.name      => :published  # and the state has been updated.

StateFu works with any modern Ruby ( 1.8.6, 1.8.7, and 1.9.1)

Getting started

You can either clone the repository in the usual fashion (eg to
yourapp/vendor/plugins/state-fu), or use StateFu as a gem.

To install as a gem:


gem install davidlee-state-fu -s http://gems.github.com

To require it in your ruby project:


require 'rubygems'
require 'state-fu'

To install the dependencies for running specs:


 sudo gem install rspec rr
 rake             # run the specs
 rake spec:doc    # generate specdocs
 rake doc         # generate rdocs
 rake build       # build the gem locally
 rake install     # install it

Now you can simply include StateFu in any class you wish to make stateful.

The spec/ and features/ folders are currently one of the best source
of documentation. The documentation is gradually evolving to catch up
with the features, but if you have any questions I’m happy to help you
get started.

If you have questions, feature request or ideas, please join the
google group or send me a
message on GitHub.

A note about ActiveSupport

StateFu will use ActiveSupport if it is already loaded. If not, it
will load its own (heavily trimmed) ‘lite’ version.

In most projects this will behave transparently, but it does mean that
if you require StateFu before other libraries which
require ActiveSupport (e.g. ActiveRecord), you may have to
explicitly require 'activesupport' before loading the
dependent libraries.

So if you plan to use ActiveSupport in a stand-alone project with
StateFu, you should require it before StateFu.

Addditional Resources

Also see the issue tracker

And the build monitor

And the RDoc

StateFu is not a complete BPM (Business Process Management) platform

It’s worth noting that StateFu is at it’s core a state machine, which
strives to be powerful enough to be able to drive many kinds of
application behaviour.

It is not, however, a classical workflow engine on par with Ruote. In
StateFu the basic units with which “workflows” are built are states
and events; Ruote takes a higher level view, dealing with processes
and participants. As a result, it’s capable of directly implementing
these design patterns:

http://openwferu.rubyforge.org/patterns.html

Whereas StateFu cannot, for example, readily model forking / merging
of processes (nor does it handles scheduling, process management, etc.

The author of Ruote, the Ruby Workflow Engine, outlines the difference
pretty clearly here:

http://jmettraux.wordpress.com/2009/07/03/state-machine-workflow-engine/

If your application can be described with StateFu, you’ll likely find
it simpler to get running and work with; if not, you may find Ruote,
or a combination of the two, suits your needs perfectly.

Thanks

  • dsturnbull, for patches
  • lachie, benkimball for pointing out README bugs / typos
  • Ryan Allen for his original Workflow library