StrictMachine
Easily add state-machine functionality to your Ruby classes.
Installation
Add this line to your application's Gemfile:
gem 'strict_machine'And then execute:
$ bundle
Or install it yourself as:
$ gem install strict_machine
Usage
You can use this gem in two ways - either by embedding a state machine onto your class, or by mounting a separate class.
Embedded
class A < StrictMachine::Base
strict_machine do
state :initial do
on hop: :middle
end
state :middle do
on hop: :final
end
state :final
on_transition do |from, to, trigger_event, duration|
log from, to, trigger_event, duration
end
end
def log(from, to, trigger_event, duration)
# ...
end
endMounting
(using same class A as previous example, minus the log method)
class B
include StrictMachine::MountStateMachine
mount_state_machine A
def log(from, to, trigger_event, duration)
# ...
end
endNOTE: when mounting, the methods referenced should always be on the class doing the mounting, not on the state machine one!
Object extensions
Whether embedding or mounting, an object instance will have the following methods added to it:
-
#statereturns the current state's name -
#trigger(*transitions)triggers the given transition(s) by name -
#state_attrthe name of the state attribute being used -
#stateslist ofStateobjects in the machine's definition
and an object's class will have:
-
#strict_machine_attrthe name of the attribute to store state in -
#strict_machine_classeither the class containing the state machine, when mounting, orselfwhen embedding
Also, the target class (self when embedding, or the state machine class when mounting) will have:
-
#strict_machinethe DSL hook -
#definitionthe DSL evaluation context object
Internals
The gem works by storing, at the object's class level, the state machine's
definition and the name of the state storage attribute. Every instance of the class embedding or mounting a state machine will have its own state (stored in said attribute (by default state), and upon transitions, the current state and the transition requested will be passed to #change_state in
InstanceMethods and a new state will be reached (and true returned), or
an exception will be raised. Existing exceptions:
-
StateNotFoundErrortried to transition to a non-existing state -
TransitionNotFoundErrorthe transition requested does not exist -
GuardedTransitionErrorthe guard condition for the transition was not met
You can bypass the guard condition on a transition by adding a bang ! to the
transition's name, i.e.,
obj.trigger('transition!') # bypass guardsThe DSL
Here's a complete example:
strict_machine do
state :new do
on submit: :awaiting_review
end
state :awaiting_review do
on review: :under_review
end
state :under_review do
on_entry { |previous, trigger| some_method(previous, trigger) }
on accept: :accepted, if: :cool_article?
on reject: :rejected, if: :bad_article?
end
state :accepted
state :rejected
on_transition do |from, to, trigger_event, duration|
log from, to, trigger_event, duration
end
endGuards (the :ifs on on calls) must return true in order to pass.
When mounting, you can specify the state attribute's name with:
mount_state_machine SomeClass, state: "meh"
And when embedding:
strict_machine("meh") do ....
Development
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 strict_machine.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.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/sardaukar/strict_machine.
License
The gem is available as open source under the terms of the MIT License.