davidlee-state-fu 0.10.0 → 0.11.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/lib/state-fu.rb +1 -0
- data/lib/state_fu/binding.rb +56 -74
- data/lib/state_fu/exceptions.rb +6 -5
- data/lib/state_fu/method_factory.rb +91 -51
- data/lib/state_fu/nil_transition.rb +49 -0
- data/lib/state_fu/transition.rb +62 -66
- data/lib/state_fu/transition_query.rb +52 -19
- data/spec/integration/active_record_persistence_spec.rb +1 -1
- data/spec/integration/example_02_string_spec.rb +3 -3
- data/spec/integration/requirement_reflection_spec.rb +1 -1
- data/spec/integration/transition_spec.rb +10 -53
- data/spec/state_fu_spec.rb +16 -9
- data/spec/units/binding_spec.rb +11 -10
- data/spec/units/exceptions_spec.rb +8 -8
- data/spec/units/method_factory_spec.rb +20 -25
- metadata +4 -4
data/lib/state-fu.rb
CHANGED
data/lib/state_fu/binding.rb
CHANGED
|
@@ -27,10 +27,10 @@ module StateFu
|
|
|
27
27
|
@machine.helpers.inject_into( self )
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
alias_method :o,
|
|
31
|
-
alias_method :obj,
|
|
32
|
-
alias_method :model,
|
|
33
|
-
alias_method :instance,
|
|
30
|
+
alias_method :o, :object
|
|
31
|
+
alias_method :obj, :object
|
|
32
|
+
alias_method :model, :object
|
|
33
|
+
alias_method :instance, :object
|
|
34
34
|
|
|
35
35
|
alias_method :workflow, :machine
|
|
36
36
|
alias_method :state_machine, :machine
|
|
@@ -70,12 +70,6 @@ module StateFu
|
|
|
70
70
|
end
|
|
71
71
|
alias_method :events_from_current_state, :events
|
|
72
72
|
|
|
73
|
-
# the subset of events() whose requirements for firing are NOT met
|
|
74
|
-
# (with the arguments supplied, if any)
|
|
75
|
-
def invalid_events( *args )
|
|
76
|
-
( events - valid_events( *args ) ).extend StateArray
|
|
77
|
-
end
|
|
78
|
-
|
|
79
73
|
# all states which can be reached from the current_state.
|
|
80
74
|
# Does not check transition requirements, etc.
|
|
81
75
|
def next_states
|
|
@@ -95,12 +89,17 @@ module StateFu
|
|
|
95
89
|
end
|
|
96
90
|
|
|
97
91
|
def valid_next_states(*args)
|
|
98
|
-
|
|
92
|
+
valid_transitions(*args).targets
|
|
99
93
|
end
|
|
100
94
|
|
|
101
95
|
def valid_events(*args)
|
|
102
|
-
|
|
96
|
+
valid_transitions(*args).events
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def invalid_events(*args)
|
|
100
|
+
(events - valid_events(*args)).extend StateArray
|
|
103
101
|
end
|
|
102
|
+
|
|
104
103
|
|
|
105
104
|
# initializes a new Transition to the given destination, with the
|
|
106
105
|
# given *args (to be passed to requirements and hooks).
|
|
@@ -110,33 +109,6 @@ module StateFu
|
|
|
110
109
|
def transition( event_or_array, *args, &block )
|
|
111
110
|
return transitions.with(*args, &block).find(event_or_array)
|
|
112
111
|
end
|
|
113
|
-
alias_method :fire, :transition
|
|
114
|
-
alias_method :fire_event, :transition
|
|
115
|
-
alias_method :trigger, :transition
|
|
116
|
-
alias_method :trigger_event, :transition
|
|
117
|
-
alias_method :begin_transition, :transition
|
|
118
|
-
|
|
119
|
-
# check that the event and target are valid (all requirements are
|
|
120
|
-
# met) with the given (optional) arguments
|
|
121
|
-
def fireable?( event_or_array, *args )
|
|
122
|
-
begin
|
|
123
|
-
return nil unless t = transition( event_or_array, *args )
|
|
124
|
-
!! t.requirements_met?
|
|
125
|
-
rescue InvalidTransition => e
|
|
126
|
-
nil
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# construct an event transition and fire it, returning the transition.
|
|
131
|
-
# (which is == true if the transition completed successfully.)
|
|
132
|
-
def fire!( event_or_array, *args, &block)
|
|
133
|
-
# TODO rather than die, try to find the next valid transition and fire that
|
|
134
|
-
t = transition(event_or_array, *args, &block )
|
|
135
|
-
t.fire!
|
|
136
|
-
end
|
|
137
|
-
alias_method :trigger!, :fire!
|
|
138
|
-
alias_method :transition!, :fire!
|
|
139
|
-
|
|
140
112
|
#
|
|
141
113
|
# next_transition and friends: when there's exactly one valid move
|
|
142
114
|
#
|
|
@@ -155,7 +127,7 @@ module StateFu
|
|
|
155
127
|
# if there is exactly one state reachable via a transition which
|
|
156
128
|
# is valid with the given optional arguments, return it.
|
|
157
129
|
def next_state(*args, &block)
|
|
158
|
-
transitions.with(*args, &block).next_state
|
|
130
|
+
transitions.with(*args, &block).next_state
|
|
159
131
|
end
|
|
160
132
|
|
|
161
133
|
# if there is exactly one event which is valid with the given
|
|
@@ -165,7 +137,7 @@ module StateFu
|
|
|
165
137
|
end
|
|
166
138
|
|
|
167
139
|
# if there is a next_transition, create, fire & return it
|
|
168
|
-
# otherwise raise an
|
|
140
|
+
# otherwise raise an IllegalTransition
|
|
169
141
|
def next!( *args, &block )
|
|
170
142
|
if t = next_transition( *args, &block )
|
|
171
143
|
t.fire!
|
|
@@ -176,7 +148,7 @@ module StateFu
|
|
|
176
148
|
alias_method :next_transition!, :next!
|
|
177
149
|
alias_method :next_event!, :next!
|
|
178
150
|
alias_method :next_state!, :next!
|
|
179
|
-
|
|
151
|
+
|
|
180
152
|
# if there is a next_transition, return true / false depending on
|
|
181
153
|
# whether its requirements are met
|
|
182
154
|
# otherwise, nil
|
|
@@ -202,13 +174,12 @@ module StateFu
|
|
|
202
174
|
end
|
|
203
175
|
|
|
204
176
|
# if there is a single possible cycle() transition, fire and return it
|
|
205
|
-
# otherwise raise an
|
|
177
|
+
# otherwise raise an IllegalTransition
|
|
206
178
|
def cycle!(event_or_array=nil, *args, &block )
|
|
207
|
-
|
|
179
|
+
returning cycle(event_or_array, *args, &block ) do |t|
|
|
180
|
+
raise TransitionNotFound.new( self, transitions.cyclic.with(*args,&block), "Cannot cycle! unless there is exactly one cyclic event") \
|
|
181
|
+
if t.nil?
|
|
208
182
|
t.fire!
|
|
209
|
-
t
|
|
210
|
-
else
|
|
211
|
-
raise TransitionNotFound.new( self, transitions.cyclic.with(*args,&block), "Cannot cycle! unless there is exactly one cyclic event")
|
|
212
183
|
end
|
|
213
184
|
end
|
|
214
185
|
|
|
@@ -233,13 +204,13 @@ module StateFu
|
|
|
233
204
|
|
|
234
205
|
# display something sensible that doesn't take up the whole screen
|
|
235
206
|
def inspect
|
|
236
|
-
'
|
|
207
|
+
'<#' + self.class.to_s + ' ' +
|
|
237
208
|
attrs = [[:current_state, state_name.inspect],
|
|
238
209
|
[:object_type , @object.class],
|
|
239
210
|
[:method_name , method_name.inspect],
|
|
240
211
|
[:field_name , field_name.inspect],
|
|
241
|
-
[:machine , machine.
|
|
242
|
-
map {|x| x.join('=') }.join( " " ) + '
|
|
212
|
+
[:machine , machine.to_s]].
|
|
213
|
+
map {|x| x.join('=') }.join( " " ) + '>'
|
|
243
214
|
end
|
|
244
215
|
|
|
245
216
|
# let's be == (and hence ===) the current_state_name as a symbol.
|
|
@@ -267,31 +238,42 @@ module StateFu
|
|
|
267
238
|
self
|
|
268
239
|
end
|
|
269
240
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
target = target_or_options.to_sym
|
|
281
|
-
when nil
|
|
282
|
-
target = nil
|
|
283
|
-
end
|
|
241
|
+
def inspect
|
|
242
|
+
s = self.to_s
|
|
243
|
+
s = s[0,s.length-1]
|
|
244
|
+
s << " object=#{object}"
|
|
245
|
+
s << " current_state=#{current_state.to_sym.inspect rescue nil}"
|
|
246
|
+
s << " events=#{events.map(&:to_sym).inspect rescue nil}"
|
|
247
|
+
s << " machine=#{machine.to_s}"
|
|
248
|
+
s << ">"
|
|
249
|
+
s
|
|
250
|
+
end
|
|
284
251
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
252
|
+
# These methods are called from methods defined by MethodFactory.
|
|
253
|
+
# You probably don't want to call them directly.
|
|
254
|
+
|
|
255
|
+
# event_name
|
|
256
|
+
def find_transition(event, target=nil, *args)
|
|
257
|
+
target ||= args.last[:to].to_sym rescue nil
|
|
258
|
+
query = transitions.for_event(event).to(target).with(*args)
|
|
259
|
+
query.find || query.valid.singular || NilTransition.new
|
|
260
|
+
# transition = binding.transitions.with(*args).search([event, target])
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# event_name?
|
|
264
|
+
def can_transition?(event, target=nil, *args)
|
|
265
|
+
begin
|
|
266
|
+
if t = find_transition(event, target, *args)
|
|
267
|
+
t.valid?(*args)
|
|
268
|
+
end
|
|
269
|
+
rescue IllegalTransition, UnknownTarget
|
|
270
|
+
nil
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# event_name!
|
|
275
|
+
def fire_transition!(event, target=nil, *args)
|
|
276
|
+
find_transition(event, target, *args).fire!
|
|
295
277
|
end
|
|
296
278
|
|
|
297
279
|
#
|
data/lib/state_fu/exceptions.rb
CHANGED
|
@@ -92,14 +92,15 @@ module StateFu
|
|
|
92
92
|
class TransitionHalted < TransitionError
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
# deprecated?
|
|
96
|
+
# class Invalid Transition < TransitionError
|
|
97
|
+
|
|
98
|
+
class IllegalTransition < TransitionError
|
|
99
|
+
attr_reader :legal_transitions
|
|
97
100
|
|
|
98
101
|
def initialize transition, message=nil, valid_transitions=nil, options={}
|
|
99
102
|
@valid_transitions = valid_transitions
|
|
100
103
|
super transition, message, options
|
|
101
|
-
end
|
|
102
|
-
|
|
104
|
+
end
|
|
103
105
|
end
|
|
104
|
-
|
|
105
106
|
end
|
|
@@ -6,63 +6,103 @@ module StateFu
|
|
|
6
6
|
# complex events will be called as: event_name! :state, *args
|
|
7
7
|
|
|
8
8
|
class MethodFactory
|
|
9
|
-
|
|
9
|
+
attr_accessor :method_definitions
|
|
10
|
+
attr_reader :binding
|
|
11
|
+
|
|
10
12
|
# An instance of MethodFactory is created to define methods on a specific StateFu::Binding, and
|
|
11
|
-
# the object it is bound to.
|
|
12
|
-
|
|
13
|
-
# During the initializer, it will call define_event_methods_on(the binding), which installs
|
|
14
|
-
#
|
|
13
|
+
# on the object it is bound to.
|
|
14
|
+
|
|
15
15
|
def initialize( _binding )
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
@binding = _binding
|
|
17
|
+
simple_events, complex_events = @binding.machine.events.partition(&:simple?)
|
|
18
|
+
@method_definitions = {}
|
|
19
|
+
|
|
20
|
+
# simple event methods
|
|
21
|
+
# all arguments are passed into the transition / transition query
|
|
22
|
+
|
|
23
|
+
simple_events.each do |event|
|
|
24
|
+
method_definitions["#{event.name}"] = lambda do |*args|
|
|
25
|
+
_binding.find_transition(event, event.target, *args)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
method_definitions["can_#{event.name}?"] = lambda do |*args|
|
|
29
|
+
_binding.can_transition?(event, event.target, *args)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
method_definitions["#{event.name}!"] = lambda do |*args|
|
|
33
|
+
_binding.fire_transition!(event, event.target, *args)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# complex event methods
|
|
38
|
+
# the first argument is the target state
|
|
39
|
+
# any remaining arguments are passed into the transition / transition query
|
|
40
|
+
|
|
41
|
+
# object.event_name [:target], *arguments
|
|
42
|
+
#
|
|
43
|
+
# returns a new transition. Will raise an IllegalTransition if
|
|
44
|
+
# it is not given arguments which result in a valid combination
|
|
45
|
+
# of event and target state being deducted.
|
|
46
|
+
#
|
|
47
|
+
# object.event_name [nil] suffices if the event has only one valid
|
|
48
|
+
# target (ie only one transition which would not raise a
|
|
49
|
+
# RequirementError if fired)
|
|
50
|
+
|
|
51
|
+
# object.event_name! [:target], *arguments
|
|
52
|
+
#
|
|
53
|
+
# as per the method above, except that it also fires the event
|
|
54
|
+
|
|
55
|
+
# object.can_event_name? [:target], *arguments
|
|
56
|
+
#
|
|
57
|
+
# tests that calling event_name or event_name! would not raise an error
|
|
58
|
+
# ie, the transition is legal and is valid with the arguments supplied
|
|
59
|
+
|
|
60
|
+
complex_events.each do |event|
|
|
61
|
+
method_definitions["#{event.name}"] = lambda do |target, *args|
|
|
62
|
+
_binding.find_transition(event, target, *args)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
method_definitions["can_#{event.name}?"] = lambda do |target, *args|
|
|
66
|
+
begin
|
|
67
|
+
t = _binding.find_transition(event, target, *args)
|
|
68
|
+
t.valid?
|
|
69
|
+
rescue IllegalTransition
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
end
|
|
18
73
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
74
|
+
method_definitions["#{event.name}!"] = lambda do |target, *args|
|
|
75
|
+
_binding.fire_transition!(event, target, *args)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# methods dedicated to a combination of event and target
|
|
80
|
+
# all arguments are passed into the transition / transition query
|
|
81
|
+
|
|
82
|
+
(simple_events + complex_events).each do |event|
|
|
83
|
+
event.targets.each do |target|
|
|
84
|
+
method_definitions["#{event.name}_to_#{target.name}"] = lambda do |*args|
|
|
85
|
+
_binding.find_transition(event, target, *args)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
method_definitions["can_#{event.name}_to_#{target.name}?"] = lambda do |*args|
|
|
89
|
+
_binding.can_transition?(event, target, *args)
|
|
90
|
+
end
|
|
22
91
|
|
|
92
|
+
method_definitions["#{event.name}_to_#{target.name}!"] = lambda do |*args|
|
|
93
|
+
_binding.fire_transition!(event, target, *args)
|
|
94
|
+
end
|
|
95
|
+
end unless event.targets.nil?
|
|
96
|
+
end
|
|
97
|
+
|
|
23
98
|
@binding.machine.states.each do |state|
|
|
24
|
-
|
|
99
|
+
method_definitions["#{state.name}?"] = lambda do
|
|
100
|
+
_binding.current_state == state
|
|
101
|
+
end
|
|
25
102
|
end
|
|
26
103
|
|
|
27
|
-
|
|
28
|
-
@binding.machine.events.each do |event|
|
|
29
|
-
@defs[event.name] = lambda \
|
|
30
|
-
{|*args| _binding._event_method :get_transition, event, args.shift, *args }
|
|
31
|
-
@defs[:"can_#{event.name}?"] = lambda \
|
|
32
|
-
{|*args| _binding._event_method :query_transition, event, args.shift, *args }
|
|
33
|
-
@defs[:"#{event.name}!"] = lambda \
|
|
34
|
-
{|*args| _binding._event_method :fire_transition, event, args.shift, *args }
|
|
35
|
-
|
|
36
|
-
#if !event.targets.blank? # && event.targets.length > 1
|
|
37
|
-
event.targets.each do |target_state|
|
|
38
|
-
method_name = "#{event.name}_to_#{target_state.name}"
|
|
39
|
-
|
|
40
|
-
# object.event_name [:target], *arguments
|
|
41
|
-
#
|
|
42
|
-
# returns a new transition. Will raise an InvalidTransition if
|
|
43
|
-
# it is not given arguments which result in a valid combination
|
|
44
|
-
# of event and target state being deducted.
|
|
45
|
-
#
|
|
46
|
-
# object.event_name suffices without any arguments if the event
|
|
47
|
-
# has only one possible target, or only one valid target for
|
|
104
|
+
end
|
|
48
105
|
|
|
49
|
-
# object.event_name! [:target], *arguments
|
|
50
|
-
#
|
|
51
|
-
# as per the method above, except that it also
|
|
52
|
-
|
|
53
|
-
@defs[method_name.to_sym] = lambda \
|
|
54
|
-
{|*args| _binding._event_method :get_transition, event, target_state, *args }
|
|
55
|
-
|
|
56
|
-
# object.event_name! [:]
|
|
57
|
-
@defs[:"can_#{method_name}?"] = lambda \
|
|
58
|
-
{|*args| _binding._event_method :query_transition, event, target_state, *args }
|
|
59
|
-
|
|
60
|
-
@defs[:"#{method_name}!"] = lambda \
|
|
61
|
-
{|*args| _binding._event_method :fire_transition, event, target_state, *args }
|
|
62
|
-
|
|
63
|
-
end unless event.targets.nil?
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
106
|
|
|
67
107
|
#
|
|
68
108
|
# Class Methods
|
|
@@ -163,7 +203,7 @@ module StateFu
|
|
|
163
203
|
# as with simple event methods.
|
|
164
204
|
#
|
|
165
205
|
def define_event_methods_on( obj )
|
|
166
|
-
|
|
206
|
+
method_definitions.each do |method_name, method_body|
|
|
167
207
|
define_singleton_method( obj, method_name, &method_body)
|
|
168
208
|
end
|
|
169
209
|
end # define_event_methods_on
|
|
@@ -196,7 +236,7 @@ module StateFu
|
|
|
196
236
|
end
|
|
197
237
|
end
|
|
198
238
|
alias_method :define_singleton_method, :define_singleton_method
|
|
199
|
-
|
|
239
|
+
|
|
200
240
|
end # class MethodFactory
|
|
201
241
|
end # module StateFu
|
|
202
242
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module StateFu
|
|
2
|
+
|
|
3
|
+
class NilTransition
|
|
4
|
+
def method_missing(method_name, *args, &block)
|
|
5
|
+
nil
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def blank?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def nil?
|
|
13
|
+
true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# def <=> x
|
|
17
|
+
# false <=> x
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# def | x
|
|
21
|
+
# false
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# def & x
|
|
25
|
+
# false
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# def ^ x
|
|
29
|
+
# false
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# def equal? x
|
|
33
|
+
# x.is_a? NilTransition || x == nil
|
|
34
|
+
# end
|
|
35
|
+
|
|
36
|
+
def == x
|
|
37
|
+
case x
|
|
38
|
+
when false
|
|
39
|
+
true
|
|
40
|
+
when true
|
|
41
|
+
false
|
|
42
|
+
else
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
data/lib/state_fu/transition.rb
CHANGED
|
@@ -23,57 +23,67 @@ module StateFu
|
|
|
23
23
|
:current_hook_slot,
|
|
24
24
|
:current_hook
|
|
25
25
|
|
|
26
|
-
attr_accessor :test_only
|
|
27
26
|
alias_method :arguments, :args
|
|
28
27
|
|
|
29
28
|
def initialize( binding, event, target=nil, *argument_list, &block )
|
|
30
|
-
# ensure
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
end
|
|
34
|
-
raise( ArgumentError, "Not an event: #{event}" ) unless event.is_a? Event
|
|
29
|
+
# ensure we have an Event
|
|
30
|
+
event = binding.machine.events[event] if event.is_a?(Symbol)
|
|
31
|
+
raise( UnknownTarget.new(self, "Not an event: #{event} #{self.inspect}" )) unless event.is_a? Event
|
|
35
32
|
|
|
36
33
|
@binding = binding
|
|
37
34
|
@machine = binding.machine
|
|
38
35
|
@object = binding.object
|
|
39
36
|
@origin = binding.current_state
|
|
40
37
|
|
|
41
|
-
self.args= argument_list
|
|
42
|
-
apply!(argument_list, &block )
|
|
43
|
-
|
|
44
38
|
# ensure we have a target
|
|
45
39
|
target = find_event_target( event, target ) || raise( UnknownTarget.new(self, "target cannot be determined: #{target.inspect} #{self.inspect}"))
|
|
46
40
|
|
|
47
41
|
@target = target
|
|
48
42
|
@event = event
|
|
49
43
|
@errors = []
|
|
50
|
-
@testing = @options.delete(:test_only)
|
|
51
44
|
|
|
52
45
|
if event.target_for_origin(origin) == target
|
|
53
|
-
#
|
|
46
|
+
# it's a "sequence"
|
|
47
|
+
# which is a hacky way of emulating simpler state machines with
|
|
48
|
+
# state-local events - and in which case, the targets & origins are
|
|
49
|
+
# valid. Quite likely this notion will be removed in time.
|
|
54
50
|
else
|
|
55
51
|
# ensure target is valid for the event
|
|
56
52
|
unless event.targets.include? target
|
|
57
|
-
raise
|
|
53
|
+
raise IllegalTransition.new self, "Illegal target #{target} for #{event}"
|
|
58
54
|
end
|
|
59
55
|
|
|
60
56
|
# ensure current_state is a valid origin for the event
|
|
61
57
|
unless event.origins.include? origin
|
|
62
|
-
raise
|
|
58
|
+
raise IllegalTransition.new( self, "Illegal event #{event.name} for current state #{binding.state_name}" )
|
|
63
59
|
end
|
|
64
60
|
end
|
|
65
61
|
|
|
66
62
|
machine.inject_helpers_into( self )
|
|
63
|
+
self.args = argument_list
|
|
64
|
+
apply!(argument_list, &block )
|
|
67
65
|
end
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def valid?(*args)
|
|
69
|
+
self.args = args unless args.empty?
|
|
70
|
+
requirements_met?(true, true) # revalidate; exit on first failure
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def args=(args)
|
|
74
|
+
@args = args.extend(TransitionArgsArray).init(self)
|
|
75
|
+
apply!(args) if args.last.is_a?(Hash) unless options.nil?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def with(*args)
|
|
79
|
+
self.args = args unless args.empty?
|
|
80
|
+
self
|
|
71
81
|
end
|
|
72
82
|
|
|
73
|
-
def args
|
|
74
|
-
|
|
75
|
-
apply!(a) if a.last.is_a?(Hash)
|
|
83
|
+
def with?(*args)
|
|
84
|
+
valid?
|
|
76
85
|
end
|
|
86
|
+
alias_method :valid_with?, :with?
|
|
77
87
|
|
|
78
88
|
#
|
|
79
89
|
# Requirements
|
|
@@ -83,18 +93,20 @@ module StateFu
|
|
|
83
93
|
origin.exit_requirements + target.entry_requirements + event.requirements
|
|
84
94
|
end
|
|
85
95
|
|
|
86
|
-
def unmet_requirements(revalidate=false, fail_fast=false)
|
|
96
|
+
def unmet_requirements(revalidate=false, fail_fast=false)
|
|
87
97
|
if revalidate
|
|
88
|
-
return @unmet_requirements if @unmet_requirements
|
|
89
|
-
else
|
|
90
98
|
@unmet_requirements = nil
|
|
99
|
+
else
|
|
100
|
+
return @unmet_requirements if @unmet_requirements
|
|
91
101
|
end
|
|
92
|
-
result = requirements.uniq.inject([]) do |unmet, requirement|
|
|
93
|
-
next if fail_fast && !unmet.empty?
|
|
102
|
+
result = [requirements].flatten.uniq.inject([]) do |unmet, requirement|
|
|
94
103
|
unmet << requirement unless evaluate(requirement)
|
|
104
|
+
break(unmet) if fail_fast && !unmet.empty?
|
|
95
105
|
unmet
|
|
96
106
|
end
|
|
97
|
-
|
|
107
|
+
raise self.inspect if result.nil?
|
|
108
|
+
# don't cache result if it might
|
|
109
|
+
@unmet_requirements = result unless (fail_fast && unmet_requirements.length != 0)
|
|
98
110
|
result
|
|
99
111
|
end
|
|
100
112
|
|
|
@@ -104,25 +116,23 @@ module StateFu
|
|
|
104
116
|
|
|
105
117
|
def unmet_requirement_messages(revalidate=false, fail_fast=false) # TODO
|
|
106
118
|
unmet_requirements(revalidate, fail_fast).map do |requirement|
|
|
107
|
-
evaluate_requirement_message
|
|
119
|
+
evaluate_requirement_message(requirement, revalidate)
|
|
108
120
|
end.extend MessageArray
|
|
109
121
|
end
|
|
110
122
|
alias_method :error_messages, :unmet_requirement_messages
|
|
111
|
-
|
|
123
|
+
|
|
124
|
+
# return a hash of requirement_name => evaluated message
|
|
112
125
|
def requirement_errors(revalidate=false, fail_fast=false)
|
|
113
126
|
unmet_requirements(revalidate, fail_fast).
|
|
114
127
|
map { |requirement| [requirement, evaluate_requirement_message(requirement)]}.
|
|
115
128
|
to_h
|
|
116
129
|
end
|
|
117
|
-
|
|
118
|
-
def first_unmet_requirement(revalidate=false)
|
|
119
|
-
unmet_requirements(revalidate, fail_fast=true)[0]
|
|
120
|
-
end
|
|
121
|
-
|
|
130
|
+
|
|
122
131
|
def first_unmet_requirement_message(revalidate=false)
|
|
123
|
-
|
|
132
|
+
evaluate_requirement_message(first_unmet_requirement(revalidate), revalidate)
|
|
124
133
|
end
|
|
125
134
|
|
|
135
|
+
# raise a RequirementError unless all requirements are met.
|
|
126
136
|
def check_requirements!(revalidate=false, fail_fast=true) # TODO
|
|
127
137
|
raise RequirementError.new( self, unmet_requirement_messages.inspect ) unless requirements_met?(revalidate, fail_fast)
|
|
128
138
|
end
|
|
@@ -130,7 +140,6 @@ module StateFu
|
|
|
130
140
|
def requirements_met?(revalidate=false, fail_fast=false) # TODO
|
|
131
141
|
unmet_requirements(revalidate, fail_fast).empty?
|
|
132
142
|
end
|
|
133
|
-
alias_method :valid?, :requirements_met?
|
|
134
143
|
|
|
135
144
|
#
|
|
136
145
|
# Hooks
|
|
@@ -149,8 +158,6 @@ module StateFu
|
|
|
149
158
|
evaluate hook
|
|
150
159
|
end
|
|
151
160
|
|
|
152
|
-
|
|
153
|
-
|
|
154
161
|
#
|
|
155
162
|
#
|
|
156
163
|
#
|
|
@@ -166,9 +173,10 @@ module StateFu
|
|
|
166
173
|
#
|
|
167
174
|
|
|
168
175
|
# actually fire the transition
|
|
169
|
-
def fire!
|
|
176
|
+
def fire!(*arguments) # block?
|
|
170
177
|
raise TransitionAlreadyFired.new(self) if fired?
|
|
171
|
-
|
|
178
|
+
self.args = arguments unless arguments.empty?
|
|
179
|
+
|
|
172
180
|
check_requirements!
|
|
173
181
|
@fired = true
|
|
174
182
|
begin
|
|
@@ -218,14 +226,6 @@ module StateFu
|
|
|
218
226
|
!!@fired
|
|
219
227
|
end
|
|
220
228
|
|
|
221
|
-
def testing?
|
|
222
|
-
!!@test_only
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def live?
|
|
226
|
-
!testing?
|
|
227
|
-
end
|
|
228
|
-
|
|
229
229
|
def accepted?
|
|
230
230
|
!!@accepted
|
|
231
231
|
end
|
|
@@ -256,12 +256,6 @@ module StateFu
|
|
|
256
256
|
alias_method :initial_state, :origin
|
|
257
257
|
alias_method :from, :origin
|
|
258
258
|
|
|
259
|
-
alias_method :test?, :testing?
|
|
260
|
-
alias_method :test_only?, :testing?
|
|
261
|
-
alias_method :read_only?, :testing?
|
|
262
|
-
alias_method :only_pretend?, :testing?
|
|
263
|
-
alias_method :dry_run?, :testing?
|
|
264
|
-
|
|
265
259
|
# an accepted transition == true
|
|
266
260
|
# an unaccepted transition == false
|
|
267
261
|
# same for === (for case equality)
|
|
@@ -306,26 +300,28 @@ module StateFu
|
|
|
306
300
|
executioner.evaluate(method_name_or_proc)
|
|
307
301
|
end
|
|
308
302
|
|
|
309
|
-
def evaluate_requirement_message( name )
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
303
|
+
def evaluate_requirement_message( name, revalidate=false)
|
|
304
|
+
@requirement_messages ||= {}
|
|
305
|
+
name = name.to_sym
|
|
306
|
+
return @requirement_messages[name] if @requirement_messages[name] && !revalidate
|
|
307
|
+
msg = machine.requirement_messages[name.to_sym]
|
|
308
|
+
result = case msg
|
|
309
|
+
when String
|
|
310
|
+
msg
|
|
311
|
+
when Symbol, Proc
|
|
312
|
+
evaluate msg
|
|
313
|
+
else
|
|
314
|
+
name
|
|
315
|
+
end
|
|
316
|
+
@requirement_messages[name] = result
|
|
321
317
|
end
|
|
322
|
-
|
|
318
|
+
|
|
323
319
|
def find_event_target( evt, tgt )
|
|
324
320
|
case tgt
|
|
325
321
|
when StateFu::State
|
|
326
322
|
tgt
|
|
327
323
|
when Symbol
|
|
328
|
-
binding && binding.machine.states[ tgt ]
|
|
324
|
+
binding && binding.machine.states[ tgt ]
|
|
329
325
|
when NilClass
|
|
330
326
|
evt.respond_to?(:target) && evt.target
|
|
331
327
|
else
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module StateFu
|
|
2
|
-
class TransitionQuery
|
|
2
|
+
class TransitionQuery
|
|
3
3
|
attr_accessor :binding, :options, :result, :args, :block
|
|
4
4
|
|
|
5
5
|
def initialize(binding, options={})
|
|
@@ -23,20 +23,46 @@ module StateFu
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
#
|
|
26
|
-
#
|
|
26
|
+
#
|
|
27
27
|
#
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
# same as find, except that if there is more than one target for the event
|
|
30
|
+
# and only one is valid, it will return that one.
|
|
31
|
+
# def search(destination=nil, &block)
|
|
32
|
+
# # use the prepared event & target if none are supplied
|
|
33
|
+
# event, target = destination.nil? ? [options[:event], options[:target]] : parse_destination(destination)
|
|
34
|
+
# query = for_event(event).to(target)
|
|
35
|
+
# query.find || query.valid.singular || NilTransition.new
|
|
36
|
+
# end
|
|
37
|
+
|
|
38
|
+
# find a transition by event and optionally (optional if it can be inferred) target.
|
|
39
|
+
def find(destination=nil, &block)
|
|
40
|
+
# use the prepared event & target if none are supplied
|
|
41
|
+
event, target = destination.nil? ? [options[:event], options[:target]] : parse_destination(destination)
|
|
31
42
|
_args, _block = @args, @block
|
|
32
|
-
returning binding.new_transition(event, target) do |
|
|
33
|
-
|
|
43
|
+
returning binding.new_transition(event, target) do |transition|
|
|
44
|
+
# return NilTransition.new if transition.nil?
|
|
45
|
+
transition.apply!(&_block) if _block
|
|
34
46
|
if _args
|
|
35
|
-
|
|
47
|
+
transition.args = _args
|
|
36
48
|
end
|
|
37
49
|
end
|
|
38
50
|
end
|
|
39
51
|
|
|
52
|
+
# def legal?(destination=nil, &block)
|
|
53
|
+
# # use the prepared event & target if none are supplied
|
|
54
|
+
# event, target = destination.nil? ? [options[:event], options[:target]] : parse_destination(destination)
|
|
55
|
+
# begin
|
|
56
|
+
# !!search(destination, &block)
|
|
57
|
+
# rescue IllegalTransition
|
|
58
|
+
# false
|
|
59
|
+
# end
|
|
60
|
+
# end
|
|
61
|
+
|
|
62
|
+
#
|
|
63
|
+
#
|
|
64
|
+
#
|
|
65
|
+
|
|
40
66
|
def cyclic
|
|
41
67
|
@options.merge! :cyclic => true
|
|
42
68
|
self
|
|
@@ -63,7 +89,7 @@ module StateFu
|
|
|
63
89
|
self
|
|
64
90
|
end
|
|
65
91
|
|
|
66
|
-
def
|
|
92
|
+
def for_event event
|
|
67
93
|
@options.merge! :event => event
|
|
68
94
|
self
|
|
69
95
|
end
|
|
@@ -77,9 +103,14 @@ module StateFu
|
|
|
77
103
|
#
|
|
78
104
|
#
|
|
79
105
|
|
|
80
|
-
def
|
|
106
|
+
def only_one
|
|
81
107
|
result.first if result.length == 1
|
|
82
108
|
end
|
|
109
|
+
alias_method :singular, :only_one
|
|
110
|
+
|
|
111
|
+
def only_one?
|
|
112
|
+
!!singular
|
|
113
|
+
end
|
|
83
114
|
|
|
84
115
|
def next
|
|
85
116
|
@options[:cyclic] ||= false
|
|
@@ -118,7 +149,7 @@ module StateFu
|
|
|
118
149
|
|
|
119
150
|
def with(*args, &block)
|
|
120
151
|
@args = args
|
|
121
|
-
@block = block
|
|
152
|
+
@block = block if block_given?
|
|
122
153
|
self
|
|
123
154
|
end
|
|
124
155
|
|
|
@@ -196,17 +227,19 @@ module StateFu
|
|
|
196
227
|
# takes a single, simple (one target only) event,
|
|
197
228
|
# or an array of [event, target],
|
|
198
229
|
# or one of the above with symbols in place of the objects themselves.
|
|
199
|
-
def parse_destination(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
230
|
+
def parse_destination(destination)
|
|
231
|
+
event, target = destination
|
|
232
|
+
|
|
233
|
+
unless event.is_a?(Event)
|
|
234
|
+
event = binding.machine.events[event]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
unless target.is_a?(State)
|
|
238
|
+
target = binding.machine.states[target] rescue nil
|
|
206
239
|
end
|
|
240
|
+
|
|
207
241
|
raise ArgumentError.new( [event,target].inspect ) unless
|
|
208
|
-
[Event,
|
|
209
|
-
[State, Symbol, NilClass].include?(target.class)
|
|
242
|
+
[[Event, State],[Event, NilClass]].include?( [event,target].map(&:class) )
|
|
210
243
|
[event, target]
|
|
211
244
|
end # parse_destination
|
|
212
245
|
|
|
@@ -142,7 +142,7 @@ describe "an ActiveRecord model with StateFu included:" do
|
|
|
142
142
|
ex = ExampleRecord.create!( :name => "exemplar" )
|
|
143
143
|
ex.state_fu.state.name.should == :initial
|
|
144
144
|
ex.state_fu_field.should == 'initial'
|
|
145
|
-
t =
|
|
145
|
+
t = ex.state_fu.change!
|
|
146
146
|
t.should be_accepted
|
|
147
147
|
ex.state_fu.state.name.should == :final
|
|
148
148
|
ex.state_fu_field.should == 'final'
|
|
@@ -28,7 +28,7 @@ describe String do
|
|
|
28
28
|
klone = clone
|
|
29
29
|
begin
|
|
30
30
|
klone.shell.escape!
|
|
31
|
-
rescue StateFu::
|
|
31
|
+
rescue StateFu::IllegalTransition
|
|
32
32
|
end
|
|
33
33
|
klone
|
|
34
34
|
end
|
|
@@ -55,11 +55,11 @@ describe String do
|
|
|
55
55
|
@str.should be_clean
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
it "should raise an
|
|
58
|
+
it "should raise an IllegalTransition if shell.escape! is called more than once" do
|
|
59
59
|
@str.shell.escape!
|
|
60
60
|
@str.shell.state_name.should == :clean
|
|
61
61
|
|
|
62
|
-
lambda { @str.shell.escape! }.should raise_error( StateFu::
|
|
62
|
+
lambda { @str.shell.escape! }.should raise_error( StateFu::IllegalTransition )
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
it "should modify the string when shell.escape is called" do
|
|
@@ -90,7 +90,7 @@ describe "Transition requirement reflection" do
|
|
|
90
90
|
end # unmet requirements
|
|
91
91
|
|
|
92
92
|
describe "given transition.unmet_requirement_messages" do
|
|
93
|
-
it "should return a list of
|
|
93
|
+
it "should return a list of symbols" do
|
|
94
94
|
@obj.state_fu.catch_plane(:america).unmet_requirement_messages.should ==
|
|
95
95
|
[:papers_in_order?, :money_for_bribe?, :no_turban?, :us_visa?, :no_arrest_warrant?]
|
|
96
96
|
end
|
|
@@ -88,15 +88,6 @@ describe StateFu::Transition do
|
|
|
88
88
|
@t.should_not be_halted
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
it "should not be testing" do
|
|
92
|
-
@t.should_not be_test
|
|
93
|
-
@t.should_not be_testing
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it "should be live" do
|
|
97
|
-
@t.should be_live
|
|
98
|
-
end
|
|
99
|
-
|
|
100
91
|
it "should not be accepted" do
|
|
101
92
|
@t.should_not be_accepted
|
|
102
93
|
end
|
|
@@ -143,15 +134,6 @@ describe StateFu::Transition do
|
|
|
143
134
|
@t.should_not be_halted
|
|
144
135
|
end
|
|
145
136
|
|
|
146
|
-
it "should not be testing" do
|
|
147
|
-
@t.should_not be_test
|
|
148
|
-
@t.should_not be_testing
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
it "should be live" do
|
|
152
|
-
@t.should be_live
|
|
153
|
-
end
|
|
154
|
-
|
|
155
137
|
it "should be accepted" do
|
|
156
138
|
@t.should be_accepted
|
|
157
139
|
end
|
|
@@ -204,14 +186,8 @@ describe StateFu::Transition do
|
|
|
204
186
|
trans.args.should == []
|
|
205
187
|
end
|
|
206
188
|
|
|
207
|
-
it "should be a live? transition, not a test?" do
|
|
208
|
-
trans = @obj.state_fu.transition( :transfer )
|
|
209
|
-
trans.should be_live
|
|
210
|
-
trans.should_not be_test
|
|
211
|
-
end
|
|
212
|
-
|
|
213
189
|
it "should define any methods declared in a block given to .transition" do
|
|
214
|
-
trans = @obj.state_fu.transition( :transfer ) do
|
|
190
|
+
trans = @obj.state_fu.transition( :transfer ) do
|
|
215
191
|
def snoo
|
|
216
192
|
return [self]
|
|
217
193
|
end
|
|
@@ -232,25 +208,14 @@ describe StateFu::Transition do
|
|
|
232
208
|
|
|
233
209
|
describe "state_fu.fire!( :transfer )" do
|
|
234
210
|
it "should change the state when called" do
|
|
235
|
-
@obj.state_fu.should respond_to( :
|
|
211
|
+
@obj.state_fu.should respond_to( :fire_transition! )
|
|
236
212
|
@obj.state_fu.state.should == @origin
|
|
237
|
-
@obj.state_fu.
|
|
213
|
+
@obj.state_fu.fire_transition!( :transfer )
|
|
238
214
|
@obj.state_fu.state.should == @target
|
|
239
215
|
end
|
|
240
216
|
|
|
241
|
-
it "should define any methods declared in the .fire! block" do
|
|
242
|
-
trans = @obj.state_fu.fire!( :transfer ) do
|
|
243
|
-
def snoo
|
|
244
|
-
return [self]
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
trans.should be_kind_of( StateFu::Transition )
|
|
248
|
-
trans.should respond_to(:snoo)
|
|
249
|
-
trans.snoo.should == [trans]
|
|
250
|
-
end
|
|
251
|
-
|
|
252
217
|
it "should return a transition object" do
|
|
253
|
-
@obj.state_fu.
|
|
218
|
+
@obj.state_fu.fire_transition!( :transfer ).should be_kind_of( StateFu::Transition )
|
|
254
219
|
end
|
|
255
220
|
|
|
256
221
|
end # state_fu.fire!
|
|
@@ -310,14 +275,6 @@ describe StateFu::Transition do
|
|
|
310
275
|
end
|
|
311
276
|
end
|
|
312
277
|
|
|
313
|
-
describe "calling fire!( :transfer, :a, :b, :c => :d )" do
|
|
314
|
-
it "should set args and options on the transition" do
|
|
315
|
-
t = @obj.state_fu.fire!( :transfer, *@args )
|
|
316
|
-
t.args.should == [ :a, :b, {:c =>:d} ]
|
|
317
|
-
t.options.should == { :c => :d }
|
|
318
|
-
end
|
|
319
|
-
end
|
|
320
|
-
|
|
321
278
|
describe "calling next!( :a, :b, :c => :d )" do
|
|
322
279
|
it "should set args and options on the transition" do
|
|
323
280
|
t = @obj.state_fu.next!( *@args )
|
|
@@ -443,14 +400,14 @@ describe StateFu::Transition do
|
|
|
443
400
|
end
|
|
444
401
|
end # state_fu.transition
|
|
445
402
|
|
|
446
|
-
describe "state_fu.
|
|
403
|
+
describe "state_fu.fire_transition!" do
|
|
447
404
|
it "should raise an StateFu::UnknownTarget unless a valid targets state is supplied" do
|
|
448
405
|
lambda do
|
|
449
|
-
@obj.state_fu.
|
|
406
|
+
@obj.state_fu.fire_transition!( :go )
|
|
450
407
|
end.should raise_error( StateFu::UnknownTarget )
|
|
451
408
|
|
|
452
409
|
lambda do
|
|
453
|
-
@obj.state_fu.
|
|
410
|
+
@obj.state_fu.fire_transition!( [ :go, :awol ] )
|
|
454
411
|
end.should raise_error( StateFu::UnknownTarget )
|
|
455
412
|
end
|
|
456
413
|
end # state_fu.fire!
|
|
@@ -797,7 +754,7 @@ describe StateFu::Transition do
|
|
|
797
754
|
#stub( @binding ).ok? { false }
|
|
798
755
|
@obj.ok = false
|
|
799
756
|
lambda do
|
|
800
|
-
@obj.state_fu.
|
|
757
|
+
@obj.state_fu.fire_transition!( :go )
|
|
801
758
|
end.should raise_error( StateFu::RequirementError )
|
|
802
759
|
end
|
|
803
760
|
|
|
@@ -1065,8 +1022,8 @@ describe StateFu::Transition do
|
|
|
1065
1022
|
t.should be_nil
|
|
1066
1023
|
end
|
|
1067
1024
|
|
|
1068
|
-
it "should raise an
|
|
1069
|
-
lambda { @binding.next! }.should raise_error( StateFu::
|
|
1025
|
+
it "should raise an IllegalTransition if next! is called" do
|
|
1026
|
+
lambda { @binding.next! }.should raise_error( StateFu::IllegalTransition )
|
|
1070
1027
|
end
|
|
1071
1028
|
end
|
|
1072
1029
|
|
data/spec/state_fu_spec.rb
CHANGED
|
@@ -22,7 +22,7 @@ describe "A door which opens and shuts:" do
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def method_missing(method_name, *args, &block)
|
|
25
|
-
raise NoMethodError.new("I'm just a door!")
|
|
25
|
+
raise NoMethodError.new("I'm just a door!" )
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
machine do
|
|
@@ -181,6 +181,7 @@ describe "A door which opens and shuts:" do
|
|
|
181
181
|
|
|
182
182
|
it "#can_shut? when the current state is open" do
|
|
183
183
|
@door.current_state.should == :open
|
|
184
|
+
# @door.state_fu.valid_transitions.map(&:destination).inspect
|
|
184
185
|
@door.can_shut?.should == true
|
|
185
186
|
@door.can_open?.should == nil # not a valid transition from this state -> nil
|
|
186
187
|
end
|
|
@@ -194,14 +195,14 @@ describe "A door which opens and shuts:" do
|
|
|
194
195
|
@door.current_state.should == :closed
|
|
195
196
|
end
|
|
196
197
|
|
|
197
|
-
it "raises a StateFu::
|
|
198
|
+
it "raises a StateFu::IllegalTransition if #shut! is called when already :closed" do
|
|
198
199
|
@door.current_state.should == :open
|
|
199
200
|
@door.shut!.should be_true
|
|
200
201
|
@door.current_state.should == :closed
|
|
201
202
|
lambda do
|
|
202
203
|
t = @door.shut!
|
|
203
204
|
t.origin.should == :open
|
|
204
|
-
end.should raise_error(StateFu::
|
|
205
|
+
end.should raise_error(StateFu::IllegalTransition)
|
|
205
206
|
end
|
|
206
207
|
|
|
207
208
|
it "raises StateFu::RequirementError if #open! is called when it is locked" do
|
|
@@ -250,9 +251,15 @@ describe "A door which opens and shuts:" do
|
|
|
250
251
|
transition.target.should == :closed
|
|
251
252
|
end
|
|
252
253
|
|
|
253
|
-
it "returns a Transition on #slam" do
|
|
254
|
-
|
|
255
|
-
|
|
254
|
+
it "returns a Transition on #slam" do
|
|
255
|
+
@door.slam do |transition|
|
|
256
|
+
transition.should be_kind_of(StateFu::Transition)
|
|
257
|
+
transition.fired?.should == false
|
|
258
|
+
transition.current_state.should == :open
|
|
259
|
+
transition.event.should == :slam
|
|
260
|
+
transition.origin.should == :open
|
|
261
|
+
transition.target.should == :closed
|
|
262
|
+
end
|
|
256
263
|
end
|
|
257
264
|
|
|
258
265
|
it "returns a Transition on #state_fu.slam" do
|
|
@@ -396,10 +403,10 @@ describe "a simple machine, a heart which beats:" do
|
|
|
396
403
|
@heart.heartbeats.should == [:thumpthump]
|
|
397
404
|
end
|
|
398
405
|
|
|
399
|
-
it "raise an
|
|
406
|
+
it "raise an IllegalTransition if it tries to beat after it's stopped" do
|
|
400
407
|
@heart.stop!
|
|
401
408
|
@heart.current_state.should == :stopped
|
|
402
|
-
lambda { @heart.beat! }.should raise_error(StateFu::
|
|
409
|
+
lambda { @heart.beat! }.should raise_error(StateFu::IllegalTransition)
|
|
403
410
|
end
|
|
404
411
|
|
|
405
412
|
it "transition to :stopped on #next!" do
|
|
@@ -838,7 +845,7 @@ describe "sitting at a poker machine" do
|
|
|
838
845
|
@pokie.pull_lever!
|
|
839
846
|
@pokie.spinning?.should be_true
|
|
840
847
|
@pokie.can_pull_lever?.should == nil
|
|
841
|
-
lambda{ @pokie.pull_lever! }.should raise_error(StateFu::
|
|
848
|
+
lambda{ @pokie.pull_lever! }.should raise_error(StateFu::IllegalTransition)
|
|
842
849
|
end
|
|
843
850
|
|
|
844
851
|
it "makes a spinning sound while you wait" do
|
data/spec/units/binding_spec.rb
CHANGED
|
@@ -138,7 +138,7 @@ describe StateFu::Binding do
|
|
|
138
138
|
describe "Instance methods" do
|
|
139
139
|
before do
|
|
140
140
|
end
|
|
141
|
-
describe "
|
|
141
|
+
describe "can_transition?" do
|
|
142
142
|
before do
|
|
143
143
|
reset!
|
|
144
144
|
make_pristine_class("Klass")
|
|
@@ -147,7 +147,7 @@ describe StateFu::Binding do
|
|
|
147
147
|
end
|
|
148
148
|
@machine = Klass.state_fu_machine do
|
|
149
149
|
state :snoo do
|
|
150
|
-
event :
|
|
150
|
+
event :fire, :to => :wizz do
|
|
151
151
|
requires :tissue?
|
|
152
152
|
end
|
|
153
153
|
end
|
|
@@ -160,16 +160,15 @@ describe StateFu::Binding do
|
|
|
160
160
|
|
|
161
161
|
describe "when called with arguments which would return a valid transition from .transition()" do
|
|
162
162
|
it "should return true" do
|
|
163
|
-
@obj.state_fu.
|
|
163
|
+
@obj.state_fu.can_transition?(:fire).should == true
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
|
|
167
|
-
describe "when called with arguments which would raise an
|
|
167
|
+
describe "when called with arguments which would raise an IllegalTransition from .transition()" do
|
|
168
168
|
it "should return nil" do
|
|
169
169
|
@obj.state_fu.name.should == :snoo
|
|
170
|
-
lambda { @obj.state_fu.
|
|
171
|
-
|
|
172
|
-
@obj.state_fu.fireable?(:not_fireable).should == nil
|
|
170
|
+
lambda { @obj.state_fu.can_transition?(:not_fire) }.should_not raise_error( StateFu::IllegalTransition )
|
|
171
|
+
@obj.state_fu.can_transition?(:not_fire).should == nil
|
|
173
172
|
end
|
|
174
173
|
end
|
|
175
174
|
|
|
@@ -178,10 +177,12 @@ describe StateFu::Binding do
|
|
|
178
177
|
# This would make very little sense to someone trying to understand how to use the library.
|
|
179
178
|
it "should pass the arguments to any requirements to determine transition availability" do
|
|
180
179
|
pending
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
t = nil
|
|
181
|
+
mock(@obj).tissue?(anything) do
|
|
182
|
+
# current_transition.should be_kind_of(StateFu::Transition)
|
|
183
|
+
t << current_transition
|
|
183
184
|
end #{|tr| tr.args.should == [:a,:b] }
|
|
184
|
-
@obj.state_fu.
|
|
185
|
+
@obj.state_fu.can_fire?(:a, :b)
|
|
185
186
|
end
|
|
186
187
|
end
|
|
187
188
|
|
|
@@ -46,7 +46,7 @@ describe StateFu::TransitionHalted do
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
describe StateFu::
|
|
49
|
+
describe StateFu::IllegalTransition do
|
|
50
50
|
before do
|
|
51
51
|
@binding = Object.new
|
|
52
52
|
@origin = Object.new
|
|
@@ -55,24 +55,24 @@ describe StateFu::InvalidTransition do
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
describe "constructor" do
|
|
58
|
-
it "should create an
|
|
58
|
+
it "should create an IllegalTransition given a binding, event, origin & target" do
|
|
59
59
|
pending
|
|
60
|
-
e = StateFu::
|
|
61
|
-
e.should be_kind_of( StateFu::
|
|
62
|
-
e.message.should == StateFu::
|
|
60
|
+
e = StateFu::IllegalTransition.new( @binding, @event, @origin, @target )
|
|
61
|
+
e.should be_kind_of( StateFu::IllegalTransition )
|
|
62
|
+
e.message.should == StateFu::IllegalTransition::DEFAULT_MESSAGE
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
it "should allow a custom message" do
|
|
66
66
|
pending
|
|
67
67
|
msg = 'helo'
|
|
68
|
-
e = StateFu::
|
|
69
|
-
e.should be_kind_of( StateFu::
|
|
68
|
+
e = StateFu::IllegalTransition.new( @binding, @event, @origin, @target, msg )
|
|
69
|
+
e.should be_kind_of( StateFu::IllegalTransition )
|
|
70
70
|
e.message.should == msg
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
it "should allow access to the binding, event, origin, and target" do
|
|
74
74
|
pending
|
|
75
|
-
e = StateFu::
|
|
75
|
+
e = StateFu::IllegalTransition.new( @binding, @event, @origin, @target )
|
|
76
76
|
e.binding.should == @binding
|
|
77
77
|
e.event.should == @event
|
|
78
78
|
e.origin.should == @origin
|
|
@@ -61,8 +61,8 @@ describe StateFu::MethodFactory do
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
it "should call binding.fire!( :simple_event ... ) with any specified args" do
|
|
64
|
-
mock.instance_of( StateFu::Binding ).
|
|
65
|
-
t = @obj.simple_event!(
|
|
64
|
+
mock.instance_of( StateFu::Binding ).fire_transition!( is_a(StateFu::Event), is_a(StateFu::State), :aa, :bb, {:cc => "dd"} )
|
|
65
|
+
t = @obj.simple_event!( :aa, :bb, :cc => "dd" )
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
it "should fire the transition" do
|
|
@@ -107,7 +107,7 @@ describe StateFu::MethodFactory do
|
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
it "should add any arguments / options it is called with to the transition" do
|
|
110
|
-
t = @binding.simple_event
|
|
110
|
+
t = @binding.simple_event :a, :b, :c, {'d' => 'e'}
|
|
111
111
|
#t.should be_kind_of( StateFu::Transition )
|
|
112
112
|
#t.target.should == @machine.states[:targ]
|
|
113
113
|
#t.event.should == @machine.events[:simple_event]
|
|
@@ -116,21 +116,21 @@ describe StateFu::MethodFactory do
|
|
|
116
116
|
end
|
|
117
117
|
end # transition builder
|
|
118
118
|
|
|
119
|
-
describe "method which tests if the event is
|
|
119
|
+
describe "method which tests if the event is can_transition?" do
|
|
120
120
|
it "should have the name of the event suffixed with ?" do
|
|
121
121
|
@binding.should respond_to(:can_simple_event?)
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
-
it "should be true when the binding says it\'s
|
|
125
|
-
@binding.
|
|
124
|
+
it "should be true when the binding says it\'s can_transition?" do
|
|
125
|
+
@binding.can_transition?( :simple_event ).should == true
|
|
126
126
|
@binding.can_simple_event?.should == true
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
it "should be false when the binding says it\'s not
|
|
130
|
-
mock( @binding ).
|
|
129
|
+
it "should be false when the binding says it\'s not can_transition?" do
|
|
130
|
+
mock( @binding ).can_transition?( is_a(StateFu::Event), is_a(StateFu::State) ) { false }
|
|
131
131
|
@binding.can_simple_event?.should == false
|
|
132
132
|
end
|
|
133
|
-
end #
|
|
133
|
+
end # can_transition?
|
|
134
134
|
|
|
135
135
|
describe "bang (!) method which creates, fires and returns a transition" do
|
|
136
136
|
it "should have the name of the event suffixed with a bang (!)" do
|
|
@@ -144,7 +144,7 @@ describe StateFu::MethodFactory do
|
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
it "should pass any arguments to the transition as args / options" do
|
|
147
|
-
t = @binding.simple_event!(
|
|
147
|
+
t = @binding.simple_event!( :a, :b, {'c' => :d } )
|
|
148
148
|
t.should be_kind_of( StateFu::Transition )
|
|
149
149
|
t.args.should == [:a, :b, {'c' => :d} ]
|
|
150
150
|
t.options.should == { :c => :d }
|
|
@@ -178,15 +178,15 @@ describe StateFu::MethodFactory do
|
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
it "should raise an error if called without any arguments" do
|
|
181
|
-
lambda { @binding.complex_event() }.should raise_error(
|
|
181
|
+
lambda { @binding.complex_event() }.should raise_error( ArgumentError )
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
it "should raise an ArgumentError if called with a nonexistent target state" do
|
|
185
185
|
lambda { @binding.complex_event(:nonexistent) }.should raise_error( StateFu::UnknownTarget )
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
-
it "should raise an
|
|
189
|
-
lambda { @binding.complex_event(:orphan) }.should raise_error( StateFu::
|
|
188
|
+
it "should raise an IllegalTransition if called with an invalid target state" do
|
|
189
|
+
lambda { @binding.complex_event(:orphan) }.should raise_error( StateFu::IllegalTransition )
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
it "should return a transition to the specified state if supplied a valid state" do
|
|
@@ -204,7 +204,7 @@ describe StateFu::MethodFactory do
|
|
|
204
204
|
end
|
|
205
205
|
end # transition builder
|
|
206
206
|
|
|
207
|
-
describe "method which tests if the event is
|
|
207
|
+
describe "method which tests if the event is can_transition?" do
|
|
208
208
|
it "should have the name of the event suffixed with ?" do
|
|
209
209
|
@binding.should respond_to(:can_complex_event?)
|
|
210
210
|
end
|
|
@@ -212,20 +212,15 @@ describe StateFu::MethodFactory do
|
|
|
212
212
|
it "should require a valid state name" do
|
|
213
213
|
lambda { @binding.can_complex_event?(:nonexistent) }.should raise_error( StateFu::UnknownTarget )
|
|
214
214
|
lambda { @binding.can_complex_event?(:orphan) }.should_not raise_error()
|
|
215
|
-
@binding.can_complex_event?(:orphan).should ==
|
|
215
|
+
@binding.can_complex_event?(:orphan).should == false
|
|
216
216
|
lambda { @binding.can_complex_event?(:x) }.should_not raise_error
|
|
217
217
|
end
|
|
218
218
|
|
|
219
|
-
it "should be true when the binding says the event is
|
|
220
|
-
@binding.
|
|
219
|
+
it "should be true when the binding says the event is can_transition? " do
|
|
220
|
+
@binding.can_transition?( :complex_event, :x ).should == true
|
|
221
221
|
@binding.can_complex_event?(:x).should == true
|
|
222
222
|
end
|
|
223
|
-
|
|
224
|
-
it "should be false when the binding says the event is not fireable?" do
|
|
225
|
-
mock( @binding ).fireable?( anything ) { false }
|
|
226
|
-
@binding.can_complex_event?(:x).should == false
|
|
227
|
-
end
|
|
228
|
-
end # fireable?
|
|
223
|
+
end # can_transition?
|
|
229
224
|
|
|
230
225
|
describe "bang (!) method which creates, fires and returns a transition" do
|
|
231
226
|
it "should have the name of the event suffixed with a bang (!)" do
|
|
@@ -234,7 +229,7 @@ describe StateFu::MethodFactory do
|
|
|
234
229
|
|
|
235
230
|
it "should require a valid state name" do
|
|
236
231
|
lambda { @binding.complex_event!(:nonexistent) }.should raise_error( StateFu::UnknownTarget )
|
|
237
|
-
lambda { @binding.complex_event!(:orphan) }.should raise_error( StateFu::
|
|
232
|
+
lambda { @binding.complex_event!(:orphan) }.should raise_error( StateFu::IllegalTransition )
|
|
238
233
|
lambda { @binding.complex_event!(:x) }.should_not raise_error
|
|
239
234
|
end
|
|
240
235
|
|
|
@@ -350,7 +345,7 @@ describe StateFu::MethodFactory do
|
|
|
350
345
|
end
|
|
351
346
|
|
|
352
347
|
describe "next_state!" do
|
|
353
|
-
it "should raise_error(
|
|
348
|
+
it "should raise_error( IllegalTransition )" do
|
|
354
349
|
lambda { @binding.next_state! }.should raise_error( StateFu::TransitionNotFound )
|
|
355
350
|
end
|
|
356
351
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: davidlee-state-fu
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Lee
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-08-
|
|
12
|
+
date: 2009-08-26 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies: []
|
|
15
15
|
|
|
@@ -58,6 +58,7 @@ files:
|
|
|
58
58
|
- lib/state_fu/machine.rb
|
|
59
59
|
- lib/state_fu/method_factory.rb
|
|
60
60
|
- lib/state_fu/methodical.rb
|
|
61
|
+
- lib/state_fu/nil_transition.rb
|
|
61
62
|
- lib/state_fu/persistence.rb
|
|
62
63
|
- lib/state_fu/persistence/active_record.rb
|
|
63
64
|
- lib/state_fu/persistence/attribute.rb
|
|
@@ -110,7 +111,6 @@ files:
|
|
|
110
111
|
- README.textile
|
|
111
112
|
has_rdoc: false
|
|
112
113
|
homepage: http://github.com/davidlee/state-fu
|
|
113
|
-
licenses:
|
|
114
114
|
post_install_message:
|
|
115
115
|
rdoc_options:
|
|
116
116
|
- --charset=UTF-8
|
|
@@ -131,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
131
131
|
requirements: []
|
|
132
132
|
|
|
133
133
|
rubyforge_project: state-fu
|
|
134
|
-
rubygems_version: 1.
|
|
134
|
+
rubygems_version: 1.2.0
|
|
135
135
|
signing_key:
|
|
136
136
|
specification_version: 3
|
|
137
137
|
summary: A rich library for state-oriented programming with state machines / workflows
|