<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,5 +1,6 @@
 == master
 
+* Add :except_from option for transitions if you want to blacklist states
 * Add PluginAWeek::StateMachine::Machine#states
 * Add PluginAWeek::StateMachine::Event#transitions
 * Allow creating transitions with no from state (effectively allowing the transition for *any* from state)</diff>
      <filename>CHANGELOG.rdoc</filename>
    </modified>
    <modified>
      <diff>@@ -36,6 +36,7 @@ module PluginAWeek #:nodoc:
       # Configuration options:
       # * +to+ - The state that being transitioned to
       # * +from+ - A state or array of states that can be transitioned from. If not specified, then the transition can occur for *any* from state
+      # * +except_from+ - A state or array of states that *cannot* be transitioned from.
       # * +if+ - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if =&gt; :moving?, or :if =&gt; Proc.new {|car| car.speed &gt; 60}). The method, proc or string should return or evaluate to a true or false value.
       # * +unless+ - Specifies a method, proc or string to call to determine if the transition should not occur (e.g. :unless =&gt; :stopped?, or :unless =&gt; Proc.new {|car| car.speed &lt;= 60}). The method, proc or string should return or evaluate to a true or false value.
       # 
@@ -46,26 +47,22 @@ module PluginAWeek #:nodoc:
       #   transition :to =&gt; 'parked', :from =&gt; %w(first_gear reverse)
       #   transition :to =&gt; 'parked', :from =&gt; 'first_gear', :if =&gt; :moving?
       #   transition :to =&gt; 'parked', :from =&gt; 'first_gear', :unless =&gt; :stopped?
+      #   transition :to =&gt; 'parked', :except_from =&gt; 'parked'
       def transition(options = {})
+        # Slice out the callback options
         options.symbolize_keys!
-        options.assert_valid_keys(:to, :from, :if, :unless)
-        raise ArgumentError, ':to state must be specified' unless options.include?(:to)
+        callback_options = {:if =&gt; options.delete(:if), :unless =&gt; options.delete(:unless)}
         
-        # Get the states involved in the transition
-        to_state = options.delete(:to)
-        from_states = Array(options.delete(:from))
-        
-        # Create the actual transition that will update records when performed
-        transition = Transition.new(self, to_state, *from_states)
+        transition = Transition.new(self, options)
         
         # Add the callback to the model. If the callback fails, then the next
         # available callback for the event will run until one is successful.
         callback = Proc.new {|record, *args| try_transition(transition, false, record, *args)}
-        owner_class.send(&quot;transition_on_#{name}&quot;, callback, options)
+        owner_class.send(&quot;transition_on_#{name}&quot;, callback, callback_options)
         
         # Add the callback! to the model similar to above
         callback = Proc.new {|record, *args| try_transition(transition, true, record, *args)}
-        owner_class.send(&quot;transition_bang_on_#{name}&quot;, callback, options)
+        owner_class.send(&quot;transition_bang_on_#{name}&quot;, callback, callback_options)
         
         transitions &lt;&lt; transition
         transition</diff>
      <filename>lib/state_machine/event.rb</filename>
    </modified>
    <modified>
      <diff>@@ -23,10 +23,18 @@ module PluginAWeek #:nodoc:
       delegate  :machine,
                   :to =&gt; :event
       
-      def initialize(event, to_state, *from_states) #:nodoc:
+      def initialize(event, options) #:nodoc:
         @event = event
-        @to_state = to_state
-        @from_states = from_states
+        
+        options.assert_valid_keys(:to, :from, :except_from)
+        raise ArgumentError, ':to state must be specified' unless options.include?(:to)
+        
+        # Get the states involved in the transition
+        @to_state = options[:to]
+        @from_states = Array(options[:from] || options[:except_from])
+        
+        # Should we be matching the from states?
+        @require_match = !options[:from].nil?
       end
       
       # Whether or not this is a loopback transition (i.e. from and to state are the same)
@@ -37,7 +45,7 @@ module PluginAWeek #:nodoc:
       # Determines whether or not this transition can be performed on the given
       # states
       def can_perform_on?(record)
-        from_states.empty? || from_states.include?(record.send(machine.attribute))
+        from_states.empty? || from_states.include?(record.send(machine.attribute)) == @require_match
       end
       
       # Runs the actual transition and any callbacks associated with entering</diff>
      <filename>lib/state_machine/transition.rb</filename>
    </modified>
    <modified>
      <diff>@@ -4,7 +4,7 @@ class TransitionTest &lt; Test::Unit::TestCase
   def setup
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial =&gt; 'off')
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
-    @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on')
+    @transition = PluginAWeek::StateMachine::Transition.new(@event, :to =&gt; 'on')
   end
   
   def test_should_not_have_any_from_states
@@ -66,13 +66,21 @@ class TransitionTest &lt; Test::Unit::TestCase
     
     assert_raise(ActiveRecord::RecordNotSaved) {@transition.perform!(record)}
   end
+  
+  def test_should_raise_exception_if_invalid_option_specified
+    assert_raise(ArgumentError) {PluginAWeek::StateMachine::Transition.new(@event, :invalid =&gt; true)}
+  end
+  
+  def test_should_raise_exception_if_to_option_not_specified
+    assert_raise(ArgumentError) {PluginAWeek::StateMachine::Transition.new(@event, :from =&gt; 'off')}
+  end
 end
 
 class TransitionWithLoopbackTest &lt; Test::Unit::TestCase
   def setup
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial =&gt; 'off')
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
-    @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'on')
+    @transition = PluginAWeek::StateMachine::Transition.new(@event, :to =&gt; 'on', :from =&gt; 'on')
   end
   
   def test_should_be_able_to_perform
@@ -90,7 +98,7 @@ class TransitionWithFromStateTest &lt; Test::Unit::TestCase
   def setup
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial =&gt; 'off')
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
-    @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'off')
+    @transition = PluginAWeek::StateMachine::Transition.new(@event, :to =&gt; 'on', :from =&gt; 'off')
   end
   
   def test_should_have_a_from_state
@@ -117,7 +125,7 @@ class TransitionWithMultipleFromStatesTest &lt; Test::Unit::TestCase
   def setup
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial =&gt; 'off')
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
-    @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'off', 'on')
+    @transition = PluginAWeek::StateMachine::Transition.new(@event, :to =&gt; 'on', :from =&gt; %w(off on))
   end
   
   def test_should_have_multiple_from_states
@@ -142,15 +150,47 @@ class TransitionWithMultipleFromStatesTest &lt; Test::Unit::TestCase
     assert @transition.perform(record)
     
     record = new_switch(:state =&gt; 'on')
+    assert @transition.perform(record)
+  end
+end
+
+class TransitionWithMismatchedFromStatesRequiredTest &lt; Test::Unit::TestCase
+  def setup
+    @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial =&gt; 'off')
+    @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
+    @transition = PluginAWeek::StateMachine::Transition.new(@event, :to =&gt; 'on', :except_from =&gt; 'on')
+  end
+  
+  def test_should_have_a_from_state
+    assert_equal ['on'], @transition.from_states
+  end
+  
+  def test_should_be_able_to_perform_if_record_state_is_not_from_state
+    record = new_switch(:state =&gt; 'off')
     assert @transition.can_perform_on?(record)
   end
+  
+  def test_should_not_be_able_to_perform_if_record_state_is_from_state
+    record = new_switch(:state =&gt; 'on')
+    assert !@transition.can_perform_on?(record)
+  end
+  
+  def test_should_perform_for_valid_from_state
+    record = new_switch(:state =&gt; 'off')
+    assert @transition.perform(record)
+  end
+  
+  def test_should_not_perform_for_invalid_from_state
+    record = new_switch(:state =&gt; 'on')
+    assert !@transition.can_perform_on?(record)
+  end
 end
 
 class TransitionAfterBeingPerformedTest &lt; Test::Unit::TestCase
   def setup
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial =&gt; 'off')
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
-    @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'off')
+    @transition = PluginAWeek::StateMachine::Transition.new(@event, :to =&gt; 'on', :from =&gt; 'off')
     
     @record = create_switch(:state =&gt; 'off')
     @transition.perform(@record)
@@ -170,7 +210,7 @@ class TransitionWithLoopbackAfterBeingPerformedTest &lt; Test::Unit::TestCase
   def setup
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial =&gt; 'off')
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
-    @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'on')
+    @transition = PluginAWeek::StateMachine::Transition.new(@event, :to =&gt; 'on', :from =&gt; 'on')
     
     @record = create_switch(:state =&gt; 'on')
     @transition.perform(@record)
@@ -190,7 +230,7 @@ class TransitionWithCallbacksTest &lt; Test::Unit::TestCase
   def setup
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial =&gt; 'off')
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
-    @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'off')
+    @transition = PluginAWeek::StateMachine::Transition.new(@event, :to =&gt; 'on', :from =&gt; 'off')
     @record = create_switch(:state =&gt; 'off')
     
     Switch.define_callbacks :before_exit_state_off, :before_enter_state_on, :after_exit_state_off, :after_enter_state_on
@@ -323,7 +363,7 @@ class TransitionWithoutFromStateAndCallbacksTest &lt; Test::Unit::TestCase
   def setup
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial =&gt; 'off')
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
-    @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on')
+    @transition = PluginAWeek::StateMachine::Transition.new(@event, :to =&gt; 'on')
     @record = create_switch(:state =&gt; 'off')
     
     Switch.define_callbacks :before_exit_state_off, :before_enter_state_on, :after_exit_state_off, :after_enter_state_on
@@ -425,7 +465,7 @@ class TransitionWithLoopbackAndCallbacksTest &lt; Test::Unit::TestCase
   def setup
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial =&gt; 'off')
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
-    @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'on')
+    @transition = PluginAWeek::StateMachine::Transition.new(@event, :to =&gt; 'on', :from =&gt; 'on')
     @record = create_switch(:state =&gt; 'on')
     
     Switch.define_callbacks :before_exit_state_off, :before_enter_state_on, :after_exit_state_off, :after_enter_state_on</diff>
      <filename>test/unit/transition_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>26d60afa4705981762799dfc36d46794b2bff370</id>
    </parent>
  </parents>
  <author>
    <name>Aaron Pfeifer</name>
    <email>aaron.pfeifer@gmail.com</email>
  </author>
  <url>http://github.com/pluginaweek/state_machine/commit/7f1b4d67ea715ca662c69582ceb7bde1d57fb118</url>
  <id>7f1b4d67ea715ca662c69582ceb7bde1d57fb118</id>
  <committed-date>2008-07-03T16:30:58-07:00</committed-date>
  <authored-date>2008-07-03T16:30:58-07:00</authored-date>
  <message>Add :except_from option for transitions if you want to blacklist states</message>
  <tree>14a92d1d9a5e064db28eb59398218c20cb5dc6c3</tree>
  <committer>
    <name>Aaron Pfeifer</name>
    <email>aaron.pfeifer@gmail.com</email>
  </committer>
</commit>
