state_machine 0.6.3 → 0.7.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.
- data/CHANGELOG.rdoc +31 -1
- data/README.rdoc +33 -21
- data/Rakefile +2 -2
- data/examples/merb-rest/controller.rb +51 -0
- data/examples/merb-rest/model.rb +28 -0
- data/examples/merb-rest/view_edit.html.erb +24 -0
- data/examples/merb-rest/view_index.html.erb +23 -0
- data/examples/merb-rest/view_new.html.erb +13 -0
- data/examples/merb-rest/view_show.html.erb +17 -0
- data/examples/rails-rest/controller.rb +43 -0
- data/examples/rails-rest/migration.rb +11 -0
- data/examples/rails-rest/model.rb +23 -0
- data/examples/rails-rest/view_edit.html.erb +25 -0
- data/examples/rails-rest/view_index.html.erb +23 -0
- data/examples/rails-rest/view_new.html.erb +14 -0
- data/examples/rails-rest/view_show.html.erb +17 -0
- data/lib/state_machine/assertions.rb +2 -2
- data/lib/state_machine/callback.rb +14 -8
- data/lib/state_machine/condition_proxy.rb +3 -3
- data/lib/state_machine/event.rb +19 -21
- data/lib/state_machine/event_collection.rb +114 -0
- data/lib/state_machine/extensions.rb +127 -11
- data/lib/state_machine/guard.rb +1 -1
- data/lib/state_machine/integrations/active_record/locale.rb +2 -1
- data/lib/state_machine/integrations/active_record.rb +117 -39
- data/lib/state_machine/integrations/data_mapper/observer.rb +20 -64
- data/lib/state_machine/integrations/data_mapper.rb +71 -26
- data/lib/state_machine/integrations/sequel.rb +69 -21
- data/lib/state_machine/machine.rb +267 -139
- data/lib/state_machine/machine_collection.rb +145 -0
- data/lib/state_machine/matcher.rb +2 -2
- data/lib/state_machine/node_collection.rb +9 -4
- data/lib/state_machine/state.rb +22 -32
- data/lib/state_machine/state_collection.rb +66 -17
- data/lib/state_machine/transition.rb +259 -28
- data/lib/state_machine.rb +121 -56
- data/tasks/state_machine.rake +1 -0
- data/tasks/state_machine.rb +26 -0
- data/test/active_record.log +116877 -0
- data/test/functional/state_machine_test.rb +118 -12
- data/test/sequel.log +28542 -0
- data/test/unit/callback_test.rb +46 -1
- data/test/unit/condition_proxy_test.rb +55 -28
- data/test/unit/event_collection_test.rb +228 -0
- data/test/unit/event_test.rb +51 -46
- data/test/unit/integrations/active_record_test.rb +128 -70
- data/test/unit/integrations/data_mapper_test.rb +150 -58
- data/test/unit/integrations/sequel_test.rb +63 -6
- data/test/unit/invalid_event_test.rb +7 -0
- data/test/unit/machine_collection_test.rb +678 -0
- data/test/unit/machine_test.rb +198 -91
- data/test/unit/node_collection_test.rb +33 -30
- data/test/unit/state_collection_test.rb +112 -5
- data/test/unit/state_test.rb +23 -3
- data/test/unit/transition_test.rb +750 -89
- metadata +28 -3
@@ -0,0 +1,17 @@
|
|
1
|
+
<p>
|
2
|
+
<b>Name:</b>
|
3
|
+
<%=h @user.name %>
|
4
|
+
</p>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<b>State:</b>
|
8
|
+
<%=h @user.state %>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
<b>Access State:</b>
|
13
|
+
<%=h @user.access_state %>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<%= link_to 'Edit', edit_user_path(@user) %> |
|
17
|
+
<%= link_to 'Back', users_path %>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module StateMachine
|
2
|
-
# Provides a set of helper methods for making assertions about the content
|
3
|
-
# various objects
|
2
|
+
# Provides a set of helper methods for making assertions about the content
|
3
|
+
# of various objects
|
4
4
|
module Assertions
|
5
5
|
# Validates that all keys in the given hash *only* includes the specified
|
6
6
|
# valid keys. If any invalid keys are found, an ArgumentError will be
|
@@ -2,14 +2,14 @@ require 'state_machine/guard'
|
|
2
2
|
require 'state_machine/eval_helpers'
|
3
3
|
|
4
4
|
module StateMachine
|
5
|
-
# Callbacks represent hooks into objects that allow
|
5
|
+
# Callbacks represent hooks into objects that allow logic to be triggered
|
6
6
|
# before or after a specific transition occurs.
|
7
7
|
class Callback
|
8
8
|
include EvalHelpers
|
9
9
|
|
10
10
|
class << self
|
11
|
-
# Determines whether to automatically bind the callback to the object
|
12
|
-
# transitioned. This only applies to callbacks that are defined as
|
11
|
+
# Determines whether to automatically bind the callback to the object
|
12
|
+
# being transitioned. This only applies to callbacks that are defined as
|
13
13
|
# lambda blocks (or Procs). Some integrations, such as DataMapper, handle
|
14
14
|
# callbacks by executing them bound to the object involved, while other
|
15
15
|
# integrations, such as ActiveRecord, pass the object as an argument to
|
@@ -53,6 +53,13 @@ module StateMachine
|
|
53
53
|
# end
|
54
54
|
# end
|
55
55
|
attr_accessor :bind_to_object
|
56
|
+
|
57
|
+
# The application-wide terminator to use for callbacks when not
|
58
|
+
# explicitly defined. Terminators determine whether to cancel a
|
59
|
+
# callback chain based on the return value of the callback.
|
60
|
+
#
|
61
|
+
# See StateMachine::Callback#terminator for more information.
|
62
|
+
attr_accessor :terminator
|
56
63
|
end
|
57
64
|
|
58
65
|
# An optional block for determining whether to cancel the callback chain
|
@@ -114,13 +121,12 @@ module StateMachine
|
|
114
121
|
options = {}
|
115
122
|
end
|
116
123
|
|
117
|
-
# The actual method to invoke must be defined
|
118
124
|
raise ArgumentError, ':do callback must be specified' unless @method
|
119
125
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
@method = bound_method(@method) if @method.is_a?(Proc)
|
126
|
+
options = {:bind_to_object => self.class.bind_to_object, :terminator => self.class.terminator}.merge(options)
|
127
|
+
|
128
|
+
# Proxy lambda blocks so that they're bound to the object
|
129
|
+
@method = bound_method(@method) if options.delete(:bind_to_object) && @method.is_a?(Proc)
|
124
130
|
@terminator = options.delete(:terminator)
|
125
131
|
|
126
132
|
@guard = Guard.new(options)
|
@@ -56,7 +56,7 @@ module StateMachine
|
|
56
56
|
@condition = condition
|
57
57
|
end
|
58
58
|
|
59
|
-
# Hooks in condition
|
59
|
+
# Hooks in condition-merging to methods that don't exist in this module
|
60
60
|
def method_missing(*args, &block)
|
61
61
|
# Get the configuration
|
62
62
|
if args.last.is_a?(Hash)
|
@@ -77,8 +77,8 @@ module StateMachine
|
|
77
77
|
# Replace the configuration condition with the one configured for this
|
78
78
|
# proxy, merging together any existing conditions
|
79
79
|
options[:if] = lambda do |*args|
|
80
|
-
# Block may be executed within the context of the actual object, so
|
81
|
-
# either be the first argument or the executing context
|
80
|
+
# Block may be executed within the context of the actual object, so
|
81
|
+
# it'll either be the first argument or the executing context
|
82
82
|
object = args.first || self
|
83
83
|
|
84
84
|
proxy.evaluate_method(object, proxy_condition) &&
|
data/lib/state_machine/event.rb
CHANGED
@@ -4,6 +4,10 @@ require 'state_machine/assertions'
|
|
4
4
|
require 'state_machine/matcher_helpers'
|
5
5
|
|
6
6
|
module StateMachine
|
7
|
+
# An invalid event was specified
|
8
|
+
class InvalidEvent < StandardError
|
9
|
+
end
|
10
|
+
|
7
11
|
# An event defines an action that transitions an attribute from one state to
|
8
12
|
# another. The state that an attribute is transitioned to depends on the
|
9
13
|
# guards configured for the event.
|
@@ -14,9 +18,12 @@ module StateMachine
|
|
14
18
|
# The state machine for which this event is defined
|
15
19
|
attr_accessor :machine
|
16
20
|
|
17
|
-
# The name of the
|
21
|
+
# The name of the event
|
18
22
|
attr_reader :name
|
19
23
|
|
24
|
+
# The fully-qualified name of the event, scoped by the machine's namespace
|
25
|
+
attr_reader :qualified_name
|
26
|
+
|
20
27
|
# The list of guards that determine what state this event transitions
|
21
28
|
# objects to when fired
|
22
29
|
attr_reader :guards
|
@@ -29,6 +36,7 @@ module StateMachine
|
|
29
36
|
def initialize(machine, name) #:nodoc:
|
30
37
|
@machine = machine
|
31
38
|
@name = name
|
39
|
+
@qualified_name = machine.namespace ? :"#{name}_#{machine.namespace}" : name
|
32
40
|
@guards = []
|
33
41
|
@known_states = []
|
34
42
|
|
@@ -54,8 +62,8 @@ module StateMachine
|
|
54
62
|
# transition :parked => :idling, :idling => :first_gear
|
55
63
|
#
|
56
64
|
# In this case, when the event is fired, this transition will cause the
|
57
|
-
# state to be +idling+ if it's current state is +parked+ or +first_gear+
|
58
|
-
# it's current state is +idling+.
|
65
|
+
# state to be +idling+ if it's current state is +parked+ or +first_gear+
|
66
|
+
# if it's current state is +idling+.
|
59
67
|
#
|
60
68
|
# To help defining these implicit transitions, a set of helpers are available
|
61
69
|
# for defining slightly more complex matching:
|
@@ -149,13 +157,13 @@ module StateMachine
|
|
149
157
|
#
|
150
158
|
# If the event can't be fired, then this will return false, otherwise true.
|
151
159
|
def can_fire?(object)
|
152
|
-
!
|
160
|
+
!transition_for(object).nil?
|
153
161
|
end
|
154
162
|
|
155
163
|
# Finds and builds the next transition that can be performed on the given
|
156
164
|
# object. If no transitions can be made, then this will return nil.
|
157
|
-
def
|
158
|
-
from = machine.
|
165
|
+
def transition_for(object)
|
166
|
+
from = machine.states.match(object).name
|
159
167
|
|
160
168
|
guards.each do |guard|
|
161
169
|
if match = guard.match(object, :from => from)
|
@@ -179,21 +187,14 @@ module StateMachine
|
|
179
187
|
def fire(object, *args)
|
180
188
|
machine.reset(object)
|
181
189
|
|
182
|
-
if transition =
|
190
|
+
if transition = transition_for(object)
|
183
191
|
transition.perform(*args)
|
184
192
|
else
|
185
|
-
machine.invalidate(object,
|
193
|
+
machine.invalidate(object, machine.attribute, :invalid_transition, [[:event, name]])
|
186
194
|
false
|
187
195
|
end
|
188
196
|
end
|
189
197
|
|
190
|
-
# Attempts to perform the next available transition on the given object.
|
191
|
-
# If no transitions can be made, then a StateMachine::InvalidTransition
|
192
|
-
# exception will be raised, otherwise true will be returned.
|
193
|
-
def fire!(object, *args)
|
194
|
-
fire(object, *args) || raise(StateMachine::InvalidTransition, "Cannot transition #{machine.attribute} via :#{name} from #{machine.state_for(object).name.inspect}")
|
195
|
-
end
|
196
|
-
|
197
198
|
# Draws a representation of this event on the given graph. This will
|
198
199
|
# create 1 or more edges on the graph for each guard (i.e. transition)
|
199
200
|
# configured.
|
@@ -225,9 +226,6 @@ module StateMachine
|
|
225
226
|
# Add the various instance methods that can transition the object using
|
226
227
|
# the current event
|
227
228
|
def add_actions
|
228
|
-
qualified_name = name = self.name
|
229
|
-
qualified_name = "#{name}_#{machine.namespace}" if machine.namespace
|
230
|
-
|
231
229
|
# Checks whether the event can be fired on the current object
|
232
230
|
machine.define_instance_method("can_#{qualified_name}?") do |machine, object|
|
233
231
|
machine.event(name).can_fire?(object)
|
@@ -235,8 +233,8 @@ module StateMachine
|
|
235
233
|
|
236
234
|
# Gets the next transition that would be performed if the event were
|
237
235
|
# fired now
|
238
|
-
machine.define_instance_method("
|
239
|
-
machine.event(name).
|
236
|
+
machine.define_instance_method("#{qualified_name}_transition") do |machine, object|
|
237
|
+
machine.event(name).transition_for(object)
|
240
238
|
end
|
241
239
|
|
242
240
|
# Fires the event
|
@@ -246,7 +244,7 @@ module StateMachine
|
|
246
244
|
|
247
245
|
# Fires the event, raising an exception if it fails
|
248
246
|
machine.define_instance_method("#{qualified_name}!") do |machine, object, *args|
|
249
|
-
|
247
|
+
object.send(qualified_name, *args) || raise(StateMachine::InvalidTransition, "Cannot transition #{machine.attribute} via :#{name} from #{machine.states.match(object).name.inspect}")
|
250
248
|
end
|
251
249
|
end
|
252
250
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module StateMachine
|
2
|
+
# Represents a collection of events in a state machine
|
3
|
+
class EventCollection < NodeCollection
|
4
|
+
def initialize(machine) #:nodoc:
|
5
|
+
super(machine, :index => [:name, :qualified_name])
|
6
|
+
end
|
7
|
+
|
8
|
+
# Gets the list of events that can be fired on the given object.
|
9
|
+
#
|
10
|
+
# == Examples
|
11
|
+
#
|
12
|
+
# class Vehicle
|
13
|
+
# state_machine :initial => :parked do
|
14
|
+
# event :park do
|
15
|
+
# transition :idling => :parked
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# event :ignite do
|
19
|
+
# transition :parked => :idling
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# events = Vehicle.state_machine(:state).events
|
25
|
+
#
|
26
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
|
27
|
+
# events.valid_for(vehicle) # => [#<StateMachine::Event name=:ignite transitions=[:parked => :idling]>]
|
28
|
+
#
|
29
|
+
# vehicle.state = 'idling'
|
30
|
+
# events.valid_for(vehicle) # => [#<StateMachine::Event name=:park transitions=[:idling => :parked]>]
|
31
|
+
def valid_for(object)
|
32
|
+
select {|event| event.can_fire?(object)}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Gets the list of transitions that can be run on the given object.
|
36
|
+
#
|
37
|
+
# == Examples
|
38
|
+
#
|
39
|
+
# class Vehicle
|
40
|
+
# state_machine :initial => :parked do
|
41
|
+
# event :park do
|
42
|
+
# transition :idling => :parked
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# event :ignite do
|
46
|
+
# transition :parked => :idling
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# events = Vehicle.state_machine.events
|
52
|
+
#
|
53
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
|
54
|
+
# events.transitions_for(vehicle) # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
|
55
|
+
#
|
56
|
+
# vehicle.state = 'idling'
|
57
|
+
# events.transitions_for(vehicle) # => [#<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
|
58
|
+
def transitions_for(object)
|
59
|
+
map {|event| event.transition_for(object)}.compact
|
60
|
+
end
|
61
|
+
|
62
|
+
# Gets the transition that should be performed for the event stored in the
|
63
|
+
# given object's event attribute. This also takes an additional parameter
|
64
|
+
# for automatically invalidating the object if the event or transition
|
65
|
+
# are invalid. By default, this is turned off.
|
66
|
+
#
|
67
|
+
# *Note* that if a transition has already been generated for the event,
|
68
|
+
# then that transition will be used.
|
69
|
+
#
|
70
|
+
# == Examples
|
71
|
+
#
|
72
|
+
# class Vehicle < ActiveRecord::Base
|
73
|
+
# state_machine :initial => :parked do
|
74
|
+
# event :ignite do
|
75
|
+
# transition :parked => :idling
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# vehicle = Vehicle.new # => #<Vehicle id: nil, state: "parked">
|
81
|
+
# events = Vehicle.state_machine.events
|
82
|
+
#
|
83
|
+
# vehicle.state_event = nil
|
84
|
+
# events.attribute_transition_for(vehicle) # => nil # Event isn't defined
|
85
|
+
#
|
86
|
+
# vehicle.state_event = 'invalid'
|
87
|
+
# events.attribute_transition_for(vehicle) # => false # Event is invalid
|
88
|
+
#
|
89
|
+
# vehicle.state_event = 'ignite'
|
90
|
+
# events.attribute_transition_for(vehicle) # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
|
91
|
+
def attribute_transition_for(object, invalidate = false)
|
92
|
+
return unless machine.action
|
93
|
+
|
94
|
+
result = nil
|
95
|
+
attribute = machine.attribute
|
96
|
+
|
97
|
+
if name = object.send("#{attribute}_event")
|
98
|
+
if event = self[name.to_sym, :qualified_name]
|
99
|
+
unless result = object.send("#{attribute}_event_transition") || event.transition_for(object)
|
100
|
+
# No valid transition: invalidate
|
101
|
+
machine.invalidate(object, "#{attribute}_event", :invalid_event, [[:state, machine.states.match(object).name]]) if invalidate
|
102
|
+
result = false
|
103
|
+
end
|
104
|
+
else
|
105
|
+
# Event is unknown: invalidate
|
106
|
+
machine.invalidate(object, "#{attribute}_event", :invalid) if invalidate
|
107
|
+
result = false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
result
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -1,19 +1,21 @@
|
|
1
|
+
require 'state_machine/machine_collection'
|
2
|
+
|
1
3
|
module StateMachine
|
2
4
|
module ClassMethods
|
3
5
|
def self.extended(base) #:nodoc:
|
4
6
|
base.class_eval do
|
5
|
-
@state_machines =
|
7
|
+
@state_machines = MachineCollection.new
|
6
8
|
end
|
7
9
|
end
|
8
10
|
|
9
11
|
# Gets the current list of state machines defined for this class. This
|
10
12
|
# class-level attribute acts like an inheritable attribute. The attribute
|
11
|
-
# is available to each subclass, each
|
12
|
-
#
|
13
|
+
# is available to each subclass, each having a copy of its superclass's
|
14
|
+
# attribute.
|
13
15
|
#
|
14
|
-
# The hash of state machines maps
|
16
|
+
# The hash of state machines maps <tt>:attribute</tt> => +machine+, e.g.
|
15
17
|
#
|
16
|
-
# Vehicle.state_machines # => {:state => #<StateMachine::Machine:0xb6f6e4a4 ...>
|
18
|
+
# Vehicle.state_machines # => {:state => #<StateMachine::Machine:0xb6f6e4a4 ...>}
|
17
19
|
def state_machines
|
18
20
|
@state_machines ||= superclass.state_machines.dup
|
19
21
|
end
|
@@ -29,14 +31,128 @@ module StateMachine
|
|
29
31
|
initialize_state_machines
|
30
32
|
end
|
31
33
|
|
34
|
+
# Runs one or more events in parallel. All events will run through the
|
35
|
+
# following steps:
|
36
|
+
# * Before callbacks
|
37
|
+
# * Persist state
|
38
|
+
# * Invoke action
|
39
|
+
# * After callbacks
|
40
|
+
#
|
41
|
+
# For example, if two events (for state machines A and B) are run in
|
42
|
+
# parallel, the order in which steps are run is:
|
43
|
+
# * A - Before transition callbacks
|
44
|
+
# * B - Before transition callbacks
|
45
|
+
# * A - Persist new state
|
46
|
+
# * B - Persist new state
|
47
|
+
# * A - Invoke action
|
48
|
+
# * B - Invoke action (only if different than A's action)
|
49
|
+
# * A - After transition callbacks
|
50
|
+
# * B - After transition callbacks
|
51
|
+
#
|
52
|
+
# *Note* that multiple events on the same state machine / attribute cannot
|
53
|
+
# be run in parallel. If this is attempted, an ArgumentError will be
|
54
|
+
# raised.
|
55
|
+
#
|
56
|
+
# == Halting callbacks
|
57
|
+
#
|
58
|
+
# When running multiple events in parallel, special consideration should
|
59
|
+
# be taken with regard to how halting within callbacks affects the flow.
|
60
|
+
#
|
61
|
+
# For *before* callbacks, any <tt>:halt</tt> error that's thrown will
|
62
|
+
# immediately cancel the perform for all transitions. As a result, it's
|
63
|
+
# possible for one event's transition to affect the continuation of
|
64
|
+
# another.
|
65
|
+
#
|
66
|
+
# On the other hand, any <tt>:halt</tt> error that's thrown within an
|
67
|
+
# *after* callback with only affect that event's transition. Other
|
68
|
+
# transitions will continue to run their own callbacks.
|
69
|
+
#
|
70
|
+
# == Example
|
71
|
+
#
|
72
|
+
# class Vehicle
|
73
|
+
# state_machine :initial => :parked do
|
74
|
+
# event :ignite do
|
75
|
+
# transition :parked => :idling
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# event :park do
|
79
|
+
# transition :idling => :parked
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# state_machine :alarm_state, :namespace => 'alarm', :initial => :on do
|
84
|
+
# event :enable do
|
85
|
+
# transition all => :active
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# event :disable do
|
89
|
+
# transition all => :off
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7c02850 @state="parked", @alarm_state="active">
|
95
|
+
# vehicle.state # => "parked"
|
96
|
+
# vehicle.alarm_state # => "active"
|
97
|
+
#
|
98
|
+
# vehicle.fire_events(:ignite, :disable_alarm) # => true
|
99
|
+
# vehicle.state # => "idling"
|
100
|
+
# vehicle.alarm_state # => "off"
|
101
|
+
#
|
102
|
+
# # If any event fails, the entire event chain fails
|
103
|
+
# vehicle.fire_events(:ignite, :enable_alarm) # => false
|
104
|
+
# vehicle.state # => "idling"
|
105
|
+
# vehicle.alarm_state # => "off"
|
106
|
+
#
|
107
|
+
# # Exception raised on invalid event
|
108
|
+
# vehicle.fire_events(:park, :invalid) # => StateMachine::InvalidEvent: :invalid is an unknown event
|
109
|
+
# vehicle.state # => "idling"
|
110
|
+
# vehicle.alarm_state # => "off"
|
111
|
+
def fire_events(*events)
|
112
|
+
self.class.state_machines.fire_events(self, *events)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Run one or more events in parallel. If any event fails to run, then
|
116
|
+
# a StateMachine::InvalidTransition exception will be raised.
|
117
|
+
#
|
118
|
+
# See StateMachine::InstanceMethods#fire_events for more information.
|
119
|
+
#
|
120
|
+
# == Example
|
121
|
+
#
|
122
|
+
# class Vehicle
|
123
|
+
# state_machine :initial => :parked do
|
124
|
+
# event :ignite do
|
125
|
+
# transition :parked => :idling
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# event :park do
|
129
|
+
# transition :idling => :parked
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# state_machine :alarm_state, :namespace => 'alarm', :initial => :active do
|
134
|
+
# event :enable do
|
135
|
+
# transition all => :active
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# event :disable do
|
139
|
+
# transition all => :off
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7c02850 @state="parked", @alarm_state="active">
|
145
|
+
# vehicle.fire_events(:ignite, :disable_alarm) # => true
|
146
|
+
#
|
147
|
+
# vehicle.fire_events!(:ignite, :disable_alarm) # => StateMachine::InvalidTranstion: Cannot run events in parallel: ignite, disable_alarm
|
148
|
+
def fire_events!(*events)
|
149
|
+
run_action = [true, false].include?(events.last) ? events.pop : true
|
150
|
+
fire_events(*(events + [run_action])) || raise(StateMachine::InvalidTransition, "Cannot run events in parallel: #{events * ', '}")
|
151
|
+
end
|
152
|
+
|
32
153
|
protected
|
33
154
|
def initialize_state_machines #:nodoc:
|
34
|
-
self.class.state_machines.
|
35
|
-
# Set the initial value of the machine's attribute unless it already
|
36
|
-
# exists (which must mean the defaults are being skipped)
|
37
|
-
value = send(attribute)
|
38
|
-
send("#{attribute}=", machine.initial_state(self).value) if value.nil? || value.respond_to?(:empty?) && value.empty?
|
39
|
-
end
|
155
|
+
self.class.state_machines.initialize_states(self)
|
40
156
|
end
|
41
157
|
end
|
42
158
|
end
|