This is a library for defining very simple state machines.
To define a state machine first, include StateMachine
to your class. Next step is defining states and events.
class MovementState
include StateMachine
state :standing, initial: true
state :walking
state :running
event :walk do
transitions from: :standing, to: :walking
end
event :run do
transitions from: [:standing, :walking], to: :running
end
event :hold do
transitions from: [:walking, :running], to: :standing
end
end
movement_state = MovementState.new(:standing)
movement_state.walk!
movement_state.walking? # => true
movement_state.hold! # => raise UnknownTransitionError
movement_state.can_hold? # => false
movement_state.standing? # => false
See more examples in examples directory
Each state can have callbacks for entering or leaving the state. A callback can be a method name defined on your StateMachine or a proc accepting your state machine instance.
class MovementState
include StateMachine
state :standing, initial: true
state :walking, before_enter: :before_walking_callback, after_leave: ->(movement_state) { movement_state.after_stop_walking }
state :running, after_enter: :after_started_running
def before_walking_callback
# do something
end
def after_stop_walking
# do something elese
end
def after_started_running
# do stuff
end
end
Available callbacks: before_enter
, after_enter
, before_leave
, after_leave
Transitions support before and after callbacks. A callback can be a method name defined on your StateMachine or a proc accepting your state machine instance.
class MovementState
include StateMachine
state :standing, initial: true
state :walking
event :walk do
transitions from: :standing, to: :walking, before: :before_start_walking
end
event :hold do
transitions from: :walking, to: :standing, before: :before_stop_walking, after: ->(movement_state) { movement_state.after_stop_walking }
end
def before_start_walking
# do something
end
def before_stop_walking
# do something else
end
def after_stop_walking
# do something more
end
end
Callbacks are called in following order:
- state
before_leave
- state
before_enter
- transition
before
- updates state
- transition
after
- state
after_enter
- state
after_leave
A condition can be defined on a transition to check if it can be run. A condition can be a method or a proc.
class MovementState
include StateMachine
state :standing, initial: true
state :walking
event :walk do
transitions from: :standing, to: :walking, when: :not_sleeping?
end
event :hold do
transitions from: :walking, to: :standing, when: ->(movement_state) { |movement_state| movement_state.walked_kilometer? }
end
def not_sleeping?
true
end
def walked_kilometer?
# do some calculations
end
end
You can genrate a graph for your state machine using GraphViz tool. To do so you can
- use command line tool:
bin/generate_graph FancyMachine ./spec/support/fancy_machine.rb images/
. - or use drawer directly from code:
StateMachine::MachineGraph.new(FancyMachine, path: './images').draw
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/dziulius/state_machine.