README.md in transitions-0.1.11 vs README.md in transitions-0.1.12

- old
+ new

@@ -20,49 +20,53 @@ ### Installation #### Rails This goes into your Gemfile: +```ruby +gem "transitions", :require => ["transitions", "active_model/transitions"] +``` - gem "transitions", :require => ["transitions", "active_model/transitions"] - … and this into your ORM model: +```ruby +include ActiveModel::Transitions +``` - include ActiveModel::Transitions - #### Standalone +```shell +gem install transitions +``` - gem install transitions - ### Using transitions +```ruby +class Product + include ActiveModel::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 } - 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, :success => :reorder 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 + event :discontinued do + transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => :do_discontinue end - + event :out_of_stock, :success => :reorder 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 automatically require `transitions`. If this is not the case for you you have to add +```ruby +require 'transitions' +``` - require 'transitions' - wherever you load your dependencies in your application. **Known limitations:** * You can only use one state machine per model. While in theory you can @@ -78,101 +82,110 @@ #### Getting and setting the current state Use the (surprise ahead) `current_state` method - in case you didn't set a state explicitly you'll get back the state that you defined as initial state. +```ruby +>> Product.new.current_state +=> :available +``` - >> Product.new.current_state - => :available - You can also set a new state explicitly via `update_current_state(new_state, persist = true / false)` but you should never do this unless you really know what you're doing and why - rather use events / state transitions (see below). Predicate methods are also available using the name of the state. +```ruby +>> Product.new.available? +=> true +``` - >> Product.new.available? - => true - #### Events 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. In addition, a `can_transition?` method is added to the object that expects one or more event names as arguments. This semi-verbose method name is used to avoid collission with [https://github.com/ryanb/cancan](the authorization gem CanCan). +```ruby +>> Product.new.can_transition? :out_of_stock +=> true +``` - >> Product.new.can_transition? :out_of_stock - => true - If you need to get all available transitions for current state you can simply call: - - >> Product.new.available_transitions - => [:discontinued, :out_of_stock] +```ruby +>> Product.new.available_transitions +=> [:discontinued, :out_of_stock] +``` #### Automatic scope generation `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 - event :move_cart do - transitions to: :pick_line_items, from: :picking_line_items - end - end +```ruby +class Order < ActiveRecord::Base + include ActiveModel::Transitions + state_machine :auto_scopes => true do + state :pick_line_items + state :picking_line_items + event :move_cart do + transitions to: :pick_line_items, from: :picking_line_items end + end +end +``` you can use this feature a la: +```ruby +>> 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">] +``` - >> 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">] - #### Using `guard` Each event definition takes an optional `guard` argument, which acts as a predicate for the transition. You can pass in Symbols, Strings, or Procs like this: +```ruby +event :discontinue do + transitions :to => :discontinued, :from => [:available, :out_of_stock], :guard => :can_discontinue +end +``` - event :discontinue do - transitions :to => :discontinued, :from => [:available, :out_of_stock], :guard => :can_discontinue - end - or +```ruby +event :discontinue do + transitions :to => :discontinued, :from => [:available, :out_of_stock], :guard => [:can_discontinue, :super_sure?] +end +``` - event :discontinue do - transitions :to => :discontinued, :from => [:available, :out_of_stock], :guard => [:can_discontinue, :super_sure?] - end - Any arguments passed to the event method will be passed on to the `guard` predicate. #### Using `on_transition` 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: +```ruby +event :discontinue do + transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => [:do_discontinue, :notify_clerk] +end +``` - event :discontinue do - transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => [:do_discontinue, :notify_clerk] - end - Any arguments passed to the event method will be passed on to the `on_transition` callback. `on_transition` is called after `guard` and before `enter` on the state that it is transitioning to. #### Using `enter` and `exit` @@ -191,93 +204,102 @@ In case you need to trigger a method call after a successful transition you can use `success`. This will be called after the `save!` is complete (if you use the `state_name!` method) and should be used for any methods that require that the object be persisted. +```ruby +event :discontinue, :success => :notfiy_admin do + transitions :to => :discontinued, :from => [:available, :out_of_stock] +end +``` - 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: +```ruby +event :discontinue, :success => lambda { |order| AdminNotifier.notify_about_discontinued_order(order) } do + transitions :to => :discontinued, :from => [:available, :out_of_stock] +end +``` - event :discontinue, :success => lambda { |order| AdminNotifier.notify_about_discontinued_order(order) } do - transitions :to => :discontinued, :from => [:available, :out_of_stock] - end - If you need it, you can even call multiple methods or lambdas just passing an array: +```ruby +event :discontinue, :success => [:notify_admin, lambda { |order| AdminNotifier.notify_about_discontinued_order(order) }] do + transitions :to => :discontinued, :from => [:available, :out_of_stock] +end +``` - event :discontinue, :success => [:notify_admin, lambda { |order| AdminNotifier.notify_about_discontinued_order(order) }] do - transitions :to => :discontinued, :from => [:available, :out_of_stock] - end - #### Timestamps 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* +```ruby +# 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 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 +``` - # 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 - #### Using `event_fired` and `event_failed` In case you define `event_fired` and / or `event_failed`, `transitions` will use those callbacks correspondingly. You can use those callbacks like this: +```ruby +def event_fired(current_state, new_state, event) + MyLogger.info "Event fired #{event.inspect}" +end - 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 +``` - def event_failed(event) - MyLogger.warn "Event failed #{event.inspect}" - end - #### Listing all the available states and events You can easily get a listing of all available states: +```ruby +Order.available_states # Uses the <tt>default</tt> state machine +# => [:pick_line_items, :picking_line_items] +``` - Order.available_states # Uses the <tt>default</tt> state machine - # => [:pick_line_items, :picking_line_items] - Same goes for the available events: +```ruby +Order.available_events +# => [:move_cart] +``` - Order.available_events - # => [:move_cart] - #### Explicitly setting the initial state with the `initial` option +```ruby +state_machine :initial => :closed do + state :open + state :closed +end +``` - state_machine :initial => :closed do - state :open - state :closed - end - ### Configuring a different column name with ActiveRecord To use a different column than `state` to track it's value simply do this: +```ruby +class Product < ActiveRecord::Base + include Transitions - class Product < ActiveRecord::Base - include Transitions + state_machine :attribute_name => :different_column do - state_machine :attribute_name => :different_column do + ... - ... - - end - end + end +end +``` ### Known bugs / limitations * Right now it seems like `transitions` does not play well with `mongoid`. A possible fix had to be rolled back due to other side effects: