Progressive
A simple ActiveModel backed state machine.
Why yet another state machine?
You may be asking why another state machine implementation? Well, first off YASM. Second, most other implementations rely on class level attributes and make it difficult for inheritance and the rare case when a subject will need to have multiple states.
If you only need to have 1 state for a model, Progressive works fine for that too.
Progressive interacts with the states and events for a model at an instance level, not class level. There are no magic methods defined based on your states or events it relies on method missing so it's very interoperable.
Installation
Add this line to your application's Gemfile:
gem 'progressive'
And then execute:
$ bundle
Or install it yourself as:
$ gem install progressive
Usage
Defining states on the subject model:
class State < Progressive::State
end
class User < ActiveRecord::Base
include Progression::Subject
has_many :states, :as => :subject, :dependent => :destroy
states do
state :pending do
event :potential
end
state :potential do
event :interview => :interviewing
event :archive => :archived
end
state :interviewing do
event :potential
event :archive => :archived
event :hire => :hired
end
state :hired
end
after_hire :user_rollup
before_interview :notify_creator
def user_rollup
# Do something
end
def notify_creator
end
end
Interfacing with the state
actor = User.first
user = User.first
state = user.states.first
state.state # => 'pending'
state.default_state # => :pending
state.to(:archived, :actor => actor) # => false
state.to(:potential, :actor => actor) # => true
state.state # => 'potential'
state.specification # => Progressive::Specification
state.current_state # => Progressive::Specification::State
Short circuiting an event
Since Progressive uses the same ActiveModel callbacks you're familiar with in your ActiveRecord models, you can short circuit the event just by returning a falsey statement within each callback.
You can add a before or after callback for any event. To see which events you have available just check the Progressive specification:
Video.specification.event_names # => [:converting, :publish]
Example:
class Video < ActiveRecord::Base
include Progression::Subject
has_one :state, :as => :subject, :dependent => :destroy
states do
state :pending do
event :converting
end
state :converting do
event :publish => :published
end
state :published
end
before_converting :valid_file_size?
def valid_file_size?
file_size < 1.gigabyte
end
end
video = Video.first
video.file_size # => 2.gigabytes
video.state # => :pending
video.converting # => false
video.state # => :pending
Maintainers
Contributing
- Fork it
- 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 new Pull Request