end_state 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/machine_spec.rb +2 -1
- data/lib/end_state/state_machine.rb +8 -3
- data/lib/end_state/transition.rb +26 -5
- data/lib/end_state/version.rb +1 -1
- data/lib/end_state_matchers.rb +21 -3
- data/spec/end_state/state_machine_spec.rb +44 -0
- data/spec/end_state/transition_spec.rb +66 -7
- 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: c3fceb0c3ef5dd0efcf27176e899e75ad55f1243
|
4
|
+
data.tar.gz: 600cc39bea6aeca957285707a280c29b07092ad1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05317e5548b4924e211f422a2a9c4eb6f6ddcc872f4fdff91828acf27768cde90ec608f10c35d50b098576e797a6c2ec4ae9a8a010fd9046c09c0e4a01e282f5
|
7
|
+
data.tar.gz: d3432693ec760a0ea06ba9790f075a3837571b0b53518b06f781ccaa9654832ff96b875eea229afb5ffe147fd1fbdc25bb9be93b4a6ea250552695c0cf3f7b2f
|
data/examples/machine_spec.rb
CHANGED
@@ -16,13 +16,14 @@ end
|
|
16
16
|
|
17
17
|
class Machine < EndState::StateMachine
|
18
18
|
transition a: :b do |t|
|
19
|
+
t.require_params :some_param
|
19
20
|
t.guard Easy
|
20
21
|
t.concluder NoOp
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
25
|
describe Machine do
|
25
|
-
specify { expect(Machine).to have_transition(a: :b).with_guard(Easy).with_concluder(NoOp) }
|
26
|
+
specify { expect(Machine).to have_transition(a: :b).with_guard(Easy).with_concluder(NoOp).with_required_params(:some_param) }
|
26
27
|
specify { expect(Machine).to have_transition(a: :b).with_guards(Easy, Easy).with_concluders(NoOp, NoOp) }
|
27
28
|
specify { expect(Machine).not_to have_transition(a: :c) }
|
28
29
|
end
|
@@ -80,7 +80,7 @@ module EndState
|
|
80
80
|
def can_transition?(state, params = {})
|
81
81
|
previous_state = self.state.to_sym
|
82
82
|
state = state.to_sym
|
83
|
-
transition =
|
83
|
+
transition = transition_for(previous_state, state)
|
84
84
|
return block_transistion(transition, state, :soft) unless transition
|
85
85
|
transition.will_allow? state, params
|
86
86
|
end
|
@@ -90,7 +90,7 @@ module EndState
|
|
90
90
|
@success_messages = []
|
91
91
|
previous_state = self.state ? self.state.to_sym : self.state
|
92
92
|
state = state.to_sym
|
93
|
-
transition =
|
93
|
+
transition = transition_for(previous_state, state)
|
94
94
|
return block_transistion(transition, state, mode) unless transition
|
95
95
|
return guard_failed(state, mode) unless transition.allowed?(self, params)
|
96
96
|
return false unless transition.action.new(self, state).call
|
@@ -109,7 +109,7 @@ module EndState
|
|
109
109
|
check_state = state_for_event(check_state) || check_state
|
110
110
|
return false if check_state == :__invalid_event__
|
111
111
|
if method.to_s.end_with?('!')
|
112
|
-
transition check_state, args[0]
|
112
|
+
transition check_state, (args[0] || {})
|
113
113
|
else
|
114
114
|
super
|
115
115
|
end
|
@@ -132,6 +132,11 @@ module EndState
|
|
132
132
|
transitions.first.values.first
|
133
133
|
end
|
134
134
|
|
135
|
+
def transition_for(from, to)
|
136
|
+
self.class.transitions[{ from => to }] ||
|
137
|
+
self.class.transitions[{ any_state: to }]
|
138
|
+
end
|
139
|
+
|
135
140
|
def invalid_event(event)
|
136
141
|
fail InvalidEvent, "Transition by event: #{event} is invalid." if self.class.mode == :hard
|
137
142
|
message = self.class.transitions[self.class.events[event].first].blocked_event_message
|
data/lib/end_state/transition.rb
CHANGED
@@ -1,20 +1,24 @@
|
|
1
1
|
module EndState
|
2
2
|
class Transition
|
3
3
|
attr_reader :state, :blocked_event_message
|
4
|
-
attr_accessor :action, :guards, :concluders
|
4
|
+
attr_accessor :action, :guards, :concluders, :allowed_params, :required_params
|
5
5
|
|
6
6
|
def initialize(state)
|
7
7
|
@state = state
|
8
8
|
@action = Action
|
9
9
|
@guards = []
|
10
10
|
@concluders = []
|
11
|
+
@allowed_params = []
|
12
|
+
@required_params = []
|
11
13
|
end
|
12
14
|
|
13
15
|
def allowed?(object, params={})
|
16
|
+
raise "Missing params: #{missing_params(params).join(',')}" unless missing_params(params).empty?
|
14
17
|
guards.all? { |guard| guard.new(object, state, params).allowed? }
|
15
18
|
end
|
16
19
|
|
17
20
|
def will_allow?(object, params={})
|
21
|
+
return false unless missing_params(params).empty?
|
18
22
|
guards.all? { |guard| guard.new(object, state, params).will_allow? }
|
19
23
|
end
|
20
24
|
|
@@ -30,18 +34,31 @@ module EndState
|
|
30
34
|
@action = action
|
31
35
|
end
|
32
36
|
|
33
|
-
def guard(
|
34
|
-
guards << guard
|
37
|
+
def guard(*guards)
|
38
|
+
Array(guards).flatten.each { |guard| self.guards << guard }
|
35
39
|
end
|
36
40
|
|
37
|
-
def concluder(
|
38
|
-
concluders << concluder
|
41
|
+
def concluder(*concluders)
|
42
|
+
Array(concluders).flatten.each { |concluder| self.concluders << concluder }
|
39
43
|
end
|
40
44
|
|
41
45
|
def persistence_on
|
42
46
|
concluder Concluders::Persistence
|
43
47
|
end
|
44
48
|
|
49
|
+
def allow_params(*params)
|
50
|
+
Array(params).flatten.each do |param|
|
51
|
+
self.allowed_params << param unless self.allowed_params.include? param
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def require_params(*params)
|
56
|
+
Array(params).flatten.each do |param|
|
57
|
+
self.allowed_params << param unless self.allowed_params.include? param
|
58
|
+
self.required_params << param unless self.required_params.include? param
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
45
62
|
def blocked(message)
|
46
63
|
@blocked_event_message = message
|
47
64
|
end
|
@@ -63,5 +80,9 @@ module EndState
|
|
63
80
|
def run_concluder(concluder, object, state, params)
|
64
81
|
concluder.new(object, state, params).call
|
65
82
|
end
|
83
|
+
|
84
|
+
def missing_params(params)
|
85
|
+
required_params.select { |key| params[key].nil? }
|
86
|
+
end
|
66
87
|
end
|
67
88
|
end
|
data/lib/end_state/version.rb
CHANGED
data/lib/end_state_matchers.rb
CHANGED
@@ -4,13 +4,14 @@ module EndStateMatchers
|
|
4
4
|
end
|
5
5
|
|
6
6
|
class TransitionMatcher
|
7
|
-
attr_reader :transition, :machine, :failure_messages, :guards, :concluders
|
7
|
+
attr_reader :transition, :machine, :failure_messages, :guards, :concluders, :required_params
|
8
8
|
|
9
9
|
def initialize(transition)
|
10
10
|
@transition = transition
|
11
11
|
@failure_messages = []
|
12
12
|
@guards = []
|
13
13
|
@concluders = []
|
14
|
+
@required_params = []
|
14
15
|
end
|
15
16
|
|
16
17
|
def matches?(actual)
|
@@ -32,7 +33,7 @@ module EndStateMatchers
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def with_guards(*guards)
|
35
|
-
@guards += Array(guards)
|
36
|
+
@guards += Array(guards).flatten
|
36
37
|
self
|
37
38
|
end
|
38
39
|
|
@@ -42,7 +43,12 @@ module EndStateMatchers
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def with_concluders(*concluders)
|
45
|
-
@concluders += Array(concluders)
|
46
|
+
@concluders += Array(concluders).flatten
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def with_required_params(*params)
|
51
|
+
@required_params += Array(params).flatten
|
46
52
|
self
|
47
53
|
end
|
48
54
|
|
@@ -59,6 +65,7 @@ module EndStateMatchers
|
|
59
65
|
if machine.transitions.keys.include? transition
|
60
66
|
result = (result && verify_guards) if guards.any?
|
61
67
|
result = (result && verify_concluders) if concluders.any?
|
68
|
+
result = (result && verify_required_params) if required_params.any?
|
62
69
|
result
|
63
70
|
else
|
64
71
|
failure_messages << "expected that #{machine.name} would have transition :#{transition.keys.first} => :#{transition.values.first}"
|
@@ -87,6 +94,17 @@ module EndStateMatchers
|
|
87
94
|
end
|
88
95
|
result
|
89
96
|
end
|
97
|
+
|
98
|
+
def verify_required_params
|
99
|
+
result = true
|
100
|
+
required_params.each do |param|
|
101
|
+
unless machine.transitions[transition].required_params.any? { |p| p == param }
|
102
|
+
failure_messages << "expected that transition :#{transition.keys.first} => :#{transition.values.first} would have required param #{param}"
|
103
|
+
result = false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
result
|
107
|
+
end
|
90
108
|
end
|
91
109
|
end
|
92
110
|
|
@@ -189,6 +189,12 @@ module EndState
|
|
189
189
|
expect(machine).to have_received(:transition).with(:b, { foo: 'bar', bar: 'foo' })
|
190
190
|
end
|
191
191
|
|
192
|
+
it 'defaults params to {}' do
|
193
|
+
machine.stub(:transition)
|
194
|
+
machine.b!
|
195
|
+
expect(machine).to have_received(:transition).with(:b, {})
|
196
|
+
end
|
197
|
+
|
192
198
|
it 'works with an event' do
|
193
199
|
machine.go!
|
194
200
|
expect(machine.state).to eq :b
|
@@ -231,6 +237,22 @@ module EndState
|
|
231
237
|
context 'when asking about a disallowed transition' do
|
232
238
|
specify { expect(machine.can_transition? :c).to be_false }
|
233
239
|
end
|
240
|
+
|
241
|
+
context 'when using :any_state' do
|
242
|
+
before { StateMachine.transition any_state: :d }
|
243
|
+
|
244
|
+
context 'and the initial state is :a' do
|
245
|
+
let(:object) { OpenStruct.new(state: :a) }
|
246
|
+
|
247
|
+
specify { expect(machine.can_transition? :d).to be_true }
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'and the initial state is :b' do
|
251
|
+
let(:object) { OpenStruct.new(state: :b) }
|
252
|
+
|
253
|
+
specify { expect(machine.can_transition? :d).to be_true }
|
254
|
+
end
|
255
|
+
end
|
234
256
|
end
|
235
257
|
|
236
258
|
describe '#transition' do
|
@@ -267,6 +289,28 @@ module EndState
|
|
267
289
|
expect(object.state).to eq 'b'
|
268
290
|
end
|
269
291
|
end
|
292
|
+
|
293
|
+
context 'and using :any_state' do
|
294
|
+
before { StateMachine.transition any_state: :d }
|
295
|
+
|
296
|
+
context 'and the initial state is :a' do
|
297
|
+
before { object.state = :a }
|
298
|
+
|
299
|
+
it 'transitions the state' do
|
300
|
+
machine.transition :d
|
301
|
+
expect(object.state).to eq :d
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
context 'and the initial state is :b' do
|
306
|
+
before { object.state = :b }
|
307
|
+
|
308
|
+
it 'transitions the state' do
|
309
|
+
machine.transition :d
|
310
|
+
expect(object.state).to eq :d
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
270
314
|
end
|
271
315
|
|
272
316
|
context 'and a guard is configured' do
|
@@ -24,10 +24,15 @@ module EndState
|
|
24
24
|
|
25
25
|
describe '#guard' do
|
26
26
|
let(:guard) { double :guard }
|
27
|
+
let(:another_guard) { double :another_guard }
|
27
28
|
|
28
29
|
it 'adds a guard' do
|
29
30
|
expect { transition.guard guard }.to change(transition.guards, :count).by(1)
|
30
31
|
end
|
32
|
+
|
33
|
+
it 'adds multiple guards' do
|
34
|
+
expect { transition.guard guard, another_guard }.to change(transition.guards, :count).by(2)
|
35
|
+
end
|
31
36
|
end
|
32
37
|
|
33
38
|
describe '#allowed?' do
|
@@ -40,6 +45,27 @@ module EndState
|
|
40
45
|
before { guard_instance.stub(:allowed?).and_return(true) }
|
41
46
|
|
42
47
|
specify { expect(transition.allowed? object).to be_true }
|
48
|
+
|
49
|
+
context 'when params are provided' do
|
50
|
+
it 'creates the guard with the params' do
|
51
|
+
transition.allowed? object, { foo: 'bar' }
|
52
|
+
expect(guard).to have_received(:new).with(object, state, { foo: 'bar' })
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'and some params are required' do
|
56
|
+
before { transition.require_params :foo, :bar }
|
57
|
+
|
58
|
+
context 'and not all required are provided' do
|
59
|
+
it 'throws an exception' do
|
60
|
+
expect { transition.allowed? object, foo: 'something' }.to raise_error('Missing params: bar')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'and all required are provided' do
|
65
|
+
specify { expect(transition.allowed? object, foo: 1, bar: 2).to be_true }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
43
69
|
end
|
44
70
|
|
45
71
|
context 'when not all guards pass' do
|
@@ -47,13 +73,6 @@ module EndState
|
|
47
73
|
|
48
74
|
specify { expect(transition.allowed? object).to be_false }
|
49
75
|
end
|
50
|
-
|
51
|
-
context 'when params are provided' do
|
52
|
-
it 'creates the guard with the params' do
|
53
|
-
transition.allowed? object, { foo: 'bar' }
|
54
|
-
expect(guard).to have_received(:new).with(object, state, { foo: 'bar' })
|
55
|
-
end
|
56
|
-
end
|
57
76
|
end
|
58
77
|
|
59
78
|
describe '#will_allow?' do
|
@@ -66,6 +85,25 @@ module EndState
|
|
66
85
|
before { guard_instance.stub(:will_allow?).and_return(true) }
|
67
86
|
|
68
87
|
specify { expect(transition.will_allow? object).to be_true }
|
88
|
+
|
89
|
+
context 'when params are provided' do
|
90
|
+
it 'creates the guard with the params' do
|
91
|
+
transition.will_allow? object, { foo: 'bar' }
|
92
|
+
expect(guard).to have_received(:new).with(object, state, { foo: 'bar' })
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'and some params are required' do
|
96
|
+
before { transition.require_params :foo, :bar }
|
97
|
+
|
98
|
+
context 'and not all required are provided' do
|
99
|
+
specify { expect(transition.will_allow? object).to be_false }
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'and all required are provided' do
|
103
|
+
specify { expect(transition.will_allow? object, foo: 1, bar: 2).to be_true }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
69
107
|
end
|
70
108
|
|
71
109
|
context 'when not all guards pass' do
|
@@ -84,10 +122,15 @@ module EndState
|
|
84
122
|
|
85
123
|
describe '#concluder' do
|
86
124
|
let(:concluder) { double :concluder }
|
125
|
+
let(:another_concluder) { double :another_concluder }
|
87
126
|
|
88
127
|
it 'adds a concluder' do
|
89
128
|
expect { transition.concluder concluder }.to change(transition.concluders, :count).by(1)
|
90
129
|
end
|
130
|
+
|
131
|
+
it 'adds multiple concluders' do
|
132
|
+
expect { transition.concluder concluder, another_concluder }.to change(transition.concluders, :count).by(2)
|
133
|
+
end
|
91
134
|
end
|
92
135
|
|
93
136
|
describe '#persistence_on' do
|
@@ -96,6 +139,22 @@ module EndState
|
|
96
139
|
end
|
97
140
|
end
|
98
141
|
|
142
|
+
describe '#allow_params' do
|
143
|
+
it 'adds supplied keys to the allowed_params array' do
|
144
|
+
expect { transition.allow_params :foo, :bar }.to change(transition.allowed_params, :count).by(2)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '#require_params' do
|
149
|
+
it 'adds supplied keys to the required_params array' do
|
150
|
+
expect { transition.require_params :foo, :bar }.to change(transition.required_params, :count).by(2)
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'adds supplied keys to the allowed_params array' do
|
154
|
+
expect { transition.allow_params :foo, :bar }.to change(transition.allowed_params, :count).by(2)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
99
158
|
describe '#conclude' do
|
100
159
|
let(:concluder) { double :concluder, new: concluder_instance }
|
101
160
|
let(:concluder_instance) { double :concluder_instance, call: nil, rollback: nil }
|
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.
|
4
|
+
version: 0.10.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-06-
|
11
|
+
date: 2014-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|