state_machine 0.9.4 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +20 -0
- data/LICENSE +1 -1
- data/README.rdoc +74 -4
- data/Rakefile +3 -3
- data/lib/state_machine.rb +51 -24
- data/lib/state_machine/{guard.rb → branch.rb} +34 -40
- data/lib/state_machine/callback.rb +13 -18
- data/lib/state_machine/error.rb +13 -0
- data/lib/state_machine/eval_helpers.rb +3 -0
- data/lib/state_machine/event.rb +67 -30
- data/lib/state_machine/event_collection.rb +20 -3
- data/lib/state_machine/extensions.rb +3 -3
- data/lib/state_machine/integrations.rb +7 -0
- data/lib/state_machine/integrations/active_model.rb +149 -59
- data/lib/state_machine/integrations/active_model/versions.rb +30 -0
- data/lib/state_machine/integrations/active_record.rb +74 -148
- data/lib/state_machine/integrations/active_record/locale.rb +0 -7
- data/lib/state_machine/integrations/active_record/versions.rb +149 -0
- data/lib/state_machine/integrations/base.rb +64 -0
- data/lib/state_machine/integrations/data_mapper.rb +50 -39
- data/lib/state_machine/integrations/data_mapper/observer.rb +47 -12
- data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
- data/lib/state_machine/integrations/mongo_mapper.rb +37 -64
- data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +102 -0
- data/lib/state_machine/integrations/mongoid.rb +297 -0
- data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
- data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
- data/lib/state_machine/integrations/sequel.rb +99 -55
- data/lib/state_machine/integrations/sequel/versions.rb +40 -0
- data/lib/state_machine/machine.rb +273 -136
- data/lib/state_machine/machine_collection.rb +21 -13
- data/lib/state_machine/node_collection.rb +6 -1
- data/lib/state_machine/path.rb +120 -0
- data/lib/state_machine/path_collection.rb +90 -0
- data/lib/state_machine/state.rb +28 -9
- data/lib/state_machine/state_collection.rb +1 -1
- data/lib/state_machine/transition.rb +65 -6
- data/lib/state_machine/transition_collection.rb +1 -1
- data/test/files/en.yml +8 -0
- data/test/functional/state_machine_test.rb +15 -2
- data/test/unit/branch_test.rb +890 -0
- data/test/unit/callback_test.rb +9 -36
- data/test/unit/error_test.rb +43 -0
- data/test/unit/event_collection_test.rb +67 -33
- data/test/unit/event_test.rb +165 -38
- data/test/unit/integrations/active_model_test.rb +103 -3
- data/test/unit/integrations/active_record_test.rb +90 -43
- data/test/unit/integrations/base_test.rb +87 -0
- data/test/unit/integrations/data_mapper_test.rb +105 -44
- data/test/unit/integrations/mongo_mapper_test.rb +261 -64
- data/test/unit/integrations/mongoid_test.rb +1529 -0
- data/test/unit/integrations/sequel_test.rb +33 -49
- data/test/unit/integrations_test.rb +4 -0
- data/test/unit/invalid_event_test.rb +15 -2
- data/test/unit/invalid_parallel_transition_test.rb +18 -0
- data/test/unit/invalid_transition_test.rb +72 -2
- data/test/unit/machine_collection_test.rb +55 -61
- data/test/unit/machine_test.rb +388 -26
- data/test/unit/node_collection_test.rb +14 -4
- data/test/unit/path_collection_test.rb +266 -0
- data/test/unit/path_test.rb +485 -0
- data/test/unit/state_collection_test.rb +30 -0
- data/test/unit/state_test.rb +82 -35
- data/test/unit/transition_collection_test.rb +48 -44
- data/test/unit/transition_test.rb +198 -41
- metadata +111 -74
- data/test/unit/guard_test.rb +0 -909
@@ -1,20 +1,28 @@
|
|
1
1
|
module StateMachine
|
2
2
|
# Represents a collection of state machines for a class
|
3
3
|
class MachineCollection < Hash
|
4
|
-
# Initializes the state of each machine in the given object.
|
5
|
-
#
|
6
|
-
#
|
4
|
+
# Initializes the state of each machine in the given object. This can allow
|
5
|
+
# states to be initialized in two groups: static and dynamic. For example:
|
6
|
+
#
|
7
|
+
# machines.initialize_states(object) do
|
8
|
+
# # After static state initialization, before dynamic state initialization
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# If no block is provided, then all states will still be initialized.
|
7
12
|
def initialize_states(object, options = {})
|
8
|
-
|
9
|
-
|
10
|
-
|
13
|
+
options = {:static => true, :dynamic => true}.merge(options)
|
14
|
+
|
15
|
+
each_value do |machine|
|
16
|
+
machine.initialize_state(object, options) unless machine.dynamic_initial_state?
|
17
|
+
end if options[:static]
|
18
|
+
|
19
|
+
result = yield if block_given?
|
11
20
|
|
12
21
|
each_value do |machine|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
22
|
+
machine.initialize_state(object, options) if machine.dynamic_initial_state?
|
23
|
+
end if options[:dynamic]
|
24
|
+
|
25
|
+
result
|
18
26
|
end
|
19
27
|
|
20
28
|
# Runs one or more events in parallel on the given object. See
|
@@ -28,12 +36,12 @@ module StateMachine
|
|
28
36
|
event = nil
|
29
37
|
detect {|name, machine| event = machine.events[event_name, :qualified_name]}
|
30
38
|
|
31
|
-
raise
|
39
|
+
raise(InvalidEvent.new(object, event_name)) unless event
|
32
40
|
|
33
41
|
# Get the transition that will be performed for the event
|
34
42
|
unless transition = event.transition_for(object)
|
35
43
|
machine = event.machine
|
36
|
-
|
44
|
+
event.on_failure(object)
|
37
45
|
end
|
38
46
|
|
39
47
|
transition
|
@@ -34,7 +34,7 @@ module StateMachine
|
|
34
34
|
nodes = @nodes
|
35
35
|
@nodes = []
|
36
36
|
@indices = @indices.inject({}) {|indices, (name, index)| indices[name] = {}; indices}
|
37
|
-
nodes.
|
37
|
+
concat(nodes.map {|n| n.dup})
|
38
38
|
end
|
39
39
|
|
40
40
|
# Changes the current machine associated with the collection. In turn, this
|
@@ -62,6 +62,11 @@ module StateMachine
|
|
62
62
|
self
|
63
63
|
end
|
64
64
|
|
65
|
+
# Appends a group of nodes to the collection
|
66
|
+
def concat(nodes)
|
67
|
+
nodes.each {|node| self << node}
|
68
|
+
end
|
69
|
+
|
65
70
|
# Updates the indexed keys for the given node. If the node's attribute
|
66
71
|
# has changed since it was added to the collection, the old indexed keys
|
67
72
|
# will be replaced with the updated ones.
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module StateMachine
|
2
|
+
# A path represents a sequence of transitions that can be run for a particular
|
3
|
+
# object. Paths can walk to new transitions, revealing all of the possible
|
4
|
+
# branches that can be encountered in the object's state machine.
|
5
|
+
class Path < Array
|
6
|
+
include Assertions
|
7
|
+
|
8
|
+
# The object whose state machine is being walked
|
9
|
+
attr_reader :object
|
10
|
+
|
11
|
+
# The state machine this path is walking
|
12
|
+
attr_reader :machine
|
13
|
+
|
14
|
+
# Creates a new transition path for the given object. Initially this is an
|
15
|
+
# empty path. In order to start walking the path, it must be populated with
|
16
|
+
# an initial transition.
|
17
|
+
#
|
18
|
+
# Configuration options:
|
19
|
+
# * <tt>:target</tt> - The target state to end the path on
|
20
|
+
# * <tt>:guard</tt> - Whether to guard transitions with the if/unless
|
21
|
+
# conditionals defined for each one
|
22
|
+
def initialize(object, machine, options = {})
|
23
|
+
assert_valid_keys(options, :target, :guard)
|
24
|
+
|
25
|
+
@object = object
|
26
|
+
@machine = machine
|
27
|
+
@target = options[:target]
|
28
|
+
@guard = options[:guard]
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize_copy(orig) #:nodoc:
|
32
|
+
super
|
33
|
+
@transitions = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# The initial state name for this path
|
37
|
+
def from_name
|
38
|
+
first && first.from_name
|
39
|
+
end
|
40
|
+
|
41
|
+
# Lists all of the from states that can be reached through this path.
|
42
|
+
#
|
43
|
+
# For example,
|
44
|
+
#
|
45
|
+
# path.to_states # => [:parked, :idling, :first_gear, ...]
|
46
|
+
def from_states
|
47
|
+
map {|transition| transition.from_name}.uniq
|
48
|
+
end
|
49
|
+
|
50
|
+
# The end state name for this path. If a target state was specified for
|
51
|
+
# the path, then that will be returned if the path is complete.
|
52
|
+
def to_name
|
53
|
+
last && last.to_name
|
54
|
+
end
|
55
|
+
|
56
|
+
# Lists all of the to states that can be reached through this path.
|
57
|
+
#
|
58
|
+
# For example,
|
59
|
+
#
|
60
|
+
# path.to_states # => [:parked, :idling, :first_gear, ...]
|
61
|
+
def to_states
|
62
|
+
map {|transition| transition.to_name}.uniq
|
63
|
+
end
|
64
|
+
|
65
|
+
# Lists all of the events that can be fired through this path.
|
66
|
+
#
|
67
|
+
# For example,
|
68
|
+
#
|
69
|
+
# path.events # => [:park, :ignite, :shift_up, ...]
|
70
|
+
def events
|
71
|
+
map {|transition| transition.event}.uniq
|
72
|
+
end
|
73
|
+
|
74
|
+
# Walks down the next transitions at the end of this path. This will only
|
75
|
+
# walk down paths that are considered valid.
|
76
|
+
def walk
|
77
|
+
transitions.each {|transition| yield dup.push(transition)}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Determines whether or not this path has completed. A path is considered
|
81
|
+
# complete when one of the following conditions is met:
|
82
|
+
# * The last transition in the path ends on the target state
|
83
|
+
# * There are no more transitions remaining to walk and there is no target
|
84
|
+
# state
|
85
|
+
def complete?
|
86
|
+
!empty? && (@target ? to_name == @target : transitions.empty?)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
# Calculates the number of times the given state has been walked to
|
91
|
+
def times_walked_to(state)
|
92
|
+
select {|transition| transition.to_name == state}.length
|
93
|
+
end
|
94
|
+
|
95
|
+
# Determines whether the given transition has been recently walked down in
|
96
|
+
# this path. If a target is configured for this path, then this will only
|
97
|
+
# look at transitions walked down since the target was last reached.
|
98
|
+
def recently_walked?(transition)
|
99
|
+
transitions = self
|
100
|
+
if @target && @target != to_name && target_transition = detect {|t| t.to_name == @target}
|
101
|
+
transitions = transitions[index(target_transition) + 1..-1]
|
102
|
+
end
|
103
|
+
transitions.include?(transition)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Determines whether it's possible to walk to the given transition from
|
107
|
+
# the current path. A transition can be walked to if:
|
108
|
+
# * It has not been recently walked and
|
109
|
+
# * If a target is specified, it has not been walked to twice yet
|
110
|
+
def can_walk_to?(transition)
|
111
|
+
!recently_walked?(transition) && (!@target || times_walked_to(@target) < 2)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Get the next set of transitions that can be walked to starting from the
|
115
|
+
# end of this path
|
116
|
+
def transitions
|
117
|
+
@transitions ||= empty? ? [] : machine.events.transitions_for(object, :from => to_name, :guard => @guard).select {|transition| can_walk_to?(transition)}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'state_machine/path'
|
2
|
+
|
3
|
+
module StateMachine
|
4
|
+
# Represents a collection of paths that are generated based on a set of
|
5
|
+
# requirements regarding what states to start and end on
|
6
|
+
class PathCollection < Array
|
7
|
+
include Assertions
|
8
|
+
|
9
|
+
# The object whose state machine is being walked
|
10
|
+
attr_reader :object
|
11
|
+
|
12
|
+
# The state machine these path are walking
|
13
|
+
attr_reader :machine
|
14
|
+
|
15
|
+
# The initial state to start each path from
|
16
|
+
attr_reader :from_name
|
17
|
+
|
18
|
+
# The target state for each path
|
19
|
+
attr_reader :to_name
|
20
|
+
|
21
|
+
# Creates a new collection of paths with the given requirements.
|
22
|
+
#
|
23
|
+
# Configuration options:
|
24
|
+
# * <tt>:from</tt> - The initial state to start from
|
25
|
+
# * <tt>:to</tt> - The target end state
|
26
|
+
# * <tt>:deep</tt> - Whether to enable deep searches for the target state.
|
27
|
+
# * <tt>:guard</tt> - Whether to guard transitions with the if/unless
|
28
|
+
# conditionals defined for each one
|
29
|
+
def initialize(object, machine, options = {})
|
30
|
+
options = {:deep => false, :from => machine.states.match!(object).name}.merge(options)
|
31
|
+
assert_valid_keys(options, :from, :to, :deep, :guard)
|
32
|
+
|
33
|
+
@object = object
|
34
|
+
@machine = machine
|
35
|
+
@from_name = machine.states.fetch(options[:from]).name
|
36
|
+
@to_name = options[:to] && machine.states.fetch(options[:to]).name
|
37
|
+
@guard = options[:guard]
|
38
|
+
@deep = options[:deep]
|
39
|
+
|
40
|
+
initial_paths.each {|path| walk(path)}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Lists all of the states that can be transitioned from through the paths in
|
44
|
+
# this collection.
|
45
|
+
#
|
46
|
+
# For example,
|
47
|
+
#
|
48
|
+
# paths.from_states # => [:parked, :idling, :first_gear, ...]
|
49
|
+
def from_states
|
50
|
+
map {|path| path.from_states}.flatten.uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
# Lists all of the states that can be transitioned to through the paths in
|
54
|
+
# this collection.
|
55
|
+
#
|
56
|
+
# For example,
|
57
|
+
#
|
58
|
+
# paths.to_states # => [:idling, :first_gear, :second_gear, ...]
|
59
|
+
def to_states
|
60
|
+
map {|path| path.to_states}.flatten.uniq
|
61
|
+
end
|
62
|
+
|
63
|
+
# Lists all of the events that can be fired through the paths in this
|
64
|
+
# collection.
|
65
|
+
#
|
66
|
+
# For example,
|
67
|
+
#
|
68
|
+
# paths.events # => [:park, :ignite, :shift_up, ...]
|
69
|
+
def events
|
70
|
+
map {|path| path.events}.flatten.uniq
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
# Gets the initial set of paths to walk
|
75
|
+
def initial_paths
|
76
|
+
machine.events.transitions_for(object, :from => from_name, :guard => @guard).map do |transition|
|
77
|
+
path = Path.new(object, machine, :target => to_name, :guard => @guard)
|
78
|
+
path << transition
|
79
|
+
path
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Walks down the given path. Each new path that matches the configured
|
84
|
+
# requirements will be added to this collection.
|
85
|
+
def walk(path)
|
86
|
+
self << path if path.complete?
|
87
|
+
path.walk {|next_path| walk(next_path)} unless to_name && path.complete? && !@deep
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/state_machine/state.rb
CHANGED
@@ -73,7 +73,20 @@ module StateMachine
|
|
73
73
|
@methods = {}
|
74
74
|
@initial = options[:initial] == true
|
75
75
|
|
76
|
-
|
76
|
+
if name
|
77
|
+
conflicting_machines = machine.owner_class.state_machines.select {|name, other_machine| other_machine != machine && other_machine.states[qualified_name, :qualified_name]}
|
78
|
+
|
79
|
+
# Output a warning if another machine has a conflicting qualified name
|
80
|
+
# for a different attribute
|
81
|
+
if conflict = conflicting_machines.detect {|name, other_machine| other_machine.attribute != machine.attribute}
|
82
|
+
name, other_machine = conflict
|
83
|
+
warn "State #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}"
|
84
|
+
elsif conflicting_machines.empty?
|
85
|
+
# Only bother adding predicates when another machine for the same
|
86
|
+
# attribute hasn't already done so
|
87
|
+
add_predicate
|
88
|
+
end
|
89
|
+
end
|
77
90
|
end
|
78
91
|
|
79
92
|
# Creates a copy of this state in addition to the list of associated
|
@@ -89,8 +102,8 @@ module StateMachine
|
|
89
102
|
# machine's definition.
|
90
103
|
def final?
|
91
104
|
!machine.events.any? do |event|
|
92
|
-
event.
|
93
|
-
|
105
|
+
event.branches.any? do |branch|
|
106
|
+
branch.state_requirements.any? do |requirement|
|
94
107
|
requirement[:from].matches?(name) && !requirement[:to].matches?(name, :from => name)
|
95
108
|
end
|
96
109
|
end
|
@@ -247,13 +260,19 @@ module StateMachine
|
|
247
260
|
end
|
248
261
|
|
249
262
|
# Adds a predicate method to the owner class so long as a name has
|
250
|
-
# actually been configured for the state
|
263
|
+
# actually been configured for the state and the method isn't already
|
264
|
+
# defined in the owner class.
|
251
265
|
def add_predicate
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
machine.
|
266
|
+
owner_class = machine.owner_class
|
267
|
+
predicate = "#{qualified_name}?"
|
268
|
+
if !owner_class.method_defined?(predicate) && !owner_class.private_method_defined?(predicate)
|
269
|
+
# Checks whether the current value matches this state
|
270
|
+
machine.define_helper(:instance, predicate) do |machine, object|
|
271
|
+
machine.states.matches?(object, name)
|
272
|
+
end
|
273
|
+
else
|
274
|
+
# Only output a warning since we can't defined the predicate
|
275
|
+
warn "#{owner_class.name}##{predicate} is already defined, use #{owner_class.name}##{machine.name}?(:#{name}) instead."
|
257
276
|
end
|
258
277
|
end
|
259
278
|
end
|
@@ -4,7 +4,7 @@ module StateMachine
|
|
4
4
|
# Represents a collection of states in a state machine
|
5
5
|
class StateCollection < NodeCollection
|
6
6
|
def initialize(machine) #:nodoc:
|
7
|
-
super(machine, :index => [:name, :value])
|
7
|
+
super(machine, :index => [:name, :qualified_name, :value])
|
8
8
|
end
|
9
9
|
|
10
10
|
# Determines whether the given object is in a specific state. If the
|
@@ -1,8 +1,55 @@
|
|
1
1
|
require 'state_machine/transition_collection'
|
2
|
+
require 'state_machine/error'
|
2
3
|
|
3
4
|
module StateMachine
|
4
5
|
# An invalid transition was attempted
|
5
|
-
class InvalidTransition <
|
6
|
+
class InvalidTransition < Error
|
7
|
+
# The machine attempting to be transitioned
|
8
|
+
attr_reader :machine
|
9
|
+
|
10
|
+
# The current state value for the machine
|
11
|
+
attr_reader :from
|
12
|
+
|
13
|
+
def initialize(object, machine, event) #:nodoc:
|
14
|
+
@machine = machine
|
15
|
+
@from_state = machine.states.match!(object)
|
16
|
+
@from = machine.read(object, :state)
|
17
|
+
@event = machine.events.fetch(event)
|
18
|
+
|
19
|
+
super(object, "Cannot transition #{machine.name} via :#{self.event} from #{from_name.inspect}")
|
20
|
+
end
|
21
|
+
|
22
|
+
# The event that triggered the failed transition
|
23
|
+
def event
|
24
|
+
@event.name
|
25
|
+
end
|
26
|
+
|
27
|
+
# The fully-qualified name of the event that triggered the failed transition
|
28
|
+
def qualified_event
|
29
|
+
@event.qualified_name
|
30
|
+
end
|
31
|
+
|
32
|
+
# The name for the current state
|
33
|
+
def from_name
|
34
|
+
@from_state.name
|
35
|
+
end
|
36
|
+
|
37
|
+
# The fully-qualified name for the current state
|
38
|
+
def qualified_from_name
|
39
|
+
@from_state.qualified_name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# A set of transition failed to run in parallel
|
44
|
+
class InvalidParallelTransition < Error
|
45
|
+
# The set of events that failed the transition(s)
|
46
|
+
attr_reader :events
|
47
|
+
|
48
|
+
def initialize(object, events) #:nodoc:
|
49
|
+
@events = events
|
50
|
+
|
51
|
+
super(object, "Cannot run events in parallel: #{events * ', '}")
|
52
|
+
end
|
6
53
|
end
|
7
54
|
|
8
55
|
# A transition represents a state change for a specific attribute.
|
@@ -175,6 +222,7 @@ module StateMachine
|
|
175
222
|
# provided, then it will be executed between the before and after callbacks.
|
176
223
|
#
|
177
224
|
# Configuration options:
|
225
|
+
# * +before+ - Whether to run before callbacks.
|
178
226
|
# * +after+ - Whether to run after callbacks. If false, then any around
|
179
227
|
# callbacks will be paused until called again with +after+ enabled.
|
180
228
|
# Default is true.
|
@@ -182,10 +230,10 @@ module StateMachine
|
|
182
230
|
# This will return true if all before callbacks gets executed. After
|
183
231
|
# callbacks will not have an effect on the result.
|
184
232
|
def run_callbacks(options = {}, &block)
|
185
|
-
options = {:after => true}.merge(options)
|
233
|
+
options = {:before => true, :after => true}.merge(options)
|
186
234
|
@success = false
|
187
235
|
|
188
|
-
halted = pausable { before(options[:after], &block) }
|
236
|
+
halted = pausable { before(options[:after], &block) } if options[:before]
|
189
237
|
|
190
238
|
# After callbacks are only run if:
|
191
239
|
# * An around callback didn't halt after yielding
|
@@ -257,6 +305,17 @@ module StateMachine
|
|
257
305
|
@paused_block = nil
|
258
306
|
end
|
259
307
|
|
308
|
+
# Determines equality of transitions by testing whether the object, states,
|
309
|
+
# and event involved in the transition are equal
|
310
|
+
def ==(other)
|
311
|
+
other.instance_of?(self.class) &&
|
312
|
+
other.object == object &&
|
313
|
+
other.machine == machine &&
|
314
|
+
other.from_name == from_name &&
|
315
|
+
other.to_name == to_name &&
|
316
|
+
other.event == event
|
317
|
+
end
|
318
|
+
|
260
319
|
# Generates a nicely formatted description of this transitions's contents.
|
261
320
|
#
|
262
321
|
# For example,
|
@@ -341,7 +400,7 @@ module StateMachine
|
|
341
400
|
before(complete, index, &block)
|
342
401
|
|
343
402
|
pause if @success && !complete
|
344
|
-
throw :cancel, true unless
|
403
|
+
throw :cancel, true unless @success
|
345
404
|
end
|
346
405
|
end
|
347
406
|
else
|
@@ -375,8 +434,8 @@ module StateMachine
|
|
375
434
|
# First resume previously paused callbacks
|
376
435
|
if resume
|
377
436
|
catch(:halt) do
|
378
|
-
|
379
|
-
machine.callbacks[
|
437
|
+
type = @success ? :after : :failure
|
438
|
+
machine.callbacks[type].each {|callback| callback.call(object, context, self)}
|
380
439
|
end
|
381
440
|
end
|
382
441
|
|