state_machine 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,58 +17,60 @@ module StateMachine
17
17
  # The condition that must *not* be met on an object
18
18
  attr_reader :unless_condition
19
19
 
20
- # The requirement for verifying the event being guarded (includes :on and
21
- # :except_on).
20
+ # The requirement for verifying the event being guarded
22
21
  attr_reader :event_requirement
23
22
 
24
- # The requirement for verifying the states being guarded (includes :from,
25
- # :to, :except_from, and :except_to). All options map to
26
- # either nil (if not specified) or an array of state names.
27
- attr_reader :state_requirement
23
+ # One or more requrirements for verifying the states being guarded. All
24
+ # requirements contain a mapping of {:from => matcher, :to => matcher}.
25
+ attr_reader :state_requirements
28
26
 
29
27
  # A list of all of the states known to this guard. This will pull states
30
- # from the following options in +state_requirements+ (in the same order):
28
+ # from the following options (in the same order):
31
29
  # * +from+ / +except_from+
32
30
  # * +to+ / +except_to+
33
31
  attr_reader :known_states
34
32
 
35
33
  # Creates a new guard
36
34
  def initialize(options = {}) #:nodoc:
37
- assert_valid_keys(options, :from, :to, :on, :except_from, :except_to, :except_on, :if, :unless)
38
-
39
35
  # Build conditionals
40
- assert_exclusive_keys(options, :if, :unless)
41
36
  @if_condition = options.delete(:if)
42
37
  @unless_condition = options.delete(:unless)
43
38
 
44
- # Build event/state requirements
39
+ # Build event requirement
45
40
  @event_requirement = build_matcher(options, :on, :except_on)
46
- @state_requirement = {:from => build_matcher(options, :from, :except_from), :to => build_matcher(options, :to, :except_to)}
41
+
42
+ if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on]).empty?
43
+ # Explicit from/to requirements specified
44
+ @state_requirements = [{:from => build_matcher(options, :from, :except_from), :to => build_matcher(options, :to, :except_to)}]
45
+ else
46
+ # Separate out the event requirement
47
+ options.delete(:on)
48
+ options.delete(:except_on)
49
+
50
+ # Implicit from/to requirements specified
51
+ @state_requirements = options.collect do |from, to|
52
+ from = WhitelistMatcher.new(from) unless from.is_a?(Matcher)
53
+ to = WhitelistMatcher.new(to) unless to.is_a?(Matcher)
54
+ {:from => from, :to => to}
55
+ end
56
+ end
47
57
 
48
58
  # Track known states. The order that requirements are iterated is based
49
59
  # on the priority in which tracked states should be added.
50
60
  @known_states = []
51
- [:from, :to].each {|option| @known_states |= @state_requirement[option].values}
61
+ @state_requirements.each do |state_requirement|
62
+ [:from, :to].each {|option| @known_states |= state_requirement[option].values}
63
+ end
52
64
  end
53
65
 
54
- # Attempts to match the given object / query against the set of requirements
66
+ # Determines whether the given object / query matches the requirements
55
67
  # configured for this guard. In addition to matching the event, from state,
56
68
  # and to state, this will also check whether the configured :if/:unless
57
69
  # conditions pass on the given object.
58
70
  #
59
- # This will return true or false depending on whether a match is found.
60
- #
61
- # Query options:
62
- # * <tt>:from</tt> - One or more states being transitioned from. If none
63
- # are specified, then this will always match.
64
- # * <tt>:to</tt> - One or more states being transitioned to. If none are
65
- # specified, then this will always match.
66
- # * <tt>:on</tt> - One or more events that fired the transition. If none
67
- # are specified, then this will always match.
68
- #
69
71
  # == Examples
70
72
  #
71
- # guard = StateMachine::Guard.new(:from => [nil, :parked], :to => :idling, :on => :ignite)
73
+ # guard = StateMachine::Guard.new(:parked => :idling, :on => :ignite)
72
74
  #
73
75
  # # Successful
74
76
  # guard.matches?(object, :on => :ignite) # => true
@@ -85,7 +87,36 @@ module StateMachine
85
87
  # guard.matches?(object, :from => :parked, :to => :first_gear) # => false
86
88
  # guard.matches?(object, :on => :park, :from => :parked, :to => :idling) # => false
87
89
  def matches?(object, query = {})
