state_shifter
NOTE This gem has been deprecated, and will no longer be updated. I wrote a new one
This gem makes it easy to incorporate state machine behavior in a Ruby class.
Features include:
- on_entry and on_transition handlees
- ActiveRecord integration
- event guards and easy event handlers
- graphViz visualization creator
- flexible machine syntax
Usage
An example of state machine definition possible with this gem:
class Simple
include StateShifter::Definition
state_machine do
# first state to be defined is the initial one
state :new do
event :submit => :awaiting_review
end
state :awaiting_review do
event :review => :being_reviewed
end
state :being_reviewed do
event :accept => :accepted, :if => :cool_article?
event :reject => :rejected, :if => :bad_article?
end
state :accepted
state :rejected
end
def cool_article?
true
end
def bad_article?
false
end
end
Basically, you need to have a state_machine
block with a collection of states and events. The initial state is the first one on the definition, and events are in the form of event :event_name => :next_state_name
. Events can have guards, and also refer back to the same state, in which case you simple omit the next_state_name
- mostly to have "touch-and-go" events that just execute a method specified in the :call
option passed to it, and remain in the same state. The next example shows relevant usage of it.
class Advanced
include StateShifter::Definition
###
state_machine do
state :initialized do
event :start_date_changed, :call => :handle_start_date_changed
event :forced_start => :running
event :start_date_reached => :running, :if => :start_date_reached?
event :abort_initialized_contest => :finalized
end
state :running do
on_entry do |previous_state, trigger_event|
running_entry previous_state, trigger_event
end
event :abort_running_contest => :notify_stakeholders
event :deadline_reached => :notify_organizers, :if => :entries_deadline_reached?
event :spots_filled => :notify_organizers, :if => :spots_filled?
event :deadline_reached_without_approvals => :notify_pending_users, :if => :entries_deadline_reached_without_approvals?
event :deadline_reached_without_entries => :finalized, :if => :entries_deadline_reached_without_entries?
end
state :notify_organizers do
on_entry :send_notification_to_organizers
event :organizers_notified => :awaiting_organizer_reply
end
state :awaiting_organizer_reply do
event :organizer_confirmation_missing => :notify_stakeholders, :if => :organizer_confirmation_deadline_reached?
event :organizer_confirmation_received => :notify_approved_users
event :organizer_has_more_tickets => :running
end
state :notify_stakeholders do
on_entry :send_notification, :stakeholders, :organizers
event :stakeholders_notified => :cancelled
end
state :cancelled
state :notify_pending_users do
on_entry :send_notification, :pending_users
event :pending_users_notified => :finalized
end
state :notify_approved_users do
on_entry :send_notification_to_approved_users
event :approved_users_notified => :send_list_to_organizers
end
state :send_list_to_organizers do
on_entry :send_guestlist_to_organizers
event :list_sent_to_organizers => :awaiting_attendance
end
state :awaiting_attendance do
event :remind_to_fill_in_report => :create_report_filling_requests
end
state :create_report_filling_requests do
on_entry :send_report_filling_requests
event :finalize => :finalized
end
state :finalized
on_transition do |from,to,trigger_event, duration|
benchmark from, to, trigger_event, duration
end
end
###
def send_notification to
#
end
def entries_deadline_reached?
true
end
def running_entry previous_state, trigger_event
#
end
def benchmark from, to, trigger_event, duration
#
end
end
Plagiarism alert
This gem draws heavy inspiration from both pluginaweek's state_machine and mdh's ssm gems. I liked both of them, but the DSL syntax was not 100% to my liking. Kudos to them.
Future
I want to add "mountable" state machines as per ssm's gem, to have a clear separation which would ease testing, but haven't had the chance yet.
Contributing to state_shifter
- Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
- Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
- Fork the project.
- Start a feature/bugfix branch.
- Commit and push until you are happy with your contribution.
- Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
- Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
Copyright
Copyright (c) 2012 Bruno Antunes. See LICENSE.txt for further details.