anthonyw-simple_state 0.1.3 → 0.2.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.markdown +104 -24
- data/VERSION.yml +2 -2
- data/lib/simple_state/builder.rb +29 -10
- data/lib/simple_state/mixins.rb +1 -1
- metadata +2 -2
data/README.markdown
CHANGED
@@ -10,38 +10,49 @@ There are several existing implementations of state machines in Ruby, notably
|
|
10
10
|
cumbersome, when all I really needed was a lightweight means for setting the
|
11
11
|
state of a class instance, and transitioning from one state to another.
|
12
12
|
|
13
|
-
There is no support for adding your own code to customise
|
14
|
-
is there any support for callbacks or event guards
|
15
|
-
|
16
|
-
|
17
|
-
permitted, and that's
|
13
|
+
There is no explicit support for adding your own code to customise
|
14
|
+
transitions, nor is there any support for callbacks or event guards (although
|
15
|
+
you can still do similar things fairly trivially). It's called **Simple**State
|
16
|
+
for a reason! The library adds some helper methods to your class, keeps track
|
17
|
+
of the valid states, makes sure that a transition is permitted, and that's
|
18
|
+
about it.
|
18
19
|
|
19
20
|
## Why use SimpleState?
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
<ul style="margin-top: 1em">
|
23
|
+
<li>Lightweight.</li>
|
24
|
+
<li>method_missing isn't used. ;)</li>
|
25
|
+
<li>No dependencies.</li>
|
26
|
+
<li>No extensions to core classes.</li>
|
27
|
+
<li>Tested on Ruby 1.8.6 (p287), 1.8.7 (p72), and 1.9.1 (p0).</li>
|
28
|
+
<li>Uses an API similar to Workflow, which I find to be more logical than
|
29
|
+
that in the acts_as_state_machine family.</li>
|
30
|
+
</ul>
|
28
31
|
|
29
32
|
## Why use something else?
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
34
|
+
<ul style="margin-top: 1em">
|
35
|
+
<li>The three libraries mentioned above make available, as part of their
|
36
|
+
DSL, a means of customising events/transitions with your own code.
|
37
|
+
SimpleState makes no such provision, however you can mimic the behaviour
|
38
|
+
quite easily as documented in example 3, below.</li>
|
39
|
+
<li>Similarly, some other libraries provide the ability to add guard
|
40
|
+
conditions -- a condition which must be satisfied before a transition
|
41
|
+
can take place. SimpleState also does explicitly support this, however it
|
42
|
+
is possible by adapting example 3.
|
43
|
+
<li>SimpleState forces you to use an attribute called `state` - other libraries
|
44
|
+
let you choose whatever name you want.</li>
|
45
|
+
<li>Uses a class variable to keep track of transitions - doesn't lend itself
|
46
|
+
all that well to subclassing your state machines.</li>
|
47
|
+
</ul>
|
48
|
+
|
49
|
+
If SimpleState's limitations are too much for you, then you are probably
|
50
|
+
better off choosing one of the other libraries instead.
|
42
51
|
|
43
52
|
## Examples
|
44
53
|
|
54
|
+
### Example 1: Basic usage
|
55
|
+
|
45
56
|
require 'rubygems'
|
46
57
|
require 'simple_state'
|
47
58
|
|
@@ -96,13 +107,15 @@ state of the instance.
|
|
96
107
|
|
97
108
|
# etc...
|
98
109
|
|
110
|
+
### Example 2: Events in multiple states
|
111
|
+
|
99
112
|
It is possible for the same event to be used in multiple states:
|
100
113
|
|
101
114
|
state :not_started do
|
102
115
|
event :start, :transitions_to => :started
|
103
116
|
event :cancel, :transitions_to => :cancelled # <--
|
104
117
|
end
|
105
|
-
|
118
|
+
|
106
119
|
state :started do
|
107
120
|
event :finish, :transitions_to => :finished
|
108
121
|
event :cancel, :transitions_to => :cancelled # <--
|
@@ -125,6 +138,73 @@ current state:
|
|
125
138
|
state :cancelled
|
126
139
|
state :cancelled_before_start
|
127
140
|
|
141
|
+
### Example 3: Customising event transitions
|
142
|
+
|
143
|
+
If the built in event methods aren't sufficient and you need to do extra stuff
|
144
|
+
to your class during a particular event, you can simply override the method;
|
145
|
+
the original method is available via `super`:
|
146
|
+
|
147
|
+
class OverriddenEvent
|
148
|
+
extend SimpleState
|
149
|
+
|
150
|
+
state_machine do
|
151
|
+
state :start do
|
152
|
+
event :start, :transitions_to => :started
|
153
|
+
end
|
154
|
+
|
155
|
+
state :started
|
156
|
+
end
|
157
|
+
|
158
|
+
def start!
|
159
|
+
puts "Before super() : state=#{self.state}"
|
160
|
+
ret = super
|
161
|
+
puts "After super() : state=#{self.state}"
|
162
|
+
ret
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
OverriddenEvent.new.start!
|
167
|
+
# => Before super() : state=start
|
168
|
+
# => After super() : state=finished
|
169
|
+
# => :started
|
170
|
+
|
171
|
+
If the event transition isn't valid, super will simply return false, otherwise
|
172
|
+
it will return the symbol representing the new state.
|
173
|
+
|
174
|
+
def start!
|
175
|
+
if new_state = super
|
176
|
+
puts "Started! The new state is #{self.state}"
|
177
|
+
else
|
178
|
+
puts "Could not start!"
|
179
|
+
end
|
180
|
+
|
181
|
+
new_state
|
182
|
+
end
|
183
|
+
|
184
|
+
machine = OverriddenEvent.new
|
185
|
+
machine.start!
|
186
|
+
=> Started! The new state is finished
|
187
|
+
=> :started
|
188
|
+
|
189
|
+
machine.start!
|
190
|
+
=> Could not start!
|
191
|
+
=> false
|
192
|
+
|
193
|
+
If you need to know whether a transition will be permitted before you call
|
194
|
+
super(), SimpleState provides `#event_permitted?`, expecting you to provide a
|
195
|
+
symbol representing the event.
|
196
|
+
|
197
|
+
machine.event_permitted?(:start)
|
198
|
+
# => true|false
|
199
|
+
|
200
|
+
This also provides an easy means for creating guard conditions:
|
201
|
+
|
202
|
+
def start!
|
203
|
+
if event_permitted?(:start) && SomeExternalService.can_start?(self)
|
204
|
+
super
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
128
208
|
## ORM Integration
|
129
209
|
|
130
210
|
SimpleState should play nicely with your ORM of choice. When an object's state
|
data/VERSION.yml
CHANGED
data/lib/simple_state/builder.rb
CHANGED
@@ -32,11 +32,31 @@ module SimpleState
|
|
32
32
|
|
33
33
|
# Create an anonymous module which will be added to the state machine
|
34
34
|
# class's inheritance chain.
|
35
|
-
mod = @mod = Module.new
|
35
|
+
mod = @mod = Module.new
|
36
|
+
mod.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
36
37
|
def self.inspect
|
37
|
-
"SimpleState::#{@klass}
|
38
|
+
"SimpleState::#{@klass}AnonMixin"
|
38
39
|
end
|
39
|
-
|
40
|
+
|
41
|
+
# Handles the change of state.
|
42
|
+
# @api private
|
43
|
+
def _change_state_using_event!(event)
|
44
|
+
self.state = self.class._determine_new_state(self.state, event)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns if the passed event is permitted with the instance in it's
|
48
|
+
# current state.
|
49
|
+
# @api public
|
50
|
+
def event_permitted?(event)
|
51
|
+
self.class._event_permitted?(self.state, event)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns true if the given symbol matches the current state.
|
55
|
+
# @api public
|
56
|
+
def in_state?(state)
|
57
|
+
self.state == state
|
58
|
+
end
|
59
|
+
RUBY
|
40
60
|
|
41
61
|
# Declare the state machine rules.
|
42
62
|
instance_eval(&blk)
|
@@ -61,9 +81,9 @@ module SimpleState
|
|
61
81
|
@klass.initial_state ||= name
|
62
82
|
|
63
83
|
@mod.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
64
|
-
def #{name}?
|
65
|
-
|
66
|
-
end
|
84
|
+
def #{name}? # def prepared?
|
85
|
+
in_state?(:#{name}) # self.state == :prepared
|
86
|
+
end # end
|
67
87
|
RUBY
|
68
88
|
|
69
89
|
# Define transitions for this state.
|
@@ -107,7 +127,7 @@ module SimpleState
|
|
107
127
|
# Example:
|
108
128
|
#
|
109
129
|
# def process!
|
110
|
-
# if self.class.
|
130
|
+
# if self.class._transition_permitted?(self.state, :process)
|
111
131
|
# self.state =
|
112
132
|
# self.class._determine_new_state(self.state, :process)
|
113
133
|
# else
|
@@ -116,9 +136,8 @@ module SimpleState
|
|
116
136
|
# end
|
117
137
|
@module.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
118
138
|
def #{event}!
|
119
|
-
if
|
120
|
-
|
121
|
-
self.class._determine_new_state(self.state, :#{event})
|
139
|
+
if event_permitted?(:#{event})
|
140
|
+
_change_state_using_event!(:#{event})
|
122
141
|
else
|
123
142
|
false
|
124
143
|
end
|
data/lib/simple_state/mixins.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anthonyw-simple_state
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anthony Williams
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-03-
|
12
|
+
date: 2009-03-11 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|