88
- matches_query?(query) && matches_conditions?(object)
90
+ !match(object, query).nil?
91
+ end
92
+
93
+ # Attempts to match the given object / query against the set of requirements
94
+ # configured for this guard. In addition to matching the event, from state,
95
+ # and to state, this will also check whether the configured :if/:unless
96
+ # conditions pass on the given object.
97
+ #
98
+ # If a match is found, then the event/state requirements that the query
99
+ # passed successfully will be returned. Otherwise, nil is returned if there
100
+ # was no match.
101
+ #
102
+ # Query options:
103
+ # * <tt>:from</tt> - One or more states being transitioned from. If none
104
+ # are specified, then this will always match.
105
+ # * <tt>:to</tt> - One or more states being transitioned to. If none are
106
+ # specified, then this will always match.
107
+ # * <tt>:on</tt> - One or more events that fired the transition. If none
108
+ # are specified, then this will always match.
109
+ #
110
+ # == Examples
111
+ #
112
+ # guard = StateMachine::Guard.new(:parked => :idling, :on => :ignite)
113
+ #
114
+ # guard.match(object, :on => :ignite) # => {:to => ..., :from => ..., :on => ...}
115
+ # guard.match(object, :on => :park) # => nil
116
+ def match(object, query = {})
117
+ if (match = match_query(query)) && matches_conditions?(object)
118
+ match
119
+ end
89
120
  end
90
121
 
91
122
  # Draws a representation of this guard on the given graph. This will draw
@@ -108,26 +139,28 @@ module StateMachine
108
139
  #
109
140
  # The collection of edges generated on the graph will be returned.
110
141
  def draw(graph, event, valid_states)
111
- edges = []
112
-
113
- # From states determined based on the known valid states
114
- from_states = state_requirement[:from].filter(valid_states)
115
-
116
- # If a to state is not specified, then it's a loopback and each from
117
- # state maps back to itself
118
- if state_requirement[:to].values.any?
119
- to_state = state_requirement[:to].values.first
120
- loopback = false
121
- else
122
- loopback = true
123
- end
124
-
125
- # Generate an edge between each from and to state
126
- from_states.each do |from_state|
127
- edges << graph.add_edge(from_state.to_s, (loopback ? from_state : to_state).to_s, :label => event.to_s)
142
+ state_requirements.inject([]) do |edges, state_requirement|
143
+ # From states determined based on the known valid states
144
+ from_states = state_requirement[:from].filter(valid_states)
145
+
146
+ # If a to state is not specified, then it's a loopback and each from
147
+ # state maps back to itself
148
+ if state_requirement[:to].values.empty?
149
+ loopback = true
150
+ else
151
+ to_state = state_requirement[:to].values.first
152
+ to_state = to_state ? to_state.to_s : 'nil'
153
+ loopback = false
154
+ end
155
+
156
+ # Generate an edge between each from and to state
157
+ from_states.each do |from_state|
158
+ from_state = from_state ? from_state.to_s : 'nil'
159
+ edges << graph.add_edge(from_state, loopback ? from_state : to_state, :label => event.to_s)
160
+ end
161
+
162
+ edges
128
163
  end
129
-
130
- edges
131
164
  end
132
165
 
133
166
  protected
@@ -149,20 +182,25 @@ module StateMachine
149
182
  # Verifies that all configured requirements (event and state) match the
150
183
  # given query. If a match is return, then a hash containing the
151
184
  # event/state requirements that passed will be returned; otherwise, nil.
152
- def matches_query?(query)
185
+ def match_query(query)
153
186
  query ||= {}
154
- matches_event?(query) && matches_states?(query)
187
+
188
+ if match_event(query) && (state_requirement = match_states(query))
189
+ state_requirement.merge(:on => event_requirement)
190
+ end
155
191
  end
156
192
 
157
193
  # Verifies that the event requirement matches the given query
158
- def matches_event?(query)
194
+ def match_event(query)
159
195
  matches_requirement?(query, :on, event_requirement)
160
196
  end
161
197
 
162
198
  # Verifies that the state requirements match the given query. If a
163
199
  # matching requirement is found, then it is returned.
164
- def matches_states?(query)
165
- [:from, :to].all? {|option| matches_requirement?(query, option, state_requirement[option])}
200
+ def match_states(query)
201
+ state_requirements.detect do |state_requirement|
202
+ [:from, :to].all? {|option| matches_requirement?(query, option, state_requirement[option])}
203
+ end
166
204
  end
