state_machine 0.7: Better integrations, more access

Fast and furious. That’s how development’s been on state_machine over the last month (and is also what happens to sequels when you run out of bad ideas). Yesterday, 0.7 was officially added to the state_machine family of releases. With this release comes better integrations, easier access to more state information, and the all new and improved Done Right™. Check out the changelog for a detailed list of changes.
REST and events – Events are auto-fired when a machine’s action is called (or when validations run) based on the value of #{attribute}_event:
purchase = Purchase.create # => #<Purchase id: 1, state: "pending"> purchase.state_event # => nil purchase.update_attributes(:state_event => 'authorize') purchase.state # => "authorized" purchase.state_event # => nil purchase.state_event = 'capture' purchase.valid? # => true purchase.state # => "captured" purchase.save # => true
This means that your create/update controller actions don’t change at all. For an example of the full Rails/Merb vertical slice, see the RESTful examples.
State rollbacks – If an action fails when firing an event, the transition is rolled back:
purchase = Purchase.find(1) purchase.state # => "pending" purchase.amount = nil purchase.valid? # => false # validates_presence_of :amount purchase.authorize # => false purchase.state # => "pending" # Returned "authorized" pre-0.7
ActiveRecord Observers – The ActiveRecord integration kicked it up a notch (bam!) with new Observer hooks:
class PurchaseObserver < ActiveRecord::Observer def before_capture_from_pending_to_captured(p, transition); end def before_capture_from_pending(p, transition); end def before_capture_to_captured(p, transition); end def before_capture(p, transition); end def before_transition_state_from_pending_to_captured(p, transition); end def before_transition_state_to_captured(p, transition); end def before_transition_state_from_pending(p, transition); end def before_transition_state(p, transition); end def before_transition(p, transition); end end
You can define the same types of hooks for after_* as well.
DataMapper Defaults – The DataMapper integration no longer uses transactions by default and properties are now auto-defined if they don’t already exist:
class Purchase include DataMapper::Resource property :id, Integer, :serial => true # property :state, String # Automatically done by state_machine state_machine :use_transactions => true do ... end end
Available events / transitions – The full list of available events / transitions for a state machine can be accessed via two new helpers:
purchase = Purchase.find(1) # => #<Purchase id: 1, state: "pending"> purchase.state_events # => [:authorize, :capture] purchase.state_transitions # => [#<StateMachine::Transition ...>, ...] purchase.authorize purchase.state_events # => [:capture] purchase.state_transitions # => [#<StateMachine::Transition ...>]
This becomes particularly useful when presenting these in a view as a dropdown for users to select from.
Backwards Incompatible! Accessing a specific event’s transition has been renamed from next_#{event}_transition to #{event}_transition:
purchase.authorize_transition # => #<StateMachine::Transition ...> purchase.next_authorize_transition # => NoMethodError: undefined method...
Event arguments – Arguments to event methods can be accessed from transition callbacks:
before_transition :on => :authorize do |purchase, transition| amount = transition.args.first ... end
after_transition callbacks – Backwards incompatible! The result of the action is no longer passed to after_transition callbacks. It can be accessed from the transition instead:
after_transition :on => :authorize do |purchase, transition| saved = transition.result ... end
This affects all integrations as well.
Parallel events – For classes that use multiple state machines, you can fire events off in parallel on an object. This means their before/after callbacks get run at the same time and actions only get called once if they’re the same:
purchase = Purchase.find(1) purchase.state # => "pending" purchase.shipped_at # => nil purchase.fire_events(:capture, :deliver_shipment) # => true purchase.state # => "captured" purchase.shipped_at # => Sun Apr 05 ... # Bang method is similar to events purchase.fire_events!(:capture) # => StateMachine::InvalidTransition: ...
And the rest of the gang…
In addition to the above major changes / additions, there were 6 new minor features and 4 bug fixes in this release.
A special Billy Mays shout-out goes to the folks who helped make this release happen, including:
- Aaron Gibralter
- Paweł Kondzior
- Mikhail Shirkov
Stalk me, but not in a creepy way
As always, be sure to follow the project on GitHub! Happy coding!
Comments
13 Responses to “state_machine 0.7: Better integrations, more access”
-
rebo on April 5th, 2009 5:43 pm
Awesome, looks like the RESTFUL implementation will really make things easier for controllers.
GREAT JOB!
-
Dr Nic on April 5th, 2009 6:32 pm
Is anyone working on integrating SM into restful_authentication gem as a supported state machine system?
-
Russell Jones on April 5th, 2009 8:44 pm
This is a plugin I use more than any other. Thanks for making it.
@Dr Nic … Have you looked at the Authlogic gem? It replaced restful_authentication for me a long time ago. Have a look, its stellar!
-
aaron on April 5th, 2009 10:43 pm
@Dr Nic – Someone e-mailed me about integrating with restful-authentication a few weeks ago, but I don’t think anyone has taken on the task yet.
@rebo & @Russell – Thanks for the feedback
-
David Backeus on April 6th, 2009 4:45 am
From what I see in the examples this makes changing events possible through mass assignment. Does attr_protected :state_event do the trick for protection?
Might be a good idea to point this out in the documentation since states are things that in my experience usually belong to the non mass assignable side of a model.
-
aaron on April 6th, 2009 7:19 am
@David – Great point. Perhaps it should even be protected by default. I’ll look into it. Thanks!
-
Dr Nic on April 6th, 2009 8:07 am
@Russell – investigating Ben Johnson’s *Logic gems is on my todo list.
-
aaron on April 7th, 2009 11:37 pm
@Dr Nic – Had a little extra time tonight and threw something together: http://github.com/obrie/restful-authentication/commit/132d0d947e5af165208df8cf932181ee0043d4f5
-
Joe on May 5th, 2009 2:39 pm
I came here today to feature request what you’ve already done… Saweeeeettt!!!!
-
aaron on May 5th, 2009 10:06 pm
@Joe – Great to hear that you’ll get use out of the new features
-
Gavin on May 6th, 2009 10:12 am
I am just starting to implement a state machine using this plugin, and was trying to graph the states. I have installed graphviz (and libraries) on ubuntu 8.04, and then installed the ruby-graphviz gem, but when I do:
rake state_machine:draw:rails CLASS=Entry
it always returns:
Cannot draw the machine. `gem install ruby-graphviz` and try again.Is there any way to get a more comprehensive error message so I can debug the issue? Is this something that’s been seen before?
Thanks.
-
Gavin on May 6th, 2009 10:34 am
Hehe, never mind. It was missing ruby1.8-dev, and couldn’t find mkmf (a standard ruby lib). Stupid packaging bites me again. Sorry.
-
Andy Watts on August 17th, 2009 2:13 pm
Excellent plugin. I especially like the new callback design.
Thanks
Andy
Feed
