Bstard - A New State(sman) Machine
A small state machine library with a simple interface. Designed around the concept of aggregation rather than inheritance enabling its use wherever you need it without the need to derive from it or mix it in.
Installation
Add this line to your application's Gemfile:
gem 'bstard'
And then execute:
$ bundle
Or install it yourself as:
$ gem install bstard
Usage
Use Bstard.define to describe your state machine
machine = Bstard.define do |fsm|
fsm.initial :new
fsm.event :submit, :new => :submitted
fsm.event :delete, :submitted => :deleted
end
This defines a state machine that:
- has new, submitted and deleted states
- responds to submit and delete events
- has an initial state of new
- transitions from new to submitted when triggered by the submit event
- transitions from submitted to deleted when triggered by the delete event
Pretty simple huh?
Initial State
Set the initial state of the machine with initial. If no initial state is set a default state :uninitialized is used. As a safeguard initial will ignore nil or empty string values.
Querying the State
The current state of the machine is accessed via the current_state method
machine.current_state
#=> :new
Helper methods are automatically generated for each state consisting of the state name suffixed with a question mark. This method returns true if the current state matches the queried state or false otherwise.
machine.new?
#=> true
machine.submitted?
#=> false
machine.deleted?
#=> false
Querying Transitions
Helper methods are dynamically generated to enable querying whether an event can transition the current state. These methods are named using the event name prefixed with 'can_' and suffixed with a question mark.
machine.current_state
#=> :new
machine.can_submit?
#=> true
machine.can_delete?
#=> false
Defining Events
Events are defined by supplying an event name followed by one or more state transition declarations. Each state transition declaration is just a hash key value pair. The key represents the starting state and the value the ending state.
machine = Bstard.define do |fsm|
fsm.initial :new
fsm.event :save, :new => :draft
fsm.event :approve, :draft => :approved
fsm.event :publish, :approved => :published
fsm.event :delete, :draft => :deleted, :approved => :deleted
end
I find the spear => Hash syntax style works best for me in describing the intent of the definition.
Triggering Events
Trigger events by sending a message using the event name suffixed with an exclamation mark.
machine.submit!
#=> :submitted
machine.delete!
#=> :deleted
Callbacks
Callbacks can be configured for particular events and state changes, or for any event or state change.
Event callbacks are configured with on
machine = Bstard.define do |fsm|
# ...
fsm.on :submit do |event, state, next_state|
# code that needs to be run when :submit is triggered
end
end
State change callbacks are configured with when
machine = Bstard.define do |fsm|
# ...
fsm.when :submitted do |event, previous_state, state|
# code that needs to run when state changes to :submitted
end
end
Use the symbol :any for event or state change callbacks that should run when any event is triggered or after any state change.
machine = Bstard.define do |fsm|
# ...
fsm.on :any do |event, state, next_state|
# code that will run when any event is triggered
end
fsm.when :any do |event, previous_state, state|
# code that will run when after any state change
end
end
e.g. Use initial and when :any to persist state
class MyModel < ActiveRecord::Base
def make_draft
# do stuff
state.save!
save
end
private
def state
@state ||= Bstard.define do |fsm|
fsm.initial status
fsm.event :save, :new => :draft
fsm.event :approve, :draft => :approved
fsm.when :any do |event, prev_state, new_state|
status = new_state
end
end
end
end
Exceptions
An InvalidTransition error will be raised if an event is triggered on a state that cannot transition on that event.
Development
After checking out the repo, run bin/setup to install dependencies. Then, 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 run the tests, run bundle exec rake test.
Contributing
- Fork it ( https://github.com/tonyheadford/bstard/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request