167
205
 
168
206
  # Verifies that an option in the given query matches the values required
@@ -174,13 +212,8 @@ module StateMachine
174
212
  # Verifies that the conditionals for this guard evaluate to true for the
175
213
  # given object
176
214
  def matches_conditions?(object)
177
- if if_condition
178
- evaluate_method(object, if_condition)
179
- elsif unless_condition
180
- !evaluate_method(object, unless_condition)
181
- else
182
- true
183
- end
215
+ Array(if_condition).all? {|condition| evaluate_method(object, condition)} &&
216
+ !Array(unless_condition).any? {|condition| evaluate_method(object, condition)}
184
217
  end
185
218
  end
186
219
  end
@@ -10,7 +10,7 @@ module StateMachine
10
10
  # class Vehicle < ActiveRecord::Base
11
11
  # state_machine :initial => :parked do
12
12
  # event :ignite do
13
- # transition :to => :idling, :from => :parked
13
+ # transition :parked => :idling
14
14
  # end
15
15
  # end
16
16
  # end
@@ -95,7 +95,7 @@ module StateMachine
95
95
  #
96
96
  # class Vehicle < ActiveRecord::Base
97
97
  # state_machine :initial => :parked do
98
- # before_transition :to => :idling do |vehicle|
98
+ # before_transition any => :idling do |vehicle|
99
99
  # vehicle.put_on_seatbelt
100
100
  # end
101
101
  #
@@ -104,7 +104,7 @@ module StateMachine
104
104
  # end
105
105
  #
106
106
  # event :ignite do
107
- # transition :to => :idling, :from => :parked
107
+ # transition :parked => :idling
108
108
  # end
109
109
  # end
110
110
  #
@@ -16,7 +16,7 @@ module StateMachine
16
16
  #
17
17
  # state_machine :initial => :parked do
18
18
  # event :ignite do
19
- # transition :to => :idling, :from => :parked
19
+ # transition :parked => :idling
20
20
  # end
21
21
  # end
22
22
  # end
@@ -122,7 +122,7 @@ module StateMachine
122
122
  # property :state, String
123
123
  #
124
124
  # state_machine :initial => :parked do
125
- # before_transition :to => :idling do
125
+ # before_transition any => :idling do
126
126
  # put_on_seatbelt
127
127
  # end
128
128
  #
@@ -131,7 +131,7 @@ module StateMachine
131
131
  # end
132
132
  #
133
133
  # event :ignite do
134
- # transition :to => :idling, :from => :parked
134
+ # transition :parked => :idling
135
135
  # end
136
136
  # end
137
137
  #
@@ -37,6 +37,8 @@ module StateMachine
37
37
  # If dm-observer is not available, then this feature will be skipped.
38
38
  #
39
39
  module Observer
40
+ include MatcherHelpers
41
+
40
42
  # Creates a callback that will be invoked *before* a transition is
41
43
  # performed, so long as the given configuration options match the
42
44
  # transition. Each part of the transition (event, to state, from state)
@@ -55,7 +57,7 @@ module StateMachine
55
57
  #
56
58
  # state_machine :initial => :parked do
57
59
  # event :ignite do
58
- # transition :to => :idling, :from => :parked
60
+ # transition :parked => :idling
59
61
  # end
60
62
  # end
61
63
  # end
@@ -70,12 +72,12 @@ module StateMachine
70
72
  # end
71
73
  #
72
74
  # # Target all state machines
73
- # before_transition :to => :idling, :from => :parked, :on => :ignite do
75
+ # before_transition :parked => :idling, :on => :ignite do
74
76
  # # put on seatbelt
75
77
  # end
76
78
  #
77
79
  # # Target a specific state machine
78
- # before_transition :state, :to => :idling do
80
+ # before_transition :state, any => :idling do
79
81
  # # put on seatbelt
80
82
  # end
81
83
  #
@@ -111,7 +113,7 @@ module StateMachine
111
113
  #
112
114
  # state_machine :initial => :parked do
113
115
  # event :ignite do
114
- # transition :to => :idling, :from => :parked
116
+ # transition :parked => :idling
115
117
  # end
116
118
  # end
117
119
  # end
@@ -126,12 +128,12 @@ module StateMachine
126
128
  # end
127
129
  #
128
130
  # # Target all state machines
