end_state 0.0.2 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +8 -6
- data/lib/end_state/state_machine.rb +25 -10
- data/lib/end_state/transition.rb +14 -14
- data/lib/end_state/version.rb +1 -1
- data/lib/end_state_matchers.rb +2 -2
- data/spec/end_state/state_machine_spec.rb +45 -15
- data/spec/end_state/transition_spec.rb +26 -23
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1d64ebcbb974d3190a824a6cf1c77742c009339
|
4
|
+
data.tar.gz: 6acbeb81100f52bf97956a2bcbe7c5680bf02cba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16a4bc9c85e10fce41a81659aa9bb70b182a3378030e2531f4b5077c2749cda53de5dec0b49e94862d6d71aaf698c047c5b5532dffd0079b5fc7dd459a4caf56
|
7
|
+
data.tar.gz: 953922dc24b1bccd281564ee4769d515e45ef4fa4cd30d28c945368e2bba89defb92c6da22464ff8f83ecbaa96d5b1ec0b0bd689c32c8acd8f18d4e229d01f95
|
data/README.md
CHANGED
@@ -29,7 +29,7 @@ Create a state machine by subclassing `EndState::StateMachine`.
|
|
29
29
|
|
30
30
|
```ruby
|
31
31
|
class Machine < EndState::StateMachine
|
32
|
-
transition a: :b
|
32
|
+
transition a: :b, as: :go
|
33
33
|
transition b: :c
|
34
34
|
transition [:b, :c] => :a
|
35
35
|
end
|
@@ -58,6 +58,8 @@ machine.can_transition? :a # => true
|
|
58
58
|
machine.b! # => false
|
59
59
|
machine.a! # => true
|
60
60
|
machine.state # => :a
|
61
|
+
machine.go! # => :true
|
62
|
+
machine.state # => :b
|
61
63
|
```
|
62
64
|
|
63
65
|
## Guards
|
@@ -66,7 +68,7 @@ Guards can be created by subclassing `EndState::Guard`. Your class will be provi
|
|
66
68
|
|
67
69
|
* `object` - The wrapped object.
|
68
70
|
* `state` - The desired state.
|
69
|
-
* `params` - A hash of params
|
71
|
+
* `params` - A hash of params passed when calling transition on the machine.
|
70
72
|
|
71
73
|
Your class should implement the `will_allow?` method which must return true or false.
|
72
74
|
|
@@ -95,7 +97,7 @@ A guard can be added to the transition definition:
|
|
95
97
|
class Machine < EndState::StateMachine
|
96
98
|
transition a: :b do |t|
|
97
99
|
t.guard EasyGuard
|
98
|
-
t.guard SomeOtherGuard
|
100
|
+
t.guard SomeOtherGuard
|
99
101
|
end
|
100
102
|
end
|
101
103
|
```
|
@@ -106,7 +108,7 @@ Finalizers can be created by subclassing `EndState::Finalizer`. Your class will
|
|
106
108
|
|
107
109
|
* `object` - The wrapped object that has been transitioned.
|
108
110
|
* `state` - The previous state.
|
109
|
-
* `params` - A hash of params
|
111
|
+
* `params` - A hash of params passed when calling transition on the machine.
|
110
112
|
|
111
113
|
Your class should implement the `call` method which should return true or false as to whether it was successful or not.
|
112
114
|
|
@@ -116,7 +118,7 @@ set up a little differently and you have access to:
|
|
116
118
|
|
117
119
|
* `object` - The wrapped object that has been rolled back.
|
118
120
|
* `state` - The attempted desired state.
|
119
|
-
* `params` - A hash of params
|
121
|
+
* `params` - A hash of params passed when calling transition on the machine.
|
120
122
|
|
121
123
|
The wrapped object has an array `failure_messages` available for tracking reasons for invalid transitions. You may shovel
|
122
124
|
a reason (string) into this if you want to provide information on why your finalizer failed.
|
@@ -139,7 +141,7 @@ A finalizer can be added to the transition definition:
|
|
139
141
|
```ruby
|
140
142
|
class Machine < EndState::StateMachine
|
141
143
|
transition a: :b do |t|
|
142
|
-
t.finalizer WrapUp
|
144
|
+
t.finalizer WrapUp
|
143
145
|
end
|
144
146
|
end
|
145
147
|
```
|
@@ -2,19 +2,28 @@ module EndState
|
|
2
2
|
class StateMachine < SimpleDelegator
|
3
3
|
attr_accessor :failure_messages
|
4
4
|
|
5
|
-
def self.transition(state_map
|
5
|
+
def self.transition(state_map)
|
6
|
+
initial_states = Array(state_map.keys.first)
|
6
7
|
final_state = state_map.values.first
|
8
|
+
transition_alias = state_map[:as] if state_map.keys.length > 1
|
7
9
|
transition = Transition.new(final_state)
|
8
|
-
|
10
|
+
initial_states.each do |state|
|
9
11
|
transitions[{ state => final_state }] = transition
|
10
12
|
end
|
11
|
-
|
13
|
+
unless transition_alias.nil?
|
14
|
+
aliases[transition_alias] = final_state
|
15
|
+
end
|
16
|
+
yield transition if block_given?
|
12
17
|
end
|
13
18
|
|
14
19
|
def self.transitions
|
15
20
|
@transitions ||= {}
|
16
21
|
end
|
17
22
|
|
23
|
+
def self.aliases
|
24
|
+
@aliases ||= {}
|
25
|
+
end
|
26
|
+
|
18
27
|
def self.state_attribute(attribute)
|
19
28
|
define_method(:state) { send(attribute.to_sym) }
|
20
29
|
define_method(:state=) { |val| send("#{attribute}=".to_sym, val) }
|
@@ -32,6 +41,11 @@ module EndState
|
|
32
41
|
transitions.keys.map { |state_map| state_map.values.first }.uniq
|
33
42
|
end
|
34
43
|
|
44
|
+
def self.transition_state_for(check_state)
|
45
|
+
return check_state if states.include? check_state
|
46
|
+
return aliases[check_state] if aliases.keys.include? check_state
|
47
|
+
end
|
48
|
+
|
35
49
|
def object
|
36
50
|
__getobj__
|
37
51
|
end
|
@@ -43,28 +57,29 @@ module EndState
|
|
43
57
|
transition.will_allow? state
|
44
58
|
end
|
45
59
|
|
46
|
-
def transition(state, mode = :soft)
|
60
|
+
def transition(state, params = {}, mode = :soft)
|
47
61
|
@failure_messages = []
|
48
62
|
previous_state = self.state
|
49
63
|
transition = self.class.transitions[{ previous_state => state }]
|
50
64
|
return block_transistion(transition, state, mode) unless transition
|
51
|
-
return guard_failed(state, mode) unless transition.allowed?(self)
|
65
|
+
return guard_failed(state, mode) unless transition.allowed?(self, params)
|
52
66
|
return false unless transition.action.new(self, state).call
|
53
|
-
return finalize_failed(state, mode) unless transition.finalize(self, previous_state)
|
67
|
+
return finalize_failed(state, mode) unless transition.finalize(self, previous_state, params)
|
54
68
|
true
|
55
69
|
end
|
56
70
|
|
57
|
-
def transition!(state)
|
58
|
-
transition state, :hard
|
71
|
+
def transition!(state, params = {})
|
72
|
+
transition state, params, :hard
|
59
73
|
end
|
60
74
|
|
61
75
|
def method_missing(method, *args, &block)
|
62
76
|
check_state = method.to_s[0..-2].to_sym
|
63
|
-
|
77
|
+
check_state = self.class.transition_state_for(check_state)
|
78
|
+
return super if check_state.nil?
|
64
79
|
if method.to_s.end_with?('?')
|
65
80
|
state == check_state
|
66
81
|
elsif method.to_s.end_with?('!')
|
67
|
-
transition check_state
|
82
|
+
transition check_state, args[0]
|
68
83
|
else
|
69
84
|
super
|
70
85
|
end
|
data/lib/end_state/transition.rb
CHANGED
@@ -10,18 +10,18 @@ module EndState
|
|
10
10
|
@finalizers = []
|
11
11
|
end
|
12
12
|
|
13
|
-
def allowed?(object)
|
14
|
-
guards.all? { |guard| guard
|
13
|
+
def allowed?(object, params={})
|
14
|
+
guards.all? { |guard| guard.new(object, state, params).allowed? }
|
15
15
|
end
|
16
16
|
|
17
|
-
def will_allow?(object)
|
18
|
-
guards.all? { |guard| guard
|
17
|
+
def will_allow?(object, params={})
|
18
|
+
guards.all? { |guard| guard.new(object, state, params).will_allow? }
|
19
19
|
end
|
20
20
|
|
21
|
-
def finalize(object, previous_state)
|
21
|
+
def finalize(object, previous_state, params={})
|
22
22
|
finalizers.each_with_object([]) do |finalizer, finalized|
|
23
23
|
finalized << finalizer
|
24
|
-
return rollback(finalized, object, previous_state) unless run_finalizer(finalizer, object, state)
|
24
|
+
return rollback(finalized, object, previous_state, params) unless run_finalizer(finalizer, object, state, params)
|
25
25
|
end
|
26
26
|
true
|
27
27
|
end
|
@@ -30,12 +30,12 @@ module EndState
|
|
30
30
|
@action = action
|
31
31
|
end
|
32
32
|
|
33
|
-
def guard(guard
|
34
|
-
guards <<
|
33
|
+
def guard(guard)
|
34
|
+
guards << guard
|
35
35
|
end
|
36
36
|
|
37
|
-
def finalizer(finalizer
|
38
|
-
finalizers <<
|
37
|
+
def finalizer(finalizer)
|
38
|
+
finalizers << finalizer
|
39
39
|
end
|
40
40
|
|
41
41
|
def persistence_on
|
@@ -44,14 +44,14 @@ module EndState
|
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
def rollback(finalized, object, previous_state)
|
47
|
+
def rollback(finalized, object, previous_state, params)
|
48
48
|
action.new(object, previous_state).rollback
|
49
|
-
finalized.reverse.each { |
|
49
|
+
finalized.reverse.each { |finalizer| finalizer.new(object, state, params).rollback }
|
50
50
|
false
|
51
51
|
end
|
52
52
|
|
53
|
-
def run_finalizer(finalizer, object, state)
|
54
|
-
finalizer
|
53
|
+
def run_finalizer(finalizer, object, state, params)
|
54
|
+
finalizer.new(object, state, params).call
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
data/lib/end_state/version.rb
CHANGED
data/lib/end_state_matchers.rb
CHANGED
@@ -63,7 +63,7 @@ module EndStateMatchers
|
|
63
63
|
def verify_guards
|
64
64
|
result = true
|
65
65
|
guards.each do |guard|
|
66
|
-
unless machine.transitions[transition].guards.any? { |g| g
|
66
|
+
unless machine.transitions[transition].guards.any? { |g| g == guard }
|
67
67
|
failure_messages << "expected that transition :#{transition.keys.first} => :#{transition.values.first} would have guard #{guard.name}"
|
68
68
|
result = false
|
69
69
|
end
|
@@ -74,7 +74,7 @@ module EndStateMatchers
|
|
74
74
|
def verify_finalizers
|
75
75
|
result = true
|
76
76
|
finalizers.each do |finalizer|
|
77
|
-
unless machine.transitions[transition].finalizers.any? { |f| f
|
77
|
+
unless machine.transitions[transition].finalizers.any? { |f| f == finalizer }
|
78
78
|
failure_messages << "expected that transition :#{transition.keys.first} => :#{transition.values.first} would have finalizer #{finalizer.name}"
|
79
79
|
result = false
|
80
80
|
end
|
@@ -5,7 +5,10 @@ module EndState
|
|
5
5
|
describe StateMachine do
|
6
6
|
subject(:machine) { StateMachine.new(object) }
|
7
7
|
let(:object) { OpenStruct.new(state: nil) }
|
8
|
-
before
|
8
|
+
before do
|
9
|
+
StateMachine.instance_variable_set '@transitions'.to_sym, nil
|
10
|
+
StateMachine.instance_variable_set '@aliases'.to_sym, nil
|
11
|
+
end
|
9
12
|
|
10
13
|
describe '.transition' do
|
11
14
|
let(:state_map) { { a: :b } }
|
@@ -23,6 +26,13 @@ module EndState
|
|
23
26
|
it 'adds the transition to the state machine' do
|
24
27
|
expect(StateMachine.transitions[state_map]).to eq yielded.transition
|
25
28
|
end
|
29
|
+
|
30
|
+
context 'when the :as option is used' do
|
31
|
+
it 'creates an alias' do
|
32
|
+
StateMachine.transition(state_map.merge(as: :go))
|
33
|
+
expect(StateMachine.aliases[:go]).to eq :b
|
34
|
+
end
|
35
|
+
end
|
26
36
|
end
|
27
37
|
|
28
38
|
describe '.state_attribute' do
|
@@ -111,13 +121,24 @@ module EndState
|
|
111
121
|
describe '#{state}!' do
|
112
122
|
let(:object) { OpenStruct.new(state: :a) }
|
113
123
|
before do
|
114
|
-
StateMachine.transition a: :b
|
124
|
+
StateMachine.transition a: :b, as: :go
|
115
125
|
end
|
116
126
|
|
117
127
|
it 'transitions the state' do
|
118
128
|
machine.b!
|
119
129
|
expect(machine.state).to eq :b
|
120
130
|
end
|
131
|
+
|
132
|
+
it 'accepts params' do
|
133
|
+
machine.stub(:transition)
|
134
|
+
machine.b! foo: 'bar', bar: 'foo'
|
135
|
+
expect(machine).to have_received(:transition).with(:b, { foo: 'bar', bar: 'foo' })
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'works with an alias' do
|
139
|
+
machine.go!
|
140
|
+
expect(machine.state).to eq :b
|
141
|
+
end
|
121
142
|
end
|
122
143
|
|
123
144
|
describe '#can_transition?' do
|
@@ -167,8 +188,9 @@ module EndState
|
|
167
188
|
let(:guard) { double :guard, new: guard_instance }
|
168
189
|
let(:guard_instance) { double :guard_instance, allowed?: nil }
|
169
190
|
before do
|
170
|
-
StateMachine.transition a: :b
|
171
|
-
|
191
|
+
StateMachine.transition a: :b do |transition|
|
192
|
+
transition.guard guard
|
193
|
+
end
|
172
194
|
end
|
173
195
|
|
174
196
|
context 'and the object satisfies the guard' do
|
@@ -194,19 +216,28 @@ module EndState
|
|
194
216
|
expect(object.state).to eq :a
|
195
217
|
end
|
196
218
|
end
|
219
|
+
|
220
|
+
context 'and params are passed in' do
|
221
|
+
let(:params) { { foo: 'bar' } }
|
222
|
+
it 'sends the guard the params' do
|
223
|
+
machine.transition :b, params, :soft
|
224
|
+
expect(guard).to have_received(:new).with(machine, :b, params)
|
225
|
+
end
|
226
|
+
end
|
197
227
|
end
|
198
228
|
|
199
229
|
context 'and a finalizer is configured' do
|
230
|
+
let(:finalizer) { double :finalizer, new: finalizer_instance }
|
231
|
+
let(:finalizer_instance) { double :finalizer_instance, call: nil, rollback: nil }
|
200
232
|
before do
|
201
233
|
StateMachine.transition a: :b do |transition|
|
202
|
-
transition.
|
234
|
+
transition.finalizer finalizer
|
203
235
|
end
|
204
236
|
end
|
205
237
|
|
206
238
|
context 'and the finalizer is successful' do
|
207
239
|
before do
|
208
|
-
|
209
|
-
object.stub(:save).and_return(true)
|
240
|
+
finalizer_instance.stub(:call).and_return(true)
|
210
241
|
end
|
211
242
|
|
212
243
|
it 'transitions the state' do
|
@@ -217,8 +248,7 @@ module EndState
|
|
217
248
|
|
218
249
|
context 'and the finalizer fails' do
|
219
250
|
before do
|
220
|
-
|
221
|
-
object.stub(:save).and_return(false)
|
251
|
+
finalizer_instance.stub(:call).and_return(false)
|
222
252
|
end
|
223
253
|
|
224
254
|
it 'does not transition the state' do
|
@@ -262,7 +292,7 @@ module EndState
|
|
262
292
|
let(:guard_instance) { double :guard_instance, allowed?: nil }
|
263
293
|
before do
|
264
294
|
StateMachine.transition a: :b
|
265
|
-
StateMachine.transitions[{ a: :b }].guards <<
|
295
|
+
StateMachine.transitions[{ a: :b }].guards << guard
|
266
296
|
end
|
267
297
|
|
268
298
|
context 'and the object satisfies the guard' do
|
@@ -291,16 +321,17 @@ module EndState
|
|
291
321
|
end
|
292
322
|
|
293
323
|
context 'and a finalizer is configured' do
|
324
|
+
let(:finalizer) { double :finalizer, new: finalizer_instance }
|
325
|
+
let(:finalizer_instance) { double :finalizer_instance, call: nil, rollback: nil }
|
294
326
|
before do
|
295
327
|
StateMachine.transition a: :b do |transition|
|
296
|
-
transition.
|
328
|
+
transition.finalizer finalizer
|
297
329
|
end
|
298
330
|
end
|
299
331
|
|
300
332
|
context 'and the finalizer is successful' do
|
301
333
|
before do
|
302
|
-
|
303
|
-
object.stub(:save).and_return(true)
|
334
|
+
finalizer_instance.stub(:call).and_return(true)
|
304
335
|
end
|
305
336
|
|
306
337
|
it 'transitions the state' do
|
@@ -311,8 +342,7 @@ module EndState
|
|
311
342
|
|
312
343
|
context 'and the finalizer fails' do
|
313
344
|
before do
|
314
|
-
|
315
|
-
object.stub(:save).and_return(false)
|
345
|
+
finalizer_instance.stub(:call).and_return(false)
|
316
346
|
end
|
317
347
|
|
318
348
|
it 'does not transition the state' do
|
@@ -21,54 +21,58 @@ module EndState
|
|
21
21
|
it 'adds a guard' do
|
22
22
|
expect { transition.guard guard }.to change(transition.guards, :count).by(1)
|
23
23
|
end
|
24
|
-
|
25
|
-
context 'when params are provided' do
|
26
|
-
let(:params) { {} }
|
27
|
-
|
28
|
-
it 'adds a guard' do
|
29
|
-
expect { transition.guard guard, params }.to change(transition.guards, :count).by(1)
|
30
|
-
end
|
31
|
-
end
|
32
24
|
end
|
33
25
|
|
34
26
|
describe '#allowed?' do
|
35
27
|
let(:guard) { double :guard, new: guard_instance }
|
36
28
|
let(:guard_instance) { double :guard_instance, allowed?: nil }
|
37
|
-
|
29
|
+
let(:object) { double :object }
|
30
|
+
before { transition.guards << guard }
|
38
31
|
|
39
32
|
context 'when all guards pass' do
|
40
|
-
let(:object) { double :object }
|
41
33
|
before { guard_instance.stub(:allowed?).and_return(true) }
|
42
34
|
|
43
35
|
specify { expect(transition.allowed? object).to be_true }
|
44
36
|
end
|
45
37
|
|
46
38
|
context 'when not all guards pass' do
|
47
|
-
let(:object) { double :object }
|
48
39
|
before { guard_instance.stub(:allowed?).and_return(false) }
|
49
40
|
|
50
41
|
specify { expect(transition.allowed? object).to be_false }
|
51
42
|
end
|
43
|
+
|
44
|
+
context 'when params are provided' do
|
45
|
+
it 'creates the guard with the params' do
|
46
|
+
transition.allowed? object, { foo: 'bar' }
|
47
|
+
expect(guard).to have_received(:new).with(object, state, { foo: 'bar' })
|
48
|
+
end
|
49
|
+
end
|
52
50
|
end
|
53
51
|
|
54
52
|
describe '#will_allow?' do
|
55
53
|
let(:guard) { double :guard, new: guard_instance }
|
56
54
|
let(:guard_instance) { double :guard_instance, will_allow?: nil }
|
57
|
-
|
55
|
+
let(:object) { double :object }
|
56
|
+
before { transition.guards << guard }
|
58
57
|
|
59
58
|
context 'when all guards pass' do
|
60
|
-
let(:object) { double :object }
|
61
59
|
before { guard_instance.stub(:will_allow?).and_return(true) }
|
62
60
|
|
63
61
|
specify { expect(transition.will_allow? object).to be_true }
|
64
62
|
end
|
65
63
|
|
66
64
|
context 'when not all guards pass' do
|
67
|
-
let(:object) { double :object }
|
68
65
|
before { guard_instance.stub(:will_allow?).and_return(false) }
|
69
66
|
|
70
67
|
specify { expect(transition.will_allow? object).to be_false }
|
71
68
|
end
|
69
|
+
|
70
|
+
context 'when params are provided' do
|
71
|
+
it 'creates the guard with the params' do
|
72
|
+
transition.will_allow? object, { foo: 'bar' }
|
73
|
+
expect(guard).to have_received(:new).with(object, state, { foo: 'bar' })
|
74
|
+
end
|
75
|
+
end
|
72
76
|
end
|
73
77
|
|
74
78
|
describe '#finalizer' do
|
@@ -77,14 +81,6 @@ module EndState
|
|
77
81
|
it 'adds a finalizer' do
|
78
82
|
expect { transition.finalizer finalizer }.to change(transition.finalizers, :count).by(1)
|
79
83
|
end
|
80
|
-
|
81
|
-
context 'when params are provided' do
|
82
|
-
let(:params) { {} }
|
83
|
-
|
84
|
-
it 'adds a finalizer' do
|
85
|
-
expect { transition.finalizer finalizer, params }.to change(transition.finalizers, :count).by(1)
|
86
|
-
end
|
87
|
-
end
|
88
84
|
end
|
89
85
|
|
90
86
|
describe '#persistence_on' do
|
@@ -97,7 +93,7 @@ module EndState
|
|
97
93
|
let(:finalizer) { double :finalizer, new: finalizer_instance }
|
98
94
|
let(:finalizer_instance) { double :finalizer_instance, call: nil, rollback: nil }
|
99
95
|
let(:object) { OpenStruct.new(state: :b) }
|
100
|
-
before { transition.finalizers <<
|
96
|
+
before { transition.finalizers << finalizer }
|
101
97
|
|
102
98
|
context 'when all finalizers succeed' do
|
103
99
|
before { finalizer_instance.stub(:call).and_return(true) }
|
@@ -115,6 +111,13 @@ module EndState
|
|
115
111
|
expect(finalizer_instance).to have_received(:rollback)
|
116
112
|
end
|
117
113
|
end
|
114
|
+
|
115
|
+
context 'when params are provided' do
|
116
|
+
it 'creates a finalizer with the params' do
|
117
|
+
transition.finalize object, :b, { foo: 'bar' }
|
118
|
+
expect(finalizer).to have_received(:new).twice.with(object, :a, { foo: 'bar'} )
|
119
|
+
end
|
120
|
+
end
|
118
121
|
end
|
119
122
|
end
|
120
123
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: end_state
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- alexpeachey
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|