stateful.rb 2.1.0 → 2.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +137 -0
  3. data/Gemfile +2 -0
  4. data/README.md +309 -0
  5. data/lib/Stateful/Sequel/ClassMethods.rb +117 -0
  6. data/lib/Stateful/Sequel.rb +30 -0
  7. data/lib/Stateful/VERSION.rb +1 -1
  8. data/lib/Stateful.rb +3 -0
  9. data/stateful.rb.gemspec +42 -0
  10. data/test/ActiveRecord/WithColumnName.rb +159 -0
  11. data/test/ActiveRecord/WithExplicitDeterministicEventOrdering.rb +159 -0
  12. data/test/ActiveRecord/WithExplicitGlobalNonDeterministicEventOrdering.rb +165 -0
  13. data/test/ActiveRecord/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb +163 -0
  14. data/test/ActiveRecord/WithExplicitNonDeterministicEventOrdering.rb +161 -0
  15. data/test/ActiveRecord/WithInitialStateBlock.rb +157 -0
  16. data/test/ActiveRecord/WithMultipleFinalStates.rb +188 -0
  17. data/test/ActiveRecord/WithMultipleStateMachines.rb +221 -0
  18. data/test/ActiveRecord/WithMultipleStateMachinesAndStatefulBlock.rb +223 -0
  19. data/test/ActiveRecord/WithPersistenceNonSpecificExtend.rb +159 -0
  20. data/test/ActiveRecord/WithPersistenceNonSpecificExtendAndStatefulBlock.rb +161 -0
  21. data/test/ActiveRecord/WithPersistenceSpecificExtend.rb +159 -0
  22. data/test/ActiveRecord/WithPersistenceSpecificExtendAndStatefulBlock.rb +161 -0
  23. data/test/ActiveRecord/test_helper.rb +9 -0
  24. data/test/ActiveRecord.rb +5 -0
  25. data/test/Poro/WithExplicitDeterministicEventOrdering.rb +147 -0
  26. data/test/Poro/WithExplicitGlobalNonDeterministicEventOrdering.rb +153 -0
  27. data/test/Poro/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb +151 -0
  28. data/test/Poro/WithExplicitNonDeterministicEventOrdering.rb +149 -0
  29. data/test/Poro/WithInitialStateBlock.rb +145 -0
  30. data/test/Poro/WithMultipleFinalStates.rb +176 -0
  31. data/test/Poro/WithMultipleStateMachines.rb +192 -0
  32. data/test/Poro/WithMultipleStateMachinesAndStatefulBlock.rb +194 -0
  33. data/test/Poro/WithPersistenceNonSpecificExtend.rb +147 -0
  34. data/test/Poro/WithPersistenceNonSpecificExtendAndStatefulBlock.rb +149 -0
  35. data/test/Poro/WithPersistenceSpecificExtend.rb +147 -0
  36. data/test/Poro/WithPersistenceSpecificExtendAndStatefulBlock.rb +149 -0
  37. data/test/Poro/WithVariableName.rb +147 -0
  38. data/test/Poro.rb +5 -0
  39. data/test/Sequel/WithColumnName.rb +154 -0
  40. data/test/Sequel/WithExplicitDeterministicEventOrdering.rb +154 -0
  41. data/test/Sequel/WithExplicitGlobalNonDeterministicEventOrdering.rb +160 -0
  42. data/test/Sequel/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb +158 -0
  43. data/test/Sequel/WithExplicitNonDeterministicEventOrdering.rb +156 -0
  44. data/test/Sequel/WithInitialStateBlock.rb +152 -0
  45. data/test/Sequel/WithMultipleFinalStates.rb +183 -0
  46. data/test/Sequel/WithMultipleStateMachines.rb +216 -0
  47. data/test/Sequel/WithMultipleStateMachinesAndStatefulBlock.rb +218 -0
  48. data/test/Sequel/WithPersistenceNonSpecificExtend.rb +154 -0
  49. data/test/Sequel/WithPersistenceNonSpecificExtendAndStatefulBlock.rb +156 -0
  50. data/test/Sequel/WithPersistenceSpecificExtend.rb +154 -0
  51. data/test/Sequel/WithPersistenceSpecificExtendAndStatefulBlock.rb +156 -0
  52. data/test/Sequel/test_helper.rb +5 -0
  53. data/test/Sequel.rb +5 -0
  54. data/test/Stateful.rb +15 -0
  55. metadata +69 -4