129
- # after_transition :to => :idling, :from => :parked, :on => :ignite do
131
+ # after_transition :parked => :idling, :on => :ignite do
130
132
  # # put on seatbelt
131
133
  # end
132
134
  #
133
135
  # # Target a specific state machine
134
- # after_transition :state, :to => :idling do
136
+ # after_transition :state, any => :idling do
135
137
  # # put on seatbelt
136
138
  # end
137
139
  #
@@ -10,7 +10,7 @@ module StateMachine
10
10
  # class Vehicle < Sequel::Model
11
11
  # state_machine :initial => :parked do
12
12
  # event :ignite do
13
- # transition :to => :idling, :from => :parked
13
+ # transition :parked => :idling
14
14
  # end
15
15
  # end
16
16
  # end
@@ -101,7 +101,7 @@ module StateMachine
101
101
  #
102
102
  # class Vehicle < Sequel::Model
103
103
  # state_machine :initial => :parked do
104
- # before_transition :to => :idling do
104
+ # before_transition any => :idling do
105
105
  # put_on_seatbelt
106
106
  # end
107
107
  #
@@ -110,7 +110,7 @@ module StateMachine
110
110
  # end
111
111
  #
112
112
  # event :ignite do
113
- # transition :to => :idling, :from => :parked
113
+ # transition :parked => :idling
114
114
  # end
115
115
  # end
116
116
  #
@@ -7,6 +7,7 @@ require 'state_machine/event'
7
7
  require 'state_machine/callback'
8
8
  require 'state_machine/node_collection'
9
9
  require 'state_machine/state_collection'
10
+ require 'state_machine/matcher_helpers'
10
11
 
11
12
  module StateMachine
12
13
  # Represents a state machine for a particular attribute. State machines
@@ -44,7 +45,7 @@ module StateMachine
44
45
  #
45
46
  # class Vehicle
46
47
  # state_machine, :initial => :parked do
47
- # before_transition :to => :idling, :do => lambda {|vehicle| throw :halt}
48
+ # before_transition any => :idling, :do => lambda {|vehicle| throw :halt}
48
49
  # ...
49
50
  # end
50
51
  # end
@@ -64,7 +65,7 @@ module StateMachine
64
65
  # class Vehicle
65
66
  # state_machine do
66
67
  # event :park do
67
- # transition :to => :parked, :from => :idling
68
+ # transition :idling => :parked
68
69
  # end
69
70
  # ...
70
71
  # end
@@ -115,8 +116,8 @@ module StateMachine
115
116
  # end
116
117
  # end
117
118
  #
118
- # Additional observer-like behavior may be exposed by the various
119
- # integrations available. See below for more information.
119
+ # Additional observer-like behavior may be exposed by the various integrations
120
+ # available. See below for more information.
120
121
  #
121
122
  # == Integrations
122
123
  #
@@ -138,6 +139,7 @@ module StateMachine
138
139
  # constants defined under the StateMachine::Integrations namespace.
139
140
  class Machine
140
141
  include Assertions
142
+ include MatcherHelpers
141
143
 
142
144
  class << self
143
145
  # Attempts to find or create a state machine for the given class. For
@@ -369,7 +371,7 @@ module StateMachine
369
371
  # class Vehicle
370
372
  # state_machine :initial => :parked do
371
373
  # event :ignite do
372
- # transition :to => :idling, :from => :parked
374
+ # transition :parked => :idling
373
375
  # end
374
376
  # end
375
377
  # end
@@ -387,7 +389,7 @@ module StateMachine
387
389
  # class Vehicle
388
390
  # state_machine :initial => :parked do
389
391
  # event :ignite do
390
- # transition :to => :idling, :from => :parked
392
+ # transition :parked => :idling
391
393
  # end
392
394
  #
393
395
  # state :idling, :value => 'IDLING'
@@ -405,7 +407,7 @@ module StateMachine
405
407
  # class Vehicle < ActiveRecord::Base
406
408
  # state_machine :state_id, :initial => :parked do
407
409
  # event :ignite do
408
- # transition :to => :idling, :from => :parked
410
+ # transition :parked => :idling
409
411
  # end
410
412
  #
411
413
  # states.each {|state| self.state(state.name, :value => VehicleState.find_by_name(state.name.to_s).id)}
@@ -424,11 +426,11 @@ module StateMachine
424
426
  # class Vehicle
