<img src=“https://secure.travis-ci.org/troessner/transitions.png?branch=master”/>
transitions
is a ruby state machine implementation.
This goes into your Gemfile:
gem "transitions", :require => ["transitions", "active_model/transitions"]
… and this into your ORM model:
include ActiveModel::Transitions
gem install transitions
class Product include ActiveModel::Transitions state_machine do state :available # first one is initial state state :out_of_stock, :exit => :exit_out_of_stock state :discontinued, :enter => lambda { |product| product.cancel_orders } event :discontinued do transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => :do_discontinue end event :out_of_stock do transitions :to => :out_of_stock, :from => [:available, :discontinued] end event :available do transitions :to => :available, :from => [:out_of_stock], :guard => lambda { |product| product.in_stock > 0 } end end end
In this example we assume that you are in a rails project using Bundler, which would automitcally require ‘transitions`. If this is not the case for you you have to add
require 'transitions'
whereever you load your dependencies in your application.
A word of warning: Use symbols, not strings for declaring the state machine. Using strings is not supported as is using whitespace in names (because ‘transitions` possibly generates methods out of this).
When you declare an event, say discontinue
, three methods are declared for you: discontinue
, discontinue!
and can_discontinue?
. The first two events will modify the state
attribute on successful transition, but only the bang(!)-version will call save!
. The can_discontinue?
method will not modify state but instead returns a boolean letting you know if a given transition is possible.
transitions
will automatically generate scopes for you if you are using ActiveRecord and tell it to do so via the auto_scopes
option:
Given a model like this:
class Order < ActiveRecord::Base include ActiveModel::Transitions state_machine :auto_scopes => true do state :pick_line_items state :picking_line_items end end
you can use this feature a la:
>> Order.pick_line_items => [] >> Order.create! => #<Order id: 3, state: "pick_line_items", description: nil, created_at: "2011-08-23 15:48:46", updated_at: "2011-08-23 15:48:46"> >> Order.pick_line_items => [#<Order id: 3, state: "pick_line_items", description: nil, created_at: "2011-08-23 15:48:46", updated_at: "2011-08-23 15:48:46">]
Each event definition takes an optional “on_transition” argument, which allows you to execute methods on transition. You can pass in a Symbol, a String, a Proc or an Array containing method names as Symbol or String like this:
event :discontinue do transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => [:do_discontinue, :notify_clerk] end
In case you need to trigger a method call after a successful transition you can use success
:
event :discontinue, :success => :notfiy_admin do transitions :to => :discontinued, :from => [:available, :out_of_stock] end
In addition to just specify the method name on the record as a symbol you can pass a lambda to perfom some more complex success callbacks:
event :discontinue, :success => lambda { |order) AdminNotifier.notify_about_discontinued_order(order) } do transitions :to => :discontinued, :from => [:available, :out_of_stock] end
If you’d like to note the time of a state change, Transitions comes with timestamps free! To activate them, simply pass the :timestamp option to the event definition with a value of either true or the name of the timestamp column. *NOTE - This should be either true, a String or a Symbol*
# This will look for an attribute called exploded_at or exploded_on (in that order) # If present, it will be updated event :explode, :timestamp => true do transitions :from => :complete, :to => :exploded end # This will look for an attribute named repaired_on to update upon save event :rebuild, :timestamp => :repaired_on do transitions :from => :exploded, :to => :rebuilt end
In case you define ‘event_fired` and / or `event_failed`, `transitions` will use those callbacks correspondingly. You can use those callbacks like this:
def event_fired(current_state, new_state, event) MyLogger.info "Event fired #{event.inspect}" end def event_failed(event) MyLogger.warn "Event failed #{event.inspect}" end
You can easily get a listing of all available states:
Order.available_states # Uses the <tt>default</tt> state machine # => [:pick_line_items, :picking_line_items]
state_machine :initial => :closed do state :open state :closed end
Copyright © 2010 Jakub Kuźma, Timo Rößner. See LICENSE for details.