@@ -0,0 +1,221 @@
1
+ # test/ActiveRecord/WithMultipleStateMachines.rb
2
+
3
+ gem 'minitest'
4
+ gem 'minitest-spec-context'
5
+
6
+ require 'minitest/autorun'
7
+ require 'minitest-spec-context'
8
+
9
+ require_relative './test_helper'
10
+
11
+ lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
12
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
13
+
14
+ require 'Stateful'
15
+
16
+ class CreateTableMachines < ActiveRecord::Migration[6.0]
17
+ def change
18
+ create_table :active_record_machine11s do |t|
19
+ t.string :fulfilment_state
20
+ t.string :payment_state
21
+ end
22
+ end
23
+ end
24
+
25
+ class ActiveRecordMachine11 < ActiveRecord::Base
26
+ extend Stateful
27
+
28
+ state_machine :fulfilment do
29
+ initial_state :pending do
30
+ on :items_collected => :ready
31
+ end
32
+
33
+ state :ready do
34
+ on :dispatch => :dispatched
35
+ end
36
+
37
+ final_state :dispatched
38
+ end
39
+
40
+ state_machine :payment do
41
+ initial_state :unpaid do
42
+ on :pay => :paid
43
+ end
44
+
45
+ state :paid do
46
+ on :refund => :refunded
47
+ end
48
+
49
+ final_state :refunded
50
+ end
51
+ end
52
+
53
+ CreateTableMachines.new.change
54
+
55
+ describe Stateful::ActiveRecord do
56
+ let(:machine){ActiveRecordMachine11.create}
57
+
58
+ context "initial states" do
59
+ it "must be in the fulfilment initial state" do
60
+ _(machine.fulfilment_initial_state?).must_equal true
61
+ end
62
+
63
+ it "must be in the payment initial state" do
64
+ _(machine.payment_initial_state?).must_equal true
65
+ end
66
+
67
+ it "must have the correct fulfilment state name" do
68
+ _(machine.fulfilment_state.name).must_equal :pending
69
+ end
70
+
71
+ it "must have the correct payment state name" do
72
+ _(machine.payment_state.name).must_equal :unpaid
73
+ end
74
+
75
+ it "must have fulfilment transitions" do
76
+ _(machine.fulfilment_transitions.size).must_equal 1
77
+ _(machine.fulfilment_transitions.first.event_name).must_equal :items_collected
78
+ end
79
+
80
+ it "must have payment transitions" do
81
+ _(machine.payment_transitions.size).must_equal 1
82
+ _(machine.payment_transitions.first.event_name).must_equal :pay
83
+ end
84
+
85
+ it "must not be in a fulfilment final state" do
86
+ _(machine.fulfilment_final_state?).must_equal false
87
+ end
88
+
89
+ it "must not be in a payment final state" do
90
+ _(machine.payment_final_state?).must_equal false
91
+ end
92
+
93
+ it "must have domain predicates" do
94
+ _(machine.pending?).must_equal true
95
+ _(machine.unpaid?).must_equal true
96
+ end
97
+ end
98
+
99
+ context "independent state transitions" do
100
+ before do
101
+ machine.pay
102
+ end
103
+
104
+ it "must still be in the fulfilment initial state" do
105
+ _(machine.fulfilment_initial_state?).must_equal true
106
+ _(machine.pending?).must_equal true
107
+ end
108
+
109
+ it "must have advanced the payment state" do
110
+ _(machine.payment_state.name).must_equal :paid
111
+ _(machine.paid?).must_equal true
112
+ end
113
+
114
+ it "must not be in the payment initial state" do
115
+ _(machine.payment_initial_state?).must_equal false
116
+ end
117
+
118
+ it "must have persisted the payment state" do
119
+ machine.reload
120
+ _(machine.payment_state.name).must_equal :paid
121
+ end
122
+
123
+ it "must have persisted the fulfilment state as initial" do
124
+ machine.reload
125
+ _(machine.fulfilment_state.name).must_equal :pending
126
+ end
127
+ end
128
+
129
+ context "both machines advanced" do
130
+ before do
131
+ machine.items_collected
132
+ machine.pay
133
+ end
134
+
135
+ it "must be in the ready fulfilment state" do
136
+ _(machine.fulfilment_state.name).must_equal :ready
137
+ _(machine.ready?).must_equal true
138
+ end
139
+
140
+ it "must be in the paid payment state" do
141
+ _(machine.payment_state.name).must_equal :paid
142
+ _(machine.paid?).must_equal true
143
+ end
144
+
145
+ it "must have correct fulfilment transitions" do
146
+ _(machine.fulfilment_transitions.size).must_equal 1
147
+ _(machine.fulfilment_transitions.first.event_name).must_equal :dispatch
148
+ end
149
+
150
+ it "must have correct payment transitions" do
151
+ _(machine.payment_transitions.size).must_equal 1
152
+ _(machine.payment_transitions.first.event_name).must_equal :refund
153
+ end
154
+
155
+ it "must have persisted both states" do
156
+ machine.reload
157
+ _(machine.fulfilment_state.name).must_equal :ready
158
+ _(machine.payment_state.name).must_equal :paid
159
+ end
160
+ end
161
+
162
+ context "fulfilment final state" do
163
+ before do
164
+ machine.items_collected
165
+ machine.dispatch
166
+ end
167
+
168
+ it "must be in the fulfilment final state" do
169
+ _(machine.fulfilment_final_state?).must_equal true
170
+ _(machine.dispatched?).must_equal true
171
+ end
172
+
173
+ it "must have no fulfilment transitions" do
174
+ _(machine.fulfilment_transitions.empty?).must_equal true
175
+ end
176
+ end
177
+
178
+ context "payment final state" do
179
+ before do
180
+ machine.pay
181
+ machine.refund
182
+ end
183
+
184
+ it "must be in the payment final state" do
185
+ _(machine.payment_final_state?).must_equal true
186
+ _(machine.refunded?).must_equal true
187
+ end
188
+
189
+ it "must have no payment transitions" do
190
+ _(machine.payment_transitions.empty?).must_equal true
191
+ end
192
+ end
193
+
194
+ context "active?" do
195
+ it "must be active when all machines are in initial states" do
196
+ _(machine.active?).must_equal true
197
+ end
198
+
199
+ it "must be active when one machine has reached final state" do
200
+ machine.items_collected
201
+ machine.dispatch
202
+ _(machine.fulfilment_final_state?).must_equal true
203
+ _(machine.active?).must_equal true
204
+ end
205
+
206
+ it "must not be active when all machines have reached final states" do
207
+ machine.items_collected
208
+ machine.dispatch
209
+ machine.pay
210
+ machine.refund
211
+ _(machine.active?).must_equal false
212
+ end
213
+
214
+ it "must be able to check a specific machine" do
215
+ machine.items_collected
216
+ machine.dispatch
217
+ _(machine.active?(:fulfilment)).must_equal false
218
+ _(machine.active?(:payment)).must_equal true
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,223 @@
1
+ # test/ActiveRecord/WithMultipleStateMachinesAndStatefulBlock.rb
2
+
3
+ gem 'minitest'
4
+ gem 'minitest-spec-context'
5
+
6
+ require 'minitest/autorun'
7
+ require 'minitest-spec-context'
8
+
9
+ require_relative './test_helper'
10
+
11
+ lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
12
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
13
+
14
+ require 'Stateful'
15
+
16
+ class CreateTableMachines < ActiveRecord::Migration[6.0]
17
+ def change
18
+ create_table :active_record_machine12s do |t|
19
+ t.string :fulfilment_state
20
+ t.string :payment_state
21
+ end
22
+ end
23
+ end
24
+
25
+ class ActiveRecordMachine12 < ActiveRecord::Base
26
+ extend Stateful
27
+
28
+ stateful do
29
+ state_machine :fulfilment do
30
+ initial_state :pending do
31
+ on :items_collected => :ready
32
+ end
33
+
34
+ state :ready do
35
+ on :dispatch => :dispatched
36
+ end
37
+
38
+ final_state :dispatched
39
+ end
40
+
41
+ state_machine :payment do
42
+ initial_state :unpaid do
43
+ on :pay => :paid
44
+ end
45
+
46
+ state :paid do
47
+ on :refund => :refunded
48
+ end
49
+
50
+ final_state :refunded
51
+ end
52
+ end
53
+ end
54
+
55
+ CreateTableMachines.new.change
56
+
57
+ describe Stateful::ActiveRecord do
58
+ let(:machine){ActiveRecordMachine12.create}
59
+
60
+ context "initial states" do
61
+ it "must be in the fulfilment initial state" do
62
+ _(machine.fulfilment_initial_state?).must_equal true
63
+ end
64
+
65
+ it "must be in the payment initial state" do
66
+ _(machine.payment_initial_state?).must_equal true
67
+ end
68
+
69
+ it "must have the correct fulfilment state name" do
70
+ _(machine.fulfilment_state.name).must_equal :pending
71
+ end
72
+
73
+ it "must have the correct payment state name" do
74
+ _(machine.payment_state.name).must_equal :unpaid
75
+ end
76
+
77
+ it "must have fulfilment transitions" do
78
+ _(machine.fulfilment_transitions.size).must_equal 1
79
+ _(machine.fulfilment_transitions.first.event_name).must_equal :items_collected
80
+ end
81
+
82
+ it "must have payment transitions" do
83
+ _(machine.payment_transitions.size).must_equal 1
84
+ _(machine.payment_transitions.first.event_name).must_equal :pay
85
+ end
86
+
87
+ it "must not be in a fulfilment final state" do
88
+ _(machine.fulfilment_final_state?).must_equal false
89
+ end
90
+
91
+ it "must not be in a payment final state" do
92
+ _(machine.payment_final_state?).must_equal false
93
+ end
94
+
95
+ it "must have domain predicates" do
96
+ _(machine.pending?).must_equal true
97
+ _(machine.unpaid?).must_equal true
98
+ end
99
+ end
100
+
101
+ context "independent state transitions" do
102
+ before do
103
+ machine.pay
104
+ end
105
+
106
+ it "must still be in the fulfilment initial state" do
107
+ _(machine.fulfilment_initial_state?).must_equal true
108
+ _(machine.pending?).must_equal true
109
+ end
110
+
111
+ it "must have advanced the payment state" do
112
+ _(machine.payment_state.name).must_equal :paid
113
+ _(machine.paid?).must_equal true
114
+ end
115
+
116
+ it "must not be in the payment initial state" do
117
+ _(machine.payment_initial_state?).must_equal false
118
+ end
119
+
120
+ it "must have persisted the payment state" do
121
+ machine.reload
122
+ _(machine.payment_state.name).must_equal :paid
123
+ end
124
+
125
+ it "must have persisted the fulfilment state as initial" do
126
+ machine.reload
127
+ _(machine.fulfilment_state.name).must_equal :pending
128
+ end
129
+ end
130
+
131
+ context "both machines advanced" do
132
+ before do
133
+ machine.items_collected
134
+ machine.pay
135
+ end
136
+
137
+ it "must be in the ready fulfilment state" do
138
+ _(machine.fulfilment_state.name).must_equal :ready
139
+ _(machine.ready?).must_equal true
140
+ end
141
+
142
+ it "must be in the paid payment state" do
143
+ _(machine.payment_state.name).must_equal :paid
144
+ _(machine.paid?).must_equal true
145
+ end
146
+
147
+ it "must have correct fulfilment transitions" do
148
+ _(machine.fulfilment_transitions.size).must_equal 1
149
+ _(machine.fulfilment_transitions.first.event_name).must_equal :dispatch
150
+ end
151
+
152
+ it "must have correct payment transitions" do
153
+ _(machine.payment_transitions.size).must_equal 1
154
+ _(machine.payment_transitions.first.event_name).must_equal :refund
155
+ end
156
+
157
+ it "must have persisted both states" do
158
+ machine.reload
159
+ _(machine.fulfilment_state.name).must_equal :ready
160
+ _(machine.payment_state.name).must_equal :paid
161
+ end
162
+ end
163
+
164
+ context "fulfilment final state" do
165
+ before do
166
+ machine.items_collected
167
+ machine.dispatch
168
+ end
169
+
170
+ it "must be in the fulfilment final state" do
171
+ _(machine.fulfilment_final_state?).must_equal true
172
+ _(machine.dispatched?).must_equal true
173
+ end
174
+
175
+ it "must have no fulfilment transitions" do
176
+ _(machine.fulfilment_transitions.empty?).must_equal true
177
+ end
178
+ end
179
+
180
+ context "payment final state" do
181
+ before do
182
+ machine.pay
183
+ machine.refund
184
+ end
185
+
186
+ it "must be in the payment final state" do
187
+ _(machine.payment_final_state?).must_equal true
188
+ _(machine.refunded?).must_equal true
189
+ end
190
+
191
+ it "must have no payment transitions" do
192
+ _(machine.payment_transitions.empty?).must_equal true
193
+ end
194
+ end
195
+
196
+ context "active?" do
197
+ it "must be active when all machines are in initial states" do
198
+ _(machine.active?).must_equal true
199
+ end
200
+
201
+ it "must be active when one machine has reached final state" do
202
+ machine.items_collected
203
+ machine.dispatch
204
+ _(machine.fulfilment_final_state?).must_equal true
205
+ _(machine.active?).must_equal true
206
+ end
207
+
208
+ it "must not be active when all machines have reached final states" do
209
+ machine.items_collected
210
+ machine.dispatch
211
+ machine.pay
212
+ machine.refund
213
+ _(machine.active?).must_equal false
214
+ end
215
+
216
+ it "must be able to check a specific machine" do
217
+ machine.items_collected
218
+ machine.dispatch
219
+ _(machine.active?(:fulfilment)).must_equal false
220
+ _(machine.active?(:payment)).must_equal true
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,159 @@
1
+ # test/ActiveRecord/WithPersistenceNonSpecificExtend.rb
2
+
3
+ gem 'minitest'
4
+ gem 'minitest-spec-context'
5
+
6
+ require 'minitest/autorun'
7
+ require 'minitest-spec-context'
8
+
9
+ require_relative './test_helper'
10
+
11
+ lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
12
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
13
+
14
+ require 'Stateful'
15
+
16
+ class CreateTableMachines < ActiveRecord::Migration[6.0]
17
+ def change
18
+ create_table :active_record_machine0s do |t|
19
+ t.string :current_state
20
+ end
21
+ end
22
+ end
23
+
24
+ class ActiveRecordMachine0 < ActiveRecord::Base
25
+ extend Stateful
26
+
27
+ initial_state :initial_state
28
+
29
+ state :initial_state do
30
+ on :an_event => :next_state
31
+ on :another_event => :final_state
32
+ end
33
+
34
+ state :next_state do
35
+ on :yet_another_event => :final_state
36
+ end
37
+
38
+ final_state :final_state
39
+ end
40
+
41
+ CreateTableMachines.new.change
42
+
43
+ describe Stateful::ActiveRecord do
44
+ let(:machine){ActiveRecordMachine0.create}
45
+
46
+ it "must have an initial state" do
47
+ _(machine.initial_state).wont_be_nil
48
+ end
49
+
50
+ it "must have a final state (if one has been specified)" do
51
+ if ActiveRecordMachine0.final_state?
52
+ _(machine.final_state).wont_be_nil
53
+ end
54
+ end
55
+
56
+ context "initial_state" do
57
+ it "must have an initial state before any events occur" do
58
+ _(machine.initial_state?).must_equal true
59
+ end
60
+
61
+ it "must have an initial state consistent with what is given" do
62
+ _(machine.initial_state).must_equal ActiveRecordMachine0.stateful_state_machine.initial_state
63
+ end
64
+
65
+ it "must have an initial state with name as per the name given" do
66
+ _(machine.initial_state.name).must_equal :initial_state
67
+ end
68
+
69
+ it "must not be in the final state (assuming that initial and final are different states)" do
70
+ _(machine.final_state?).must_equal false
71
+ end
72
+
73
+ it "must know what it's next state is given an event name" do
74
+ _(machine.next_state(:an_event)).must_equal ActiveRecordMachine0.stateful_state_machine.find(:next_state)
75
+ _(machine.next_state(:another_event)).must_equal ActiveRecordMachine0.stateful_state_machine.find(:final_state)
76
+ end
77
+
78
+ it "must have an intial state which has as set of transitions to other states" do
79
+ _(machine.transitions.class).must_equal Array
80
+ end
81
+
82
+ it "must have two transitions to other states" do
83
+ _(machine.transitions.size).must_equal 2
84
+ end
85
+
86
+ it "must know what transitions are available" do
87
+ _(machine.transitions.collect{|t| [t.event_name, t.next_state_name]}).must_equal [[:an_event, :next_state], [:another_event, :final_state]]
88
+ end
89
+
90
+ it "must be active" do
91
+ _(machine.active?).must_equal true
92
+ end
93
+ end
94
+
95
+ context "next_state" do
96
+ before do
97
+ machine.an_event
98
+ end
99
+
100
+ it "must not be in the initial state" do
101
+ _(machine.initial_state?).must_equal false
102
+ end
103
+
104
+ it "must not be in the final state" do
105
+ _(machine.final_state?).must_equal false
106
+ end
107
+
108
+ it "must be in the next_state state" do
109
+ _(machine.current_state.name).must_equal :next_state
110
+ _(machine.next_state?).must_equal true
111
+ end
112
+
113
+ it "must have a set of transitions to other states" do
114
+ _(machine.transitions.class).must_equal Array
115
+ end
116
+
117
+ it "must have one transition to other states" do
118
+ _(machine.transitions.size).must_equal 1
119
+ end
120
+
121
+ it "must know what transitions are available" do
122
+ _(machine.transitions.collect{|t| [t.event_name, t.next_state_name]}).must_equal [[:yet_another_event, :final_state]]
123
+ end
124
+
125
+ it "must be active" do
126
+ _(machine.active?).must_equal true
127
+ end
128
+ end
129
+
130
+ context "final_state" do
131
+ before do
132
+ machine.another_event
133
+ end
134
+
135
+ it "must have a final state" do
136
+ _(machine.final_state?).must_equal true
137
+ end
138
+
139
+ it "must have a final state consistent with what is given" do
140
+ _(machine.final_state).must_equal ActiveRecordMachine0.stateful_state_machine.final_state
141
+ end
142
+
143
+ it "must have a state with name as per the name given" do
144
+ _(machine.current_state.name).must_equal :final_state
145
+ end
146
+
147
+ it "must not be in the initial state (assuming that initial and final are different states)" do
148
+ _(machine.initial_state?).must_equal false
149
+ end
150
+
151
+ it "must have no transitions" do
152
+ _(machine.transitions.empty?).must_equal true
153
+ end
154
+
155
+ it "must not be active" do
156
+ _(machine.active?).must_equal false
157
+ end
158
+ end
159
+ end