425
427
  # state_machine :purchased_at, :initial => :available do
426
428
  # event :purchase do
427
- # transition :to => :purchased
429
+ # transition all => :purchased
428
430
  # end
429
431
  #
430
432
  # event :restock do
431
- # transition :to => :available
433
+ # transition all => :available
432
434
  # end
433
435
  #
434
436
  # state :available, :value => nil
@@ -464,7 +466,7 @@ module StateMachine
464
466
  #
465
467
  # state_machine :initial => :parked do
466
468
  # event :ignite do
467
- # transition :to => :idling, :from => :parked
469
+ # transition :parked => :idling
468
470
  # end
469
471
  #
470
472
  # state :parked do
@@ -540,6 +542,39 @@ module StateMachine
540
542
  # vehicle = Vehicle.new
541
543
  # vehicle.state = 'backing_up'
542
544
  # vehicle.speed # => NoMethodError: undefined method 'speed' for #<Vehicle:0xb7d296ac> in state "backing_up"
545
+ #
546
+ # == State-aware class methods
547
+ #
548
+ # In addition to defining scopes for instance methods that are state-aware,
549
+ # the same can be done for certain types of class methods.
550
+ #
551
+ # Some libraries have support for class-level methods that only run certain
552
+ # behaviors based on a conditions hash passed in. For example:
553
+ #
554
+ # class Vehicle < ActiveRecord::Base
555
+ # state_machine do
556
+ # ...
557
+ # state :first_gear, :second_gear, :third_gear do
558
+ # validates_presence_of :speed
559
+ # validates_inclusion_of :speed, :in => 0..25, :if => :in_school_zone?
560
+ # end
561
+ # end
562
+ # end
563
+ #
564
+ # In the above ActiveRecord model, two validations have been defined which
565
+ # will *only* run when the Vehicle object is in one of the three states:
566
+ # +first_gear+, +second_gear+, or +third_gear. Notice, also, that if/unless
567
+ # conditions can continue to be used.
568
+ #
569
+ # This functionality is not library-specific and can work for any class-level
570
+ # method that is defined like so:
571
+ #
572
+ # def validates_presence_of(args, options = {})
573
+ # ...
574
+ # end
575
+ #
576
+ # The minimum requirement is that the last argument in the method be an
577
+ # options hash which contains at least <tt>:if</tt> condition support.
543
578
  def state(*names, &block)
544
579
  options = names.last.is_a?(Hash) ? names.pop : {}
545
580
  assert_valid_keys(options, :value, :if)
@@ -616,6 +651,9 @@ module StateMachine
616
651
  # Defines one or more events for the machine and the transitions that can
617
652
  # be performed when those events are run.
618
653
  #
654
+ # This method is also aliased as +on+ for improved compatibility with
655
+ # using a domain-specific language.
656
+ #
619
657
  # == Instance methods
620
658
  #
621
659
  # The following instance methods are generated when a new event is defined
@@ -643,11 +681,11 @@ module StateMachine
643
681
  # transitions that can happen as a result of that event. For example,
644
682
  #
645
683
  # event :park, :stop do
646
- # transition :to => :parked, :from => :idling
684
+ # transition :idling => :parked
647
685
  # end
648
686
  #
649
687
  # event :first_gear do
650
- # transition :to => :first_gear, :from => :parked, :if => :seatbelt_on?
688
+ # transition :parked => :first_gear, :if => :seatbelt_on?
651
689
  # end
652
690
  #
653
691
  # See StateMachine::Event#transition for more information on
@@ -664,7 +702,7 @@ module StateMachine
664
702
  #
665
703
  # state_machine do
666
704
  # event :park do
667
- # transition :to => :parked, :from => Vehicle.safe_states
705
+ # transition Vehicle.safe_states => :parked
668
706
  # end
669
707
  # end
670
708
  # end
@@ -675,15 +713,15 @@ module StateMachine
675
713
  # state_machine do
676
714
  # # The park, stop, and halt events will all share the given transitions
677
715
  # event :park, :stop, :halt do
678
- # transition :to => :parked, :from => [:idling, :backing_up]
716
+ # transition [:idling, :backing_up] => :parked
679
717
  # end
680
718
  #
681
719
  # event :stop do
682
- # transition :to => :idling, :from => :first_gear
720
+ # transition :first_gear => :idling
683
721
  # end
684
722
  #
685
723
  # event :ignite do
686
- # transition :to => :idling, :from => :parked
724
+ # transition :parked => :idling
687
725
  # end
688
726
  # end
689
727
  # end
@@ -703,108 +741,71 @@ module StateMachine
703
741
 
704
742
  events.length == 1 ? events.first : events
705
743
  end
744
+ alias_method :on, :event
706
745
 
707
746
  # Creates a callback that will be invoked *before* a transition is
708
- # performed so long as the given configuration options match the transition.
709
- # Each part of the transition (event, to state, from state) must match in
710
- # order for the callback to get invoked.
711
- #
712
- # Configuration options:
713
- # * <tt>:from</tt> - One or more states being transitioned from. If none
714
- # are specified, then all states will match.
715
- # * <tt>:to</tt> - One or more states being transitioned to. If none are
716
- # specified, then all states will match.
717
- # * <tt>:on</tt> - One or more events that fired the transition. If none
718
- # are specified, then all events will match.
719
- # * <tt>:except_from</tt> - One or more states *not* being transitioned from
720
- # * <tt>:except_to</tt> - One more states *not* being transitioned to
721
- # * <tt>:except_on</tt> - One or more events that *did not* fire the transition
722
- # * <tt>:do</tt> - The callback to invoke when a transition matches. This
723
- # can be a method, proc or string.
724
- # * <tt>:if</tt> - A method, proc or string to call to determine if the
725
- # callback should occur (e.g. :if => :allow_callbacks, or
726
- # :if => lambda {|user| user.signup_step > 2}). The method, proc or string
727
- # should return or evaluate to a true or false value.
728
- # * <tt>:unless</tt> - A method, proc or string to call to determine if the
729
- # callback should not occur (e.g. :unless => :skip_callbacks, or
730
- # :unless => lambda {|user| user.signup_step <= 2}). The method, proc or
731
- # string should return or evaluate to a true or false value.
732
- #
733
- # The +except+ group of options (+except_to+, +exception_from+, and
734
- # +except_on+) acts as the +unless+ equivalent of their counterparts (+to+,
735
- # +from+, and +on+, respectively)
747
+ # performed so long as the given requirements match the transition.
736
748
  #
737
749
  # == The callback
738
750
  #
739
- # When defining additional configuration options, callbacks must be defined
740
- # in either the :do option or as a block. For example,
751
+ # Callbacks must be defined as either the only argument, in the :do option,
752
+ # or as a block. For example,
741
753
  #
742
754
  # class Vehicle
743
755
  # state_machine do
744
- # before_transition :to => :parked, :do => :set_alarm
745
- # before_transition :to => :parked do |vehicle, transition|
756
+ # before_transition :set_alarm
757
+ # before_transition all => :parked :do => :set_alarm
758
+ # before_transition all => :parked do |vehicle, transition|
746
759
  # vehicle.set_alarm
747
760
  # end
748
761
  # ...
749
762
  # end
750
763
  # end
751
764
  #
752
- # === Accessing the transition
765
+ # == State requirements
753
766
  #
754
- # In addition to passing the object being transitioned, the actual
755
- # transition describing the context (e.g. event, from, to) can be accessed
756
- # as well. This additional argument is only passed if the callback allows
757
- # for it.
767
+ # Callbacks can require that the machine be transitioning from and to
768
+ # specific states. These requirements use a Hash syntax to map beginning
769
+ # states to ending states. For example,
758
770
  #
759
- # For example,
771
+ # before_transition :parked => :idling, :idling => :first_gear, :do => :set_alarm
760
772
  #
761
- # class Vehicle
762
- # # Only specifies one parameter (the object being transitioned)
763
- # before_transition :to => :parked, :do => lambda {|vehicle| vehicle.set_alarm}
764
- #
765
- # # Specifies 2 parameters (object being transitioned and actual transition)
766
- # before_transition :to => :parked, :do => lambda {|vehicle, transition| vehicle.set_alarm(transition)}
767
- # end
773
+ # In this case, the +set_alarm+ callback will only be called if the machine
774
+ # is transitioning from +parked+ to +idling+ or from +idling+ to +parked+.
768
775
  #
769
- # *Note* that the object in the callback will only be passed in as an
770
- # argument if callbacks are configured to *not* be bound to the object
771
- # involved. This is the default and may change on a per-integration basis.
776
+ # To help define state requirements, a set of helpers are available for
777
+ # slightly more complex matching:
778
+ # * <tt>all</tt> - Matches every state/event in the machine
779
+ # * <tt>all - [:parked, :idling, ...]</tt> - Matches every state/event except those specified
780
+ # * <tt>any</tt> - An alias for +all+ (matches every state/event in the machine)
781
+ # * <tt>same</tt> - Matches the same state being transitioned from
772
782
  #
773
- # See StateMachine::Transition for more information about the
774
- # attributes available on the transition.
783
+ # See StateMachine::MatcherHelpers for more information.
775
784
  #
776
- # == Examples
785
+ # Examples:
777
786
  #
778
- # Below is an example of a class with one state machine and various types
779
- # of +before+ transitions defined for it:
787
+ # before_transition :parked => [:idling, :first_gear], :do => ... # Matches from parked to idling or first_gear
788
+ # before_transition all - [:parked, :idling] => :idling, :do => ... # Matches from every state except parked and idling to idling
789
+ # before_transition all => :parked, :do => ... # Matches all states to parked
790
+ # before_transition any => same, :do => ... # Matches every loopback
780
791
  #
781
- # class Vehicle
782
- # state_machine do
783
- # # Before all transitions
784
- # before_transition :update_dashboard
785
- #
786
- # # Before specific transition:
787
- # before_transition :to => :parked, :from => [:first_gear, :idling], :on => :park, :do => :take_off_seatbelt
788
- #
789
- # # With conditional callback:
790
- # before_transition :to => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
791
- #
792
- # # Using :except counterparts:
793
- # before_transition :except_to => :stalled, :except_from => :stalled, :except_on => :crash, :do => :update_dashboard
794
- # ...
795
- # end
796
- # end
792
+ # == Event requirements
797
793
  #
798
- # As can be seen, any number of transitions can be created using various
799
- # combinations of configuration options.
800
- def before_transition(options = {}, &block)
801
- add_callback(:before, options.is_a?(Hash) ? options : {:do => options}, &block)
802
- end
803
-
804
- # Creates a callback that will be invoked *after* a transition is
805
- # performed, so long as the given configuration options match the transition.
806
- # Each part of the transition (event, to state, from state) must match
807
- # in order for the callback to get invoked.
794
+ # In addition to state requirements, an event requirement can be defined so
795
+ # that the callback is only invoked on specific events using the +on+
796
+ # option. This can also use the same matcher helpers as the state
797
+ # requirements.
798
+ #
799
+ # Examples:
800
+ #
801
+ # before_transition :on => :ignite, :do => ... # Matches only on ignite
802
+ # before_transition :on => all - :ignite, :do => ... # Matches on every event except ignite
803
+ # before_transition :parked => :idling, :on => :ignite, :do => ... # Matches from parked to idling on ignite
804
+ #
805
+ # == Verbose Requirements
806
+ #
807
+ # Requirements can also be defined using verbose options rather than the
808
+ # implicit Hash syntax and helper methods described above.
808
809
  #
809
810
  # Configuration options:
810
811
  # * <tt>:from</tt> - One or more states being transitioned from. If none
@@ -816,8 +817,18 @@ module StateMachine
816
817
  # * <tt>:except_from</tt> - One or more states *not* being transitioned from
817
818
  # * <tt>:except_to</tt> - One more states *not* being transitioned to
818
819
  # * <tt>:except_on</tt> - One or more events that *did not* fire the transition
819
- # * <tt>:do</tt> - The callback to invoke when a transition matches. This
820
- # can be a method, proc or string.
820
+ #
821
+ # Examples:
822
+ #
823
+ # before_transition :from => :ignite, :to => :idling, :on => :park, :do => ...
824
+ # before_transition :except_from => :ignite, :except_to => :idling, :except_on => :park, :do => ...
825
+ #
826
+ # == Conditions
827
+ #
828
+ # In addition to the state/event requirements, a condition can also be
829
+ # defined to help determine whether the callback should be invoked.
830
+ #
831
+ # Configuration options:
821
832
  # * <tt>:if</tt> - A method, proc or string to call to determine if the
822
833
  # callback should occur (e.g. :if => :allow_callbacks, or
823
834
  # :if => lambda {|user| user.signup_step > 2}). The method, proc or string
@@ -827,40 +838,26 @@ module StateMachine
827
838
  # :unless => lambda {|user| user.signup_step <= 2}). The method, proc or
828
839
  # string should return or evaluate to a true or false value.
829
840
  #
830
- # The +except+ group of options (+except_to+, +exception_from+, and
831
- # +except_on+) acts as the +unless+ equivalent of their counterparts (+to+,
832
- # +from+, and +on+, respectively)
841
+ # Examples:
833
842
  #
834
- # == The callback
835
- #
836
- # When defining additional configuration options, callbacks must be defined
837
- # in either the :do option or as a block. For example,
843
+ # before_transition :parked => :idling, :if => :moving?
844
+ # before_transition :on => :ignite, :unless => :seatbelt_on?
838
845
  #
839
- # class Vehicle
840
- # state_machine do
841
- # after_transition :to => :parked, :do => :set_alarm
842
- # after_transition :to => :parked do |vehicle, transition, result|
843
- # vehicle.set_alarm
844
- # end
845
- # ...
846
- # end
847
- # end
848
- #
849
- # === Accessing the transition / result
846
+ # === Accessing the transition
850
847
  #
851
848
  # In addition to passing the object being transitioned, the actual
852
- # transition describing the context (e.g. event, from, to) and the result
853
- # from calling the object's action can be optionally passed as well. These
854
- # additional arguments are only passed if the callback allows for it.
849
+ # transition describing the context (e.g. event, from, to) can be accessed
850
+ # as well. This additional argument is only passed if the callback allows
851
+ # for it.
855
852
  #
856
853
  # For example,
857
854
  #
858
855
  # class Vehicle
859
856
  # # Only specifies one parameter (the object being transitioned)
860
- # after_transition :to => :parked, :do => lambda {|vehicle| vehicle.set_alarm}
857
+ # before_transition :to => :parked, :do => lambda {|vehicle| vehicle.set_alarm}
861
858
  #
862
- # # Specifies 3 parameters (object being transitioned, transition, and action result)
863
- # after_transition :to => :parked, :do => lambda {|vehicle, transition, result| vehicle.set_alarm(transition) if result}
859
+ # # Specifies 2 parameters (object being transitioned and actual transition)
860
+ # before_transition :to => :parked, :do => lambda {|vehicle, transition| vehicle.set_alarm(transition)}
864
861
  # end
865
862
  #
866
863
  # *Note* that the object in the callback will only be passed in as an
@@ -872,28 +869,37 @@ module StateMachine
872
869
  #
873
870
  # == Examples
874
871
  #
875
- # Below is an example of a model with one state machine and various types
876
- # of +after+ transitions defined for it:
872
+ # Below is an example of a class with one state machine and various types
873
+ # of +before+ transitions defined for it:
877
874
  #
878
875
  # class Vehicle
879
876
  # state_machine do
880
- # # After all transitions
881
- # after_transition :update_dashboard
877
+ # # Before all transitions
878
+ # before_transition :update_dashboard
882
879
  #
883
- # # After specific transition:
884
- # after_transition :to => :parked, :from => [:first_gear, :idling], :on => :park, :do => :take_off_seatbelt
880
+ # # Before specific transition:
881
+ # before_transition [:first_gear, :idling] => :parked, :on => :park, :do => :take_off_seatbelt
885
882
  #
886
883
  # # With conditional callback:
887
- # after_transition :to => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
884
+ # before_transition :to => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
888
885
  #
889
- # # Using :except counterparts:
890
- # after_transition :except_to => :stalled, :except_from => :stalled, :except_on => :crash, :do => :update_dashboard
886
+ # # Using helpers:
887
+ # before_transition all - :stalled => same, :on => any - :crash, :do => :update_dashboard
891
888
  # ...
892
889
  # end
893
890
  # end
894
891
  #
895
892
  # As can be seen, any number of transitions can be created using various
896
893
  # combinations of configuration options.
894
+ def before_transition(options = {}, &block)
895
+ add_callback(:before, options.is_a?(Hash) ? options : {:do => options}, &block)
896
+ end
897
+
898
+ # Creates a callback that will be invoked *after* a transition is
899
+ # performed so long as the given requirements match the transition.
900
+ #
901
+ # See +before_transition+ for a description of the possible configurations
902
+ # for defining callbacks.
897
903
  def after_transition(options = {}, &block)
898
904
  add_callback(:after, options.is_a?(Hash) ? options : {:do => options}, &block)
899
905
  end