public
Description: Adds support for creating state machines for attributes within a model
Homepage: http://www.pluginaweek.org
Clone URL: git://github.com/pluginaweek/state_machine.git
Add :except_from option for transitions if you want to blacklist states
obrie (author)
Thu Jul 03 16:30:58 -0700 2008
commit  7f1b4d67ea715ca662c69582ceb7bde1d57fb118
tree    14a92d1d9a5e064db28eb59398218c20cb5dc6c3
parent  26d60afa4705981762799dfc36d46794b2bff370
...
1
2
 
3
4
5
...
1
2
3
4
5
6
0
@@ -1,5 +1,6 @@
0
 == master
0
 
0
+* Add :except_from option for transitions if you want to blacklist states
0
 * Add PluginAWeek::StateMachine::Machine#states
0
 * Add PluginAWeek::StateMachine::Event#transitions
0
 * Allow creating transitions with no from state (effectively allowing the transition for *any* from state)
...
36
37
38
 
39
40
41
...
46
47
48
 
49
 
50
51
52
 
53
54
55
56
57
58
59
 
60
61
62
63
64
 
65
66
67
68
 
69
70
71
...
36
37
38
39
40
41
42
...
47
48
49
50
51
52
53
 
 
54
55
 
 
 
 
 
 
56
57
58
59
60
 
61
62
63
64
 
65
66
67
68
0
@@ -36,6 +36,7 @@ module PluginAWeek #:nodoc:
0
       # Configuration options:
0
       # * +to+ - The state that being transitioned to
0
       # * +from+ - A state or array of states that can be transitioned from. If not specified, then the transition can occur for *any* from state
0
+ # * +except_from+ - A state or array of states that *cannot* be transitioned from.
0
       # * +if+ - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :moving?, or :if => Proc.new {|car| car.speed > 60}). The method, proc or string should return or evaluate to a true or false value.
0
       # * +unless+ - Specifies a method, proc or string to call to determine if the transition should not occur (e.g. :unless => :stopped?, or :unless => Proc.new {|car| car.speed <= 60}). The method, proc or string should return or evaluate to a true or false value.
0
       #
0
@@ -46,26 +47,22 @@ module PluginAWeek #:nodoc:
0
       # transition :to => 'parked', :from => %w(first_gear reverse)
0
       # transition :to => 'parked', :from => 'first_gear', :if => :moving?
0
       # transition :to => 'parked', :from => 'first_gear', :unless => :stopped?
0
+ # transition :to => 'parked', :except_from => 'parked'
0
       def transition(options = {})
0
+ # Slice out the callback options
0
         options.symbolize_keys!
0
- options.assert_valid_keys(:to, :from, :if, :unless)
0
- raise ArgumentError, ':to state must be specified' unless options.include?(:to)
0
+ callback_options = {:if => options.delete(:if), :unless => options.delete(:unless)}
0
         
0
- # Get the states involved in the transition
0
- to_state = options.delete(:to)
0
- from_states = Array(options.delete(:from))
0
-
0
- # Create the actual transition that will update records when performed
0
- transition = Transition.new(self, to_state, *from_states)
0
+ transition = Transition.new(self, options)
0
         
0
         # Add the callback to the model. If the callback fails, then the next
0
         # available callback for the event will run until one is successful.
0
         callback = Proc.new {|record, *args| try_transition(transition, false, record, *args)}
0
- owner_class.send("transition_on_#{name}", callback, options)
0
+ owner_class.send("transition_on_#{name}", callback, callback_options)
0
         
0
         # Add the callback! to the model similar to above
0
         callback = Proc.new {|record, *args| try_transition(transition, true, record, *args)}
0
- owner_class.send("transition_bang_on_#{name}", callback, options)
0
+ owner_class.send("transition_bang_on_#{name}", callback, callback_options)
0
         
0
         transitions << transition
0
         transition
...
23
24
25
26
 
27
28
29
 
 
 
 
 
 
 
 
 
 
30
31
32
...
37
38
39
40
 
41
42
43
...
23
24
25
 
26
27
 
 
28
29
30
31
32
33
34
35
36
37
38
39
40
...
45
46
47
 
48
49
50
51
0
@@ -23,10 +23,18 @@ module PluginAWeek #:nodoc:
0
       delegate :machine,
0
                   :to => :event
0
       
0
- def initialize(event, to_state, *from_states) #:nodoc:
0
+ def initialize(event, options) #:nodoc:
0
         @event = event
0
- @to_state = to_state
0
- @from_states = from_states
0
+
0
+ options.assert_valid_keys(:to, :from, :except_from)
0
+ raise ArgumentError, ':to state must be specified' unless options.include?(:to)
0
+
0
+ # Get the states involved in the transition
0
+ @to_state = options[:to]
0
+ @from_states = Array(options[:from] || options[:except_from])
0
+
0
+ # Should we be matching the from states?
0
+ @require_match = !options[:from].nil?
0
       end
0
       
0
       # Whether or not this is a loopback transition (i.e. from and to state are the same)
0
@@ -37,7 +45,7 @@ module PluginAWeek #:nodoc:
0
       # Determines whether or not this transition can be performed on the given
0
       # states
0
       def can_perform_on?(record)
0
- from_states.empty? || from_states.include?(record.send(machine.attribute))
0
+ from_states.empty? || from_states.include?(record.send(machine.attribute)) == @require_match
0
       end
0
       
0
       # Runs the actual transition and any callbacks associated with entering
...
4
5
6
7
 
8
9
10
...
66
67
68
 
 
 
 
 
 
 
 
69
70
71
72
73
74
75
 
76
77
78
...
90
91
92
93
 
94
95
96
...
117
118
119
120
 
121
122
123
...
142
143
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
148
149
150
151
152
153
 
154
155
156
...
170
171
172
173
 
174
175
176
...
190
191
192
193
 
194
195
196
...
323
324
325
326
 
327
328
329
...
425
426
427
428
 
429
430
431
...
4
5
6
 
7
8
9
10
...
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
 
83
84
85
86
...
98
99
100
 
101
102
103
104
...
125
126
127
 
128
129
130
131
...
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
 
193
194
195
196
...
210
211
212
 
213
214
215
216
...
230
231
232
 
233
234
235
236
...
363
364
365
 
366
367
368
369
...
465
466
467
 
468
469
470
471
0
@@ -4,7 +4,7 @@ class TransitionTest < Test::Unit::TestCase
0
   def setup
0
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
0
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
0
- @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on')
0
+ @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on')
0
   end
0
   
0
   def test_should_not_have_any_from_states
0
@@ -66,13 +66,21 @@ class TransitionTest < Test::Unit::TestCase
0
     
0
     assert_raise(ActiveRecord::RecordNotSaved) {@transition.perform!(record)}
0
   end
0
+
0
+ def test_should_raise_exception_if_invalid_option_specified
0
+ assert_raise(ArgumentError) {PluginAWeek::StateMachine::Transition.new(@event, :invalid => true)}
0
+ end
0
+
0
+ def test_should_raise_exception_if_to_option_not_specified
0
+ assert_raise(ArgumentError) {PluginAWeek::StateMachine::Transition.new(@event, :from => 'off')}
0
+ end
0
 end
0
 
0
 class TransitionWithLoopbackTest < Test::Unit::TestCase
0
   def setup
0
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
0
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
0
- @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'on')
0
+ @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'on')
0
   end
0
   
0
   def test_should_be_able_to_perform
0
@@ -90,7 +98,7 @@ class TransitionWithFromStateTest < Test::Unit::TestCase
0
   def setup
0
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
0
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
0
- @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'off')
0
+ @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'off')
0
   end
0
   
0
   def test_should_have_a_from_state
0
@@ -117,7 +125,7 @@ class TransitionWithMultipleFromStatesTest < Test::Unit::TestCase
0
   def setup
0
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
0
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
0
- @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'off', 'on')
0
+ @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => %w(off on))
0
   end
0
   
0
   def test_should_have_multiple_from_states
0
@@ -142,15 +150,47 @@ class TransitionWithMultipleFromStatesTest < Test::Unit::TestCase
0
     assert @transition.perform(record)
0
     
0
     record = new_switch(:state => 'on')
0
+ assert @transition.perform(record)
0
+ end
0
+end
0
+
0
+class TransitionWithMismatchedFromStatesRequiredTest < Test::Unit::TestCase
0
+ def setup
0
+ @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
0
+ @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
0
+ @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :except_from => 'on')
0
+ end
0
+
0
+ def test_should_have_a_from_state
0
+ assert_equal ['on'], @transition.from_states
0
+ end
0
+
0
+ def test_should_be_able_to_perform_if_record_state_is_not_from_state
0
+ record = new_switch(:state => 'off')
0
     assert @transition.can_perform_on?(record)
0
   end
0
+
0
+ def test_should_not_be_able_to_perform_if_record_state_is_from_state
0
+ record = new_switch(:state => 'on')
0
+ assert !@transition.can_perform_on?(record)
0
+ end
0
+
0
+ def test_should_perform_for_valid_from_state
0
+ record = new_switch(:state => 'off')
0
+ assert @transition.perform(record)
0
+ end
0
+
0
+ def test_should_not_perform_for_invalid_from_state
0
+ record = new_switch(:state => 'on')
0
+ assert !@transition.can_perform_on?(record)
0
+ end
0
 end
0
 
0
 class TransitionAfterBeingPerformedTest < Test::Unit::TestCase
0
   def setup
0
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
0
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
0
- @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'off')
0
+ @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'off')
0
     
0
     @record = create_switch(:state => 'off')
0
     @transition.perform(@record)
0
@@ -170,7 +210,7 @@ class TransitionWithLoopbackAfterBeingPerformedTest < Test::Unit::TestCase
0
   def setup
0
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
0
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
0
- @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'on')
0
+ @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'on')
0
     
0
     @record = create_switch(:state => 'on')
0
     @transition.perform(@record)
0
@@ -190,7 +230,7 @@ class TransitionWithCallbacksTest < Test::Unit::TestCase
0
   def setup
0
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
0
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
0
- @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'off')
0
+ @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'off')
0
     @record = create_switch(:state => 'off')
0
     
0
     Switch.define_callbacks :before_exit_state_off, :before_enter_state_on, :after_exit_state_off, :after_enter_state_on
0
@@ -323,7 +363,7 @@ class TransitionWithoutFromStateAndCallbacksTest < Test::Unit::TestCase
0
   def setup
0
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
0
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
0
- @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on')
0
+ @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on')
0
     @record = create_switch(:state => 'off')
0
     
0
     Switch.define_callbacks :before_exit_state_off, :before_enter_state_on, :after_exit_state_off, :after_enter_state_on
0
@@ -425,7 +465,7 @@ class TransitionWithLoopbackAndCallbacksTest < Test::Unit::TestCase
0
   def setup
0
     @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
0
     @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
0
- @transition = PluginAWeek::StateMachine::Transition.new(@event, 'on', 'on')
0
+ @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'on')
0
     @record = create_switch(:state => 'on')
0
     
0
     Switch.define_callbacks :before_exit_state_off, :before_enter_state_on, :after_exit_state_off, :after_enter_state_on

Comments

    No one has commented yet.