simple_states 
A super-slim (~200 loc) statemachine-like support library focussed on use in Travis CI.
Usage
Define states and events like this:
class Foo
include SimpleStates
states :created, :started, :finished
event :start, :from => :created, :to => :started, :if => :startable?
event :finish, :to => :finished, :after => :cleanup
attr_accessor :state, :started_at, :finished_at
def start
# start foo
end
def startable?
true
end
def cleanup
# cleanup foo
end
end
Including the SimpleStates module to your class is currently required. We'll add hooks for ActiveRecord etc later.
SimpleStates expects your model to support attribute accessors for :state.
Event options have the following well-known meanings:
:from # valid states to transition from
:to # target state to transition to
:if # only proceed if the given method returns true
:unless # only proceed if the given method returns false
:before # run the given method before running `super` and setting the new state
:after # run the given method at the very end
All of these options except for :to can be given as a single symbol or string or
as an Array of symbols or strings.
Calling event will effectively add methods to a proxy module which is
included to the singleton class of your class' instances. E.g. declaring event
:start in the example above will add methods start and start! to a module
included to the singleton class of instances of Foo.
This method will
- check if
:if/:unlessconditions apply (if given) and just return from the method otherwise - check if the object currently is in a valid
:fromstate (if given) and raise an exception otherwise - run
:beforecallbacks (if given) - call
superif Foo defines the current method (i.e. callstartbut notfinishin the example above) - add the object's current state to its
past_stateshistory - set the object's
stateto the target state given as:to - set the object's
[state]_atattribute toTime.nowif the object defines a writer for it - run
:aftercallbacks (if given)
You can define options for all events like so:
event :finish, :to => :finished, :after => :cleanup
event :all, :after => :notify
This will call :cleanup first and then :notify on :finish.
If no target state was given for an event then SimpleStates will try to derive
it from the states list. I.e. for an event start it will check the states
list for a state started and use it. If it can not find a target state this
way then it will raise an exception.
By default SimpleStates will assume :created as an initial state. You can
overwrite this using:
# note that we have to use self here!
self.initial_state = :some_state
So with the example above something the following would work:
foo = Foo.new
foo.state # :created
foo.created? # true
foo.was_created? # true
foo.state?(:created) # true
foo.start # checks Foo#startable? and then calls Foo#start
# calling foo.start! (with exclamation mark) would perform same actions as foo.start, but
# also call foo.save! afterwards.
foo.state # :started
foo.started? # true
foo.started_at # Time.now
foo.created? # false
foo.was_created? # true
foo.finish # just performs state logic as there's no Foo#finish
foo.state # :finished
foo.finished? # true
foo.finished_at # Time.now
foo.was_created? # true
foo.was_started? # true
In order to treat states as "ordered", the option ordered: true can be
passed. Events will still behave the same, callbacks be called, etc. However,
the state attribute will never be set back to a previous state.
For example:
class Foo
include SimpleStates
states :created, :started, :finished, ordered: true
end
foo = Foo.new
foo.finish
foo.start
p foo.state
# => :finished