state_machine 0.9.4 → 0.10.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 +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
|
|