davidlee-state-fu 0.11.1 → 0.12.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/README.textile +144 -145
- data/lib/binding.rb +40 -28
- data/lib/event.rb +1 -1
- data/lib/executioner.rb +8 -26
- data/lib/interface.rb +12 -14
- data/lib/lathe.rb +19 -2
- data/lib/machine.rb +25 -17
- data/lib/method_factory.rb +21 -20
- data/lib/persistence.rb +9 -9
- data/lib/state-fu.rb +1 -3
- data/lib/support/core_ext.rb +0 -2
- data/lib/support/plotter.rb +0 -1
- data/lib/tasks/spec_last.rake +37 -28
- data/lib/transition.rb +4 -0
- data/lib/transition_query.rb +2 -2
- data/spec/features/machine_alias_spec.rb +46 -0
- data/spec/features/singleton_machine_spec.rb +17 -4
- data/spec/features/when_methods_are_defined_spec.rb +114 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/state_fu_spec.rb +88 -0
- data/spec/units/binding_spec.rb +7 -9
- data/spec/units/machine_spec.rb +5 -8
- metadata +6 -2
data/README.textile
CHANGED
|
@@ -1,49 +1,138 @@
|
|
|
1
|
-
h1.
|
|
1
|
+
h1. StateFu
|
|
2
2
|
|
|
3
3
|
h2. What is it?
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
StateFu is another Ruby state machine.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
event-driven programming in Ruby.
|
|
7
|
+
h2. What is a state machine?
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Finite state machines are a model for program behaviour; like
|
|
10
|
+
object-oriented programming, they provide an abstract way to think
|
|
11
|
+
about a domain.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
In a finite state machine, there are a number of discrete states. Only
|
|
14
|
+
one state may be occupied at any given time (hence the "finite").
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
States are linked together by events, and there are rules which govern
|
|
17
|
+
when or how transitions between states can occur. Actions may be fired
|
|
18
|
+
on entry to or exit from a state, or when a certain transition occurs.
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
h2. Why is StateFu different to the other twenty state machines for Ruby?
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
State machines are potentially a powerful way to simplify and
|
|
23
|
+
structure a lot of problems. They can be used to:
|
|
21
24
|
|
|
22
|
-
*
|
|
25
|
+
* succinctly define the grammar of a networking protocol or a
|
|
26
|
+
configuration DSL
|
|
23
27
|
|
|
24
|
-
*
|
|
28
|
+
* clearly and compactly describe complex logic, which might otherwise
|
|
29
|
+
be difficult to understand by playing "follow the rabbit" through
|
|
30
|
+
methods thick with implementation details
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
problems
|
|
32
|
+
* serialize and process multiple revisions of changing business rules
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
* provide an abstract representation of program or domain behaviour
|
|
35
|
+
which can be introspected, edited, queried and executed on the fly,
|
|
36
|
+
in a high-level and human-readable format
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
* provide a straightforward and easy way to record and validate
|
|
39
|
+
"status" information, especially when there are rules governing
|
|
40
|
+
when and how it can be updated
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
* reduce proliferation of classes and modules, or easily define and
|
|
43
|
+
control functionally related groups of objects, by defining
|
|
44
|
+
behaviours on interacting components of a state machine
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
unidentifiable metal which comes only from the rarest of meteorites,
|
|
39
|
-
and it ticks when you hold it up to your ear.[1]
|
|
46
|
+
* elegantly implement simple building blocks like stacks / queues,
|
|
47
|
+
parsers, schedulers, automata, etc
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
StateFu was written from the ground up with one goal in mind: to be
|
|
50
|
+
over-engineered. It is designed to make truly ambitious use of state
|
|
51
|
+
machines not only viable, but strongly advantageous in many situations.
|
|
52
|
+
|
|
53
|
+
It is designed in the very opposite vein to the intentional minimalism
|
|
54
|
+
of most ruby state machine projects; it is tasked with taking on a
|
|
55
|
+
great deal of complexity and functionality, and abstracting it behind
|
|
56
|
+
a nice DSL, so that the code which *you* have to maintain is shorter and
|
|
57
|
+
clearer.
|
|
58
|
+
|
|
59
|
+
StateFu allows you to:
|
|
60
|
+
|
|
61
|
+
* give a class any number of machines
|
|
62
|
+
|
|
63
|
+
* define behaviours on state entry / exit; before, after or during
|
|
64
|
+
execution of a particular event; or before / after every transition
|
|
65
|
+
in a given machine
|
|
66
|
+
|
|
67
|
+
* give any object own its own private, "singleton" machines,
|
|
68
|
+
which are unique to that object and modifiable at runtime.
|
|
69
|
+
|
|
70
|
+
* create events with any number of origin or target states
|
|
71
|
+
|
|
72
|
+
* define and query guard conditions / transition requirements,
|
|
73
|
+
to establish rules about when a transition is valid
|
|
74
|
+
|
|
75
|
+
* use powerful reflection and logging capabilities to easily expose
|
|
76
|
+
and debug the operation of your machines
|
|
77
|
+
|
|
78
|
+
* automatically and unobtrusively define methods for querying each
|
|
79
|
+
state and event, and for firing transitions
|
|
80
|
+
|
|
81
|
+
* easily find out which transitions are valid at any given time
|
|
82
|
+
|
|
83
|
+
* generate descriptive, contextual messages when a transition is
|
|
84
|
+
invalid
|
|
85
|
+
|
|
86
|
+
* halt a transition during execution
|
|
87
|
+
|
|
88
|
+
* easily extend StateFu's DSL to match the problem domain
|
|
89
|
+
|
|
90
|
+
* fire transitions with a payload of arguments and program context,
|
|
91
|
+
which is available to guard conditions, event hooks, and
|
|
92
|
+
requirement messages when they are evaluated
|
|
93
|
+
|
|
94
|
+
* use a lovely, simple and flexible API which gives you plenty of
|
|
95
|
+
choices about how to describe your problem domain; choose (or
|
|
96
|
+
build) a programming style which suits the task at hand from an
|
|
97
|
+
expressive range of options
|
|
98
|
+
|
|
99
|
+
* store arbitrary meta-data on any component of StateFu - a simple
|
|
100
|
+
but extremely powerful tool for integration with almost anything.
|
|
101
|
+
|
|
102
|
+
* flexible and helpful logging out of the box - will use the Rails
|
|
103
|
+
logger if you're in a Rails project, or standalone logging to
|
|
104
|
+
STDOUT or a file. Configurable loglevel and message prefixes help
|
|
105
|
+
StateFu be a good citizen in a shared application log.
|
|
106
|
+
|
|
107
|
+
* automatically generate diagrams of state machines / workflows with graphviz
|
|
108
|
+
|
|
109
|
+
* use an ActiveRecord field for state persistence, or a regular
|
|
110
|
+
attribute - or use both, on the same class, for different
|
|
111
|
+
machines. If an appropriate ActiveRecord field exists for a
|
|
112
|
+
machine, it will be used. Otherwise, an attr_accessor will be used
|
|
113
|
+
(and created, if necessary).
|
|
114
|
+
|
|
115
|
+
* customising the persistence mechanism (eg to use a Rails session,
|
|
116
|
+
or a text file, or your choice of ORM) is usually as easy as
|
|
117
|
+
defining a getter and setter method for the persistence field, and
|
|
118
|
+
a rule about when to use it. If you want to use StateFu with a
|
|
119
|
+
persistence mechanism which is not yet supported, send me a message.
|
|
120
|
+
|
|
121
|
+
* StateFu is fast, lightweight and useful enough to use in any ruby
|
|
122
|
+
project - works with Rails but does not require it.
|
|
123
|
+
|
|
124
|
+
h2. Still not sold?
|
|
125
|
+
|
|
126
|
+
StateFu is forged from a reassuringly dense but unidentifiable metal
|
|
127
|
+
which comes only from the rarest of meteorites, and it ticks when you
|
|
128
|
+
hold it up to your ear.[1]
|
|
129
|
+
|
|
130
|
+
It is elegant, powerful and transparent enough that you can use
|
|
42
131
|
it to drive substantial parts of your application, and actually want
|
|
43
132
|
to do so.
|
|
44
133
|
|
|
45
134
|
It is designed as a library for authors, as well as users, of
|
|
46
|
-
libraries:
|
|
135
|
+
libraries: StateFu goes to great lengths to impose very few limits on
|
|
47
136
|
your ability to introspect, manipulate and extend the core features.
|
|
48
137
|
|
|
49
138
|
It is also delightfully elegant and easy to use for simple things:
|
|
@@ -97,124 +186,7 @@ It is also delightfully elegant and easy to use for simple things:
|
|
|
97
186
|
|
|
98
187
|
</code></pre>
|
|
99
188
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
A few of the features which set State-Fu apart for more ambitious work are:
|
|
103
|
-
|
|
104
|
-
* a lovely, simple and flexible API gives you plenty of choices about
|
|
105
|
-
how to describe your problem domain.
|
|
106
|
-
|
|
107
|
-
* define any number of workflows on the same object / model;
|
|
108
|
-
workflows (Machines) can be entirely separate, or interact with
|
|
109
|
-
each other. Re-use machines across multiple classes, serialize them
|
|
110
|
-
to a database, or build them on the fly.
|
|
111
|
-
|
|
112
|
-
* events can transition from / to any number of states
|
|
113
|
-
|
|
114
|
-
* drive application behaviour with a rich set of event hooks
|
|
115
|
-
|
|
116
|
-
* define behaviour as methods on your objects, or keep it all in the
|
|
117
|
-
state machine itself
|
|
118
|
-
|
|
119
|
-
* requirements determine at runtime whether a particular state
|
|
120
|
-
transition can occur, and if not, can tell a user (or developer)
|
|
121
|
-
what they must do to satisfy the requirements.
|
|
122
|
-
|
|
123
|
-
* requirement failure messages can be generated at runtime, making
|
|
124
|
-
use of whatever application and state-machine context they need
|
|
125
|
-
|
|
126
|
-
* transitions can be halted mid-execution, and you can actually
|
|
127
|
-
determine why, and where from
|
|
128
|
-
|
|
129
|
-
* in every event hook, requirement filter, and other method calls,
|
|
130
|
-
you have complete and consistent access to your classes, its
|
|
131
|
-
StateFu::Machines, and any Transition context. Use real ruby code
|
|
132
|
-
anywhere, without breathing through a straw!
|
|
133
|
-
|
|
134
|
-
* extend State-Fu (with Lathe#helper) Binding and Transition
|
|
135
|
-
instances for your Machine to define your a DSL customized for your
|
|
136
|
-
problem domain, and to keep your class definitions clean. Helper
|
|
137
|
-
methods have easy access to all the context associated with your
|
|
138
|
-
object instance, its StateFu::Machines and any Transition in
|
|
139
|
-
progress.
|
|
140
|
-
|
|
141
|
-
* extend a State-Fu Lathe (with Lathe#tool) to keep your Machine
|
|
142
|
-
definitions DRY
|
|
143
|
-
|
|
144
|
-
* store arbitrary meta-data on any component of State-Fu - a simple
|
|
145
|
-
but extremely powerful tool for integration with almost anything.
|
|
146
|
-
|
|
147
|
-
* designed for transparency, introspection and ease of debugging,
|
|
148
|
-
which means a dynamic, powerful system you can actually use without
|
|
149
|
-
headaches.
|
|
150
|
-
|
|
151
|
-
* flexible and helpful logging out of the box - will use the Rails
|
|
152
|
-
logger if you're in a Rails project, or standalone logging to
|
|
153
|
-
STDOUT or a file. Configurable loglevel and message prefixes help
|
|
154
|
-
StateFu be a good citizen in a shared application log.
|
|
155
|
-
|
|
156
|
-
* magically generate diagrams of state machines / workflows with graphviz
|
|
157
|
-
|
|
158
|
-
* "magically" use an ActiveRecord field for state persistence, or just an
|
|
159
|
-
attribute - or use both, on the same class, for different
|
|
160
|
-
workflows. If an ActiveRecord field exists for a machine's
|
|
161
|
-
field_name (by default, the machine's name suffixed with '_field';
|
|
162
|
-
the default machine name is 'state_fu', so if you don't explicitly
|
|
163
|
-
name a machine it will look for 'state_fu_field' in your
|
|
164
|
-
ActiveRecord columns (if ActiveRecord is included) and use
|
|
165
|
-
that. Otherwise, an attr_accessor will be used (and created, if
|
|
166
|
-
necessary).
|
|
167
|
-
|
|
168
|
-
* customising the persistence mechanism (eg to use a Rails session,
|
|
169
|
-
or a text file, or your choice of ORM) is usually as easy as
|
|
170
|
-
defining a getter and setter method for the persistence field, and
|
|
171
|
-
a rule about when to use it. If you want to use StateFu with a
|
|
172
|
-
persistence mechanism which is not yet supported, I'd like to hear
|
|
173
|
-
about it.
|
|
174
|
-
|
|
175
|
-
* fast, lightweight and useful enough to use in any ruby
|
|
176
|
-
project - works with Rails but does not require it.
|
|
177
|
-
|
|
178
|
-
State-Fu works with any modern Ruby ( 1.8.6, 1.8.7, and 1.9.1)
|
|
179
|
-
|
|
180
|
-
fn1. No disrespect intended to the authors of other similar libraries
|
|
181
|
-
- some of whom I've borrowed an idea or two, and some useful criticism,
|
|
182
|
-
from. They're stand-up guys, all of them. It's the truth though.
|
|
183
|
-
|
|
184
|
-
I'd like to thank Ryan Allen in particular for his Workflow library,
|
|
185
|
-
which I previously forked, piled hundreds of lines of code into and renamed
|
|
186
|
-
Stateful (now deprecated). Some of his ideas (for example the ability to store metadata
|
|
187
|
-
easily on *everything* ) have been instrumental in State-Fu's design.
|
|
188
|
-
|
|
189
|
-
I'd also like to tip my hat at John Barnette, who's own
|
|
190
|
-
(coincidentally named) Stateful set a very high standard with an
|
|
191
|
-
exceptionally elegant API.
|
|
192
|
-
|
|
193
|
-
h3. StateFu is not a complete BPM (Business Process Management) platform
|
|
194
|
-
|
|
195
|
-
It's worth noting that StateFu is at it's core a state machine, which
|
|
196
|
-
strives to be powerful enough to be able to drive many kinds of
|
|
197
|
-
application behaviour.
|
|
198
|
-
|
|
199
|
-
It is not, however, a "proper" workflow engine on par with Ruote. In
|
|
200
|
-
StateFu the basic units with which "workflows" are built are states
|
|
201
|
-
and events; Ruote takes a higher level view, dealing with processes
|
|
202
|
-
and participants. As a result, it's capable of directly implementing
|
|
203
|
-
these design patterns:
|
|
204
|
-
|
|
205
|
-
http://openwferu.rubyforge.org/patterns.html
|
|
206
|
-
|
|
207
|
-
Whereas StateFu cannot, for example, readily model forking / merging
|
|
208
|
-
of processes (nor does it handles scheduling, process management, etc.
|
|
209
|
-
|
|
210
|
-
The author of Ruote, the Ruby Workflow Engine, outlines the difference
|
|
211
|
-
pretty clearly here:
|
|
212
|
-
|
|
213
|
-
http://jmettraux.wordpress.com/2009/07/03/state-machine-workflow-engine/
|
|
214
|
-
|
|
215
|
-
If your application can be described with StateFu, you'll likely find
|
|
216
|
-
it simpler to get running and work with; if not, you may find Ruote,
|
|
217
|
-
or a combination of the two, suits your needs perfectly.
|
|
189
|
+
StateFu works with any modern Ruby ( 1.8.6, 1.8.7, and 1.9.1)
|
|
218
190
|
|
|
219
191
|
h2. Getting started
|
|
220
192
|
|
|
@@ -276,6 +248,7 @@ dependent libraries.
|
|
|
276
248
|
So if you plan to use ActiveSupport in a stand-alone project with
|
|
277
249
|
StateFu, you should require it before StateFu.
|
|
278
250
|
|
|
251
|
+
|
|
279
252
|
h3. Addditional Resources
|
|
280
253
|
|
|
281
254
|
Also see the "issue tracker":http://github.com/davidlee/state-fu/issues
|
|
@@ -284,10 +257,36 @@ And the "build monitor":http://runcoderun.com/davidlee/state-fu/
|
|
|
284
257
|
|
|
285
258
|
And the "RDoc":http://rdoc.info/projects/davidlee/state-fu
|
|
286
259
|
|
|
260
|
+
|
|
261
|
+
h3. StateFu is not a complete BPM (Business Process Management) platform
|
|
262
|
+
|
|
263
|
+
It's worth noting that StateFu is at it's core a state machine, which
|
|
264
|
+
strives to be powerful enough to be able to drive many kinds of
|
|
265
|
+
application behaviour.
|
|
266
|
+
|
|
267
|
+
It is not, however, a classical workflow engine on par with Ruote. In
|
|
268
|
+
StateFu the basic units with which "workflows" are built are states
|
|
269
|
+
and events; Ruote takes a higher level view, dealing with processes
|
|
270
|
+
and participants. As a result, it's capable of directly implementing
|
|
271
|
+
these design patterns:
|
|
272
|
+
|
|
273
|
+
http://openwferu.rubyforge.org/patterns.html
|
|
274
|
+
|
|
275
|
+
Whereas StateFu cannot, for example, readily model forking / merging
|
|
276
|
+
of processes (nor does it handles scheduling, process management, etc.
|
|
277
|
+
|
|
278
|
+
The author of Ruote, the Ruby Workflow Engine, outlines the difference
|
|
279
|
+
pretty clearly here:
|
|
280
|
+
|
|
281
|
+
http://jmettraux.wordpress.com/2009/07/03/state-machine-workflow-engine/
|
|
282
|
+
|
|
283
|
+
If your application can be described with StateFu, you'll likely find
|
|
284
|
+
it simpler to get running and work with; if not, you may find Ruote,
|
|
285
|
+
or a combination of the two, suits your needs perfectly.
|
|
286
|
+
|
|
287
287
|
h3. Thanks
|
|
288
288
|
|
|
289
|
-
* dsturnbull, for
|
|
290
|
-
when an activerecord model has no database table
|
|
289
|
+
* dsturnbull, for patches
|
|
291
290
|
|
|
292
291
|
* lachie, benkimball for pointing out README bugs / typos
|
|
293
292
|
|
data/lib/binding.rb
CHANGED
|
@@ -3,13 +3,12 @@ module StateFu
|
|
|
3
3
|
|
|
4
4
|
attr_reader :object, :machine, :method_name, :field_name, :persister, :transitions, :options, :target
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
# the constructor should not be called manually; a binding is
|
|
8
7
|
# returned when an instance of a class with a StateFu::Machine
|
|
9
8
|
# calls:
|
|
10
9
|
#
|
|
11
10
|
# instance.#state_fu (for the default machine which is called :state_fu),
|
|
12
|
-
# instance.#state_fu(
|
|
11
|
+
# instance.#state_fu(:<machine_name>) ,or
|
|
13
12
|
# instance.#<machine_name>
|
|
14
13
|
#
|
|
15
14
|
def initialize( machine, object, method_name, options={} )
|
|
@@ -18,13 +17,18 @@ module StateFu
|
|
|
18
17
|
@method_name = method_name
|
|
19
18
|
@transitions = []
|
|
20
19
|
@options = options.symbolize_keys!
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
if options[:singleton]
|
|
21
|
+
@target = object
|
|
22
|
+
else
|
|
23
|
+
@target = object.class
|
|
24
|
+
@options = @target.state_fu_options[@method_name].merge(options)
|
|
25
|
+
end
|
|
26
|
+
@field_name = @options[:field_name] || raise("No field_name supplied in #{@options.inspect}")
|
|
27
|
+
@persister = Persistence.for self
|
|
24
28
|
|
|
25
29
|
# define event methods on this binding and its @object
|
|
26
|
-
MethodFactory.new(
|
|
27
|
-
@machine.helpers.inject_into
|
|
30
|
+
MethodFactory.new(self).install!
|
|
31
|
+
@machine.helpers.inject_into self
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
alias_method :o, :object
|
|
@@ -60,26 +64,26 @@ module StateFu
|
|
|
60
64
|
|
|
61
65
|
#
|
|
62
66
|
# These methods are called from methods defined by MethodFactory.
|
|
63
|
-
#
|
|
64
|
-
|
|
67
|
+
#
|
|
68
|
+
|
|
65
69
|
# event_name [target], *args
|
|
66
70
|
#
|
|
67
71
|
def find_transition(event, target=nil, *args)
|
|
68
|
-
target ||= args.last[:to].to_sym rescue nil
|
|
72
|
+
target ||= args.last[:to].to_sym rescue nil
|
|
69
73
|
query = transitions.for_event(event).to(target).with(*args)
|
|
70
|
-
query.find || query.valid.singular || nil
|
|
74
|
+
query.find || query.valid.singular || nil
|
|
71
75
|
end
|
|
72
76
|
|
|
73
77
|
# event_name? [target], *args
|
|
74
78
|
#
|
|
75
79
|
def can_transition?(event, target=nil, *args)
|
|
76
80
|
begin
|
|
77
|
-
if t = find_transition(event, target, *args)
|
|
81
|
+
if t = find_transition(event, target, *args)
|
|
78
82
|
t.valid?(*args)
|
|
79
83
|
end
|
|
80
84
|
rescue IllegalTransition, UnknownTarget
|
|
81
85
|
nil
|
|
82
|
-
end
|
|
86
|
+
end
|
|
83
87
|
end
|
|
84
88
|
|
|
85
89
|
# event_name! [target], *args
|
|
@@ -100,7 +104,7 @@ module StateFu
|
|
|
100
104
|
end
|
|
101
105
|
alias_method :events_from_current_state, :events
|
|
102
106
|
|
|
103
|
-
# all states which can be reached from the current_state.
|
|
107
|
+
# all states which can be reached from the current_state.
|
|
104
108
|
# Does not check transition requirements, etc.
|
|
105
109
|
def next_states
|
|
106
110
|
events.map(&:targets).compact.flatten.uniq.extend StateArray
|
|
@@ -125,11 +129,11 @@ module StateFu
|
|
|
125
129
|
def valid_events(*args)
|
|
126
130
|
valid_transitions(*args).events
|
|
127
131
|
end
|
|
128
|
-
|
|
132
|
+
|
|
129
133
|
def invalid_events(*args)
|
|
130
134
|
(events - valid_events(*args)).extend StateArray
|
|
131
135
|
end
|
|
132
|
-
|
|
136
|
+
|
|
133
137
|
|
|
134
138
|
# initializes a new Transition to the given destination, with the
|
|
135
139
|
# given *args (to be passed to requirements and hooks).
|
|
@@ -139,17 +143,17 @@ module StateFu
|
|
|
139
143
|
def transition( event_or_array, *args, &block )
|
|
140
144
|
return transitions.with(*args, &block).find(event_or_array)
|
|
141
145
|
end
|
|
142
|
-
|
|
146
|
+
|
|
143
147
|
#
|
|
144
148
|
# next_transition and friends: when there's exactly one valid move
|
|
145
149
|
#
|
|
146
|
-
|
|
150
|
+
|
|
147
151
|
# if there is exactly one legal & valid transition which can be fired with
|
|
148
152
|
# the given (optional) arguments, return it.
|
|
149
153
|
def next_transition( *args, &block )
|
|
150
154
|
transitions.with(*args, &block).next
|
|
151
155
|
end
|
|
152
|
-
|
|
156
|
+
|
|
153
157
|
# as above but ignoring any transitions whose origin and target are the same
|
|
154
158
|
def next_transition_excluding_cycles( *args, &block )
|
|
155
159
|
transitions.not_cyclic.with(*args, &block).next
|
|
@@ -158,7 +162,7 @@ module StateFu
|
|
|
158
162
|
# if there is exactly one state reachable via a transition which
|
|
159
163
|
# is valid with the given optional arguments, return it.
|
|
160
164
|
def next_state(*args, &block)
|
|
161
|
-
transitions.with(*args, &block).next_state
|
|
165
|
+
transitions.with(*args, &block).next_state
|
|
162
166
|
end
|
|
163
167
|
|
|
164
168
|
# if there is exactly one event which is valid with the given
|
|
@@ -166,7 +170,7 @@ module StateFu
|
|
|
166
170
|
def next_event( *args )
|
|
167
171
|
transitions.with(*args, &block).next_event
|
|
168
172
|
end
|
|
169
|
-
|
|
173
|
+
|
|
170
174
|
# if there is a next_transition, create, fire & return it
|
|
171
175
|
# otherwise raise an IllegalTransition
|
|
172
176
|
def next!( *args, &block )
|
|
@@ -179,7 +183,7 @@ module StateFu
|
|
|
179
183
|
alias_method :next_transition!, :next!
|
|
180
184
|
alias_method :next_event!, :next!
|
|
181
185
|
alias_method :next_state!, :next!
|
|
182
|
-
|
|
186
|
+
|
|
183
187
|
# if there is a next_transition, return true / false depending on
|
|
184
188
|
# whether its requirements are met
|
|
185
189
|
# otherwise, nil
|
|
@@ -222,6 +226,14 @@ module StateFu
|
|
|
222
226
|
end
|
|
223
227
|
end
|
|
224
228
|
|
|
229
|
+
# next! without the raise if there's no next transition
|
|
230
|
+
# TODO SPECME
|
|
231
|
+
def update!( *args, &block )
|
|
232
|
+
if t = next_transition( *args, &block )
|
|
233
|
+
t.fire!
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
225
237
|
#
|
|
226
238
|
# misc
|
|
227
239
|
#
|
|
@@ -260,7 +272,7 @@ module StateFu
|
|
|
260
272
|
options[:singleton]
|
|
261
273
|
end
|
|
262
274
|
|
|
263
|
-
# SPECME DOCME OR KILLME
|
|
275
|
+
# SPECME DOCME OR KILLME
|
|
264
276
|
def reload()
|
|
265
277
|
if persister.is_a?( Persistence::ActiveRecord )
|
|
266
278
|
object.reload
|
|
@@ -272,13 +284,13 @@ module StateFu
|
|
|
272
284
|
def inspect
|
|
273
285
|
s = self.to_s
|
|
274
286
|
s = s[0,s.length-1]
|
|
275
|
-
s << " object=#{object}"
|
|
276
|
-
s << " current_state=#{current_state.to_sym.inspect rescue nil}"
|
|
277
|
-
s << " events=#{events.map(&:to_sym).inspect rescue nil}"
|
|
278
|
-
s << " machine=#{machine.to_s}"
|
|
287
|
+
s << " object=#{object}"
|
|
288
|
+
s << " current_state=#{current_state.to_sym.inspect rescue nil}"
|
|
289
|
+
s << " events=#{events.map(&:to_sym).inspect rescue nil}"
|
|
290
|
+
s << " machine=#{machine.to_s}"
|
|
279
291
|
s << ">"
|
|
280
292
|
s
|
|
281
293
|
end
|
|
282
|
-
|
|
294
|
+
|
|
283
295
|
end
|
|
284
296
|
end
|
data/lib/event.rb
CHANGED
data/lib/executioner.rb
CHANGED
|
@@ -3,17 +3,7 @@ module StateFu
|
|
|
3
3
|
# delegator class for evaluation methods / procs in the context of
|
|
4
4
|
# your object.
|
|
5
5
|
#
|
|
6
|
-
|
|
7
|
-
# There's a bug in ruby 1.8.x where lambda {}.arity == -1 instead of 0
|
|
8
|
-
# To get around this, turn it into a proc if conditions are dangerous.
|
|
9
|
-
def self.get_effective_arity
|
|
10
|
-
if RUBY_VERSION[0,3] == "1.8" && proc.arity == -1
|
|
11
|
-
proc.to_proc.arity
|
|
12
|
-
else
|
|
13
|
-
proc.arity
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
6
|
+
|
|
17
7
|
class Executioner
|
|
18
8
|
|
|
19
9
|
# give us a blank slate
|
|
@@ -29,8 +19,6 @@ module StateFu
|
|
|
29
19
|
self
|
|
30
20
|
end
|
|
31
21
|
|
|
32
|
-
# delegate :self, :to => :__target__
|
|
33
|
-
|
|
34
22
|
delegate :origin, :to => :transition, :prefix => true # transition_origin
|
|
35
23
|
delegate :target, :to => :transition, :prefix => true # transition_target
|
|
36
24
|
delegate :event, :to => :transition, :prefix => true # transition_event
|
|
@@ -45,10 +33,10 @@ module StateFu
|
|
|
45
33
|
|
|
46
34
|
attr_reader :transition, :__target__, :__self__
|
|
47
35
|
|
|
48
|
-
alias_method :t,
|
|
49
|
-
alias_method :current_transition,
|
|
50
|
-
alias_method :context,
|
|
51
|
-
alias_method :ctx,
|
|
36
|
+
alias_method :t, :transition
|
|
37
|
+
alias_method :current_transition, :transition
|
|
38
|
+
alias_method :context, :transition
|
|
39
|
+
alias_method :ctx, :transition
|
|
52
40
|
|
|
53
41
|
alias_method :arguments, :args
|
|
54
42
|
alias_method :transition_arguments, :args
|
|
@@ -112,7 +100,7 @@ module StateFu
|
|
|
112
100
|
private
|
|
113
101
|
|
|
114
102
|
# Forwards any missing method call to the \target.
|
|
115
|
-
# TODO /
|
|
103
|
+
# TODO / NOTE: we don't (can't ?) handle block arguments ...
|
|
116
104
|
def method_missing(method_name, *args)
|
|
117
105
|
if __target__.respond_to?(method_name, true)
|
|
118
106
|
begin
|
|
@@ -126,13 +114,7 @@ module StateFu
|
|
|
126
114
|
end
|
|
127
115
|
|
|
128
116
|
end
|
|
129
|
-
|
|
130
|
-
#
|
|
131
|
-
# def self.const_missing(const_name)
|
|
132
|
-
# unless __target__.class.const_defined?(const_name, true)
|
|
133
|
-
# super(const_name)
|
|
134
|
-
# end
|
|
135
|
-
# __target__.class.const_get(const_name)
|
|
136
|
-
# end
|
|
117
|
+
|
|
118
|
+
# NOTE: const_missing is not handled.
|
|
137
119
|
end
|
|
138
120
|
end
|
data/lib/interface.rb
CHANGED
|
@@ -2,21 +2,19 @@ module StateFu
|
|
|
2
2
|
module Interface
|
|
3
3
|
module SoftAlias
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
# so we can be liberal with them.
|
|
7
|
-
def soft_alias(x)
|
|
8
|
-
aliases = [ x.to_a[0] ].flatten
|
|
9
|
-
original = aliases.shift
|
|
5
|
+
def soft_alias(hash)
|
|
10
6
|
existing_method_names = (self.instance_methods | self.protected_instance_methods | self.private_instance_methods).map(&:to_sym)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
hash.each do |original, aliases|
|
|
8
|
+
aliases.
|
|
9
|
+
reject { |a| existing_method_names.include?(a.to_sym) }.
|
|
10
|
+
each { |a| alias_method a, original}
|
|
11
|
+
end
|
|
12
|
+
end
|
|
16
13
|
end
|
|
17
14
|
|
|
18
15
|
module Aliases
|
|
19
|
-
|
|
16
|
+
# define aliases that won't clobber existing methods -
|
|
17
|
+
# so we can be liberal with them.
|
|
20
18
|
def self.extended(base)
|
|
21
19
|
base.extend SoftAlias
|
|
22
20
|
base.class_eval do
|
|
@@ -70,8 +68,8 @@ module StateFu
|
|
|
70
68
|
end
|
|
71
69
|
alias_method :machine, :state_fu_machine
|
|
72
70
|
|
|
73
|
-
def
|
|
74
|
-
@
|
|
71
|
+
def state_fu_options
|
|
72
|
+
@_state_fu_options ||= {}
|
|
75
73
|
end
|
|
76
74
|
|
|
77
75
|
def state_fu_machines
|
|
@@ -97,7 +95,7 @@ module StateFu
|
|
|
97
95
|
# can access a StateFu::Machine, the object's current state, the
|
|
98
96
|
# methods which trigger event transitions, etc.
|
|
99
97
|
|
|
100
|
-
def state_fu_binding(
|
|
98
|
+
def state_fu_binding(name = DEFAULT)
|
|
101
99
|
name = name.to_sym
|
|
102
100
|
if machine = self.class.state_fu_machines[name]
|
|
103
101
|
state_fu_bindings[name] ||= StateFu::Binding.new( machine, self, name )
|