分类:
2008-07-19 13:02:00
I’ve been noticing a pattern in several of my projects where I’m having a model object keep track of its state. Sometimes it’s a simple boolean flag, and this presents little problem. Other times, it gets more complex. In a current project, things started out simply. Then the model evolved and finally I realized that I was using three boolean variables to keep the state. To make matters worse, these three flags were being distilled into one state. Needless to say, this collapsed under its own tremendous weight fairly quickly.
So I stood at my newly hung whiteboard and thought and thought. I realized that this object was not just in a state, but moving from state to state, in a well defined series of steps, throughout its lifetime. I do believe this is what them academicals calls a “finite automaton” or “state machine” for those of us who doodled or slept during class. I drew some circles and lines (I have 4 colors) and concluded that yes, this was a fine idea, I would make some coffee and write this immediately.
I’ve been hacking at it here and there for a few days, incorporating it into some projects. It’s working well. Today I’ve reached what I’ve tagged release 2.0 of the Acts As State Machine plugin. I’m still polishing the documentation, but you can find some quick . You can get the code with svn from . You’ll probably have to log in as anonymous/anonymous because I’m too dumb to figure out how to make TxD serve that up without authentication.
Give it a try if you like, send feedback if it pleases you. You’ll be hearing more about it in the future, in bare naked detail. Enjoy!
Update
Someone commented for an example. The ones given were a bit abstract, so I’ve posted a more concrete .
# Acts As State Machine Examples
#
# Of note:
# * For every 'state' you define, a query method? is created
# * For every 'event' you define, a method! is created
# * Entry, exit, and guard actions can be defined either as a Proc object or
# as a symbol. The symbol should be the name of an instance method on the
# model.
# A simple transition
#
# +---------+ run +---------+
# | idle |-------->| running |
# +---------+ +---------+
#
class Machine < ActiveRecord::Base
acts_as_state_machine :initial => :idle
state :idle
state :running
event :run do
transitions :from => :idle, :to => :running
end
end
# A loopback transition
# +---------+
# | |------+
# | idle | | timeout
# | |<-----+
# +---------+
#
class Machine < ActiveRecord::Base
acts_as_state_machine :initial => :idle
state :idle
event :timeout do
transitions :from => :idle, :to => :idle
end
end
# A transition with enter and exit actions
# +---------+ +---------+
# | | run | |
# | idle |-------------->| running |
# | | stop_timer | |
# +---------+ do_work +---------+
#
class Machine < ActiveRecord::Base
acts_as_state_machine :initial => :idle
state :idle
state :running, :enter => Proc.new { |o| o.stop_timer('idle'); o.do_work() },
:exit => :exit_running
event :run do
transitions :from => :idle, :to => :running
end
def stop_timer(timer)
end
def do_work
end
def exit_running
end
end
# Transition guards
# +---------+ +---------+
# | | run[t] | |
# | idle |-------------->| running |
# | | stop_timer | |
# +---------+ do_work +---------+
# | ^
# | |
# +_____+
# run[f]
#
class Machine < ActiveRecord::Base
acts_as_state_machine :initial => :idle
state :idle
state :running, :enter => Proc.new { |o| o.stop_timer('idle'); o.do_work() }
event :run do
transitions :from => :idle, :to => :running, :guard => Proc.new {|o| o.can_go? }
transitions :from => :idle, :to => :idle
end
def stop_timer(timer)
end
def do_work
end
def can_go?
# Returns true or false, state advances accordingly
end
end