stateful.rb 2.1.0 → 2.3.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +161 -0
  3. data/Gemfile +3 -0
  4. data/README.md +309 -0
  5. data/lib/Stateful/ActiveRecord/ClassMethods.rb +7 -65
  6. data/lib/Stateful/ClassMethods.rb +60 -0
  7. data/lib/Stateful/Poro/ClassMethods.rb +12 -70
  8. data/lib/Stateful/Sequel/ClassMethods.rb +59 -0
  9. data/lib/Stateful/Sequel.rb +30 -0
  10. data/lib/Stateful/VERSION.rb +1 -1
  11. data/lib/Stateful.rb +3 -0
  12. data/stateful.rb.gemspec +42 -0
  13. data/test/ActiveRecord/WithColumnName.rb +159 -0
  14. data/test/ActiveRecord/WithExplicitDeterministicEventOrdering.rb +159 -0
  15. data/test/ActiveRecord/WithExplicitGlobalNonDeterministicEventOrdering.rb +165 -0
  16. data/test/ActiveRecord/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb +163 -0
  17. data/test/ActiveRecord/WithExplicitNonDeterministicEventOrdering.rb +161 -0
  18. data/test/ActiveRecord/WithInitialStateBlock.rb +157 -0
  19. data/test/ActiveRecord/WithMultipleFinalStates.rb +188 -0
  20. data/test/ActiveRecord/WithMultipleStateMachines.rb +221 -0
  21. data/test/ActiveRecord/WithMultipleStateMachinesAndStatefulBlock.rb +223 -0
  22. data/test/ActiveRecord/WithPersistenceNonSpecificExtend.rb +159 -0
  23. data/test/ActiveRecord/WithPersistenceNonSpecificExtendAndStatefulBlock.rb +161 -0
  24. data/test/ActiveRecord/WithPersistenceSpecificExtend.rb +159 -0
  25. data/test/ActiveRecord/WithPersistenceSpecificExtendAndStatefulBlock.rb +161 -0
  26. data/test/ActiveRecord/test_helper.rb +9 -0
  27. data/test/ActiveRecord.rb +5 -0
  28. data/test/Poro/WithExplicitDeterministicEventOrdering.rb +147 -0
  29. data/test/Poro/WithExplicitGlobalNonDeterministicEventOrdering.rb +153 -0
  30. data/test/Poro/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb +151 -0
  31. data/test/Poro/WithExplicitNonDeterministicEventOrdering.rb +149 -0
  32. data/test/Poro/WithInitialStateBlock.rb +145 -0
  33. data/test/Poro/WithMultipleFinalStates.rb +176 -0
  34. data/test/Poro/WithMultipleStateMachines.rb +192 -0
  35. data/test/Poro/WithMultipleStateMachinesAndStatefulBlock.rb +194 -0
  36. data/test/Poro/WithPersistenceNonSpecificExtend.rb +147 -0
  37. data/test/Poro/WithPersistenceNonSpecificExtendAndStatefulBlock.rb +149 -0
  38. data/test/Poro/WithPersistenceSpecificExtend.rb +147 -0
  39. data/test/Poro/WithPersistenceSpecificExtendAndStatefulBlock.rb +149 -0
  40. data/test/Poro/WithVariableName.rb +147 -0
  41. data/test/Poro.rb +5 -0
  42. data/test/Sequel/WithColumnName.rb +154 -0
  43. data/test/Sequel/WithExplicitDeterministicEventOrdering.rb +154 -0
  44. data/test/Sequel/WithExplicitGlobalNonDeterministicEventOrdering.rb +160 -0
  45. data/test/Sequel/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb +158 -0
  46. data/test/Sequel/WithExplicitNonDeterministicEventOrdering.rb +156 -0
  47. data/test/Sequel/WithInitialStateBlock.rb +152 -0
  48. data/test/Sequel/WithMultipleFinalStates.rb +183 -0
  49. data/test/Sequel/WithMultipleStateMachines.rb +216 -0
  50. data/test/Sequel/WithMultipleStateMachinesAndStatefulBlock.rb +218 -0
  51. data/test/Sequel/WithPersistenceNonSpecificExtend.rb +154 -0
  52. data/test/Sequel/WithPersistenceNonSpecificExtendAndStatefulBlock.rb +156 -0
  53. data/test/Sequel/WithPersistenceSpecificExtend.rb +154 -0
  54. data/test/Sequel/WithPersistenceSpecificExtendAndStatefulBlock.rb +156 -0
  55. data/test/Sequel/test_helper.rb +5 -0
  56. data/test/Sequel.rb +5 -0
  57. data/test/Stateful.rb +15 -0
  58. metadata +69 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7967ca169b2665febfdca466537e2cbd087eda2d2033289ddb485e5b6311fb8
4
- data.tar.gz: a9d2c650234ba4657338b61129823f6fbaccad9159827d65ba67e4ccf10a7c98
3
+ metadata.gz: 64d45e68824b71f7c09cdd74a2c5ee0b24dc8b6a5d56c8382d69db3755f2135b
4
+ data.tar.gz: cb4c4e148e18cb5fe4792db4f3c7607917bae0d15fde9823200c7dd43e34731a
5
5
  SHA512:
6
- metadata.gz: c19933b56be3391784e694fecedfb486c9b9f3be79a53d07ac5a0adb7c481aa8e68a5a0204ad48ea5869bf5385de38d0022767f14dbb5d4d247cdc00233e1379
7
- data.tar.gz: d8cebec2d02b438a6756cdf297a11ccb1abe5927bf487b283e978df90a0a79eaca6598cccca4f15a55bb654af9b20a8866bb130be7ed887f174cc81ba09ec257
6
+ metadata.gz: 2ee5b530989c26519491d9575bfdc5cad20f9eda1e1cae0c95186d52891c78e78a765d1eb1b42c194177959979abefd017f2f47edaeb586746556322b89f3f38
7
+ data.tar.gz: 6e55e936a191bc872594b3a6ad370b1912b03950680eac27ecd9a960592b16129c69c073f0d903d1d5f1c50cfadeb5b08dad0b3a521fe045df4d649854d81b9a
data/CHANGELOG.md ADDED
@@ -0,0 +1,161 @@
1
+ # stateful/CHANGELOG.md
2
+
3
+ ## 2.3.0 (20260418): Extract shared class methods.
4
+ -----------------------------------------------------------------------------------------------------------------------
5
+
6
+ ### ~
7
+ 1. lib/Stateful/ClassMethods.rb: + shared adapter methods extracted from Poro::ClassMethods, ActiveRecord::ClassMethods, Sequel::ClassMethods. All adapters now delegate to stateful\_attribute\_name\_for\_machine.
8
+ - define\_named\_machine\_methods
9
+ - define\_event\_method
10
+ - define\_status\_predicate\_method
11
+ - define\_next\_state\_method
12
+ - define\_transitions\_method
13
+ - define\_initial\_stateQ\_method
14
+ - define\_final\_stateQ\_method
15
+ 2. lib/Stateful/ActiveRecord/ClassMethods.rb:
16
+ - Extracted shared adapter methods to Stateful::ClassMethods.
17
+ - /column_name_for_machine/stateful\_attribute_name\_for\_machine/
18
+ 3. lib/Stateful/Poro/ClassMethods.rb: Extracted shared adapter methods to Stateful::ClassMethods.
19
+ - Extracted shared adapter methods to Stateful::ClassMethods.
20
+ - /variable_name_for_machine/stateful\_attribute_name\_for\_machine/
21
+ 4. lib/Stateful/Sequel/ClassMethods.rb: Extracted shared adapter methods to Stateful::ClassMethods.
22
+ - Extracted shared adapter methods to Stateful::ClassMethods.
23
+ - /column_name_for_machine/stateful\_attribute_name\_for\_machine/
24
+ 5. lib/Stateful/VERSION.rb: /2.2.0/2.3.0/
25
+
26
+
27
+ ## 2.2.0 (20260418): Add Sequel support.
28
+ -----------------------------------------------------------------------------------------------------------------------
29
+
30
+ ### +
31
+ 1. lib/Stateful/Sequel.rb: Sequel adapter module, mirroring Stateful::ActiveRecord.
32
+ 2. lib/Stateful/Sequel/ClassMethods.rb: Sequel-specific class methods using self[:column] / self[:column]= for persistence.
33
+ 3. test/Sequel/test\_helper.rb: Sequel test helper using in-memory SQLite.
34
+ 4. test/Sequel.rb: Sequel test runner.
35
+ 5. test/Sequel/WithPersistenceNonSpecificExtend.rb
36
+ 6. test/Sequel/WithPersistenceNonSpecificExtendAndStatefulBlock.rb
37
+ 7. test/Sequel/WithPersistenceSpecificExtend.rb
38
+ 8. test/Sequel/WithPersistenceSpecificExtendAndStatefulBlock.rb
39
+ 9. test/Sequel/WithInitialStateBlock.rb
40
+ 10. test/Sequel/WithColumnName.rb
41
+ 11. test/Sequel/WithMultipleFinalStates.rb
42
+ 12. test/Sequel/WithMultipleStateMachines.rb
43
+ 13. test/Sequel/WithMultipleStateMachinesAndStatefulBlock.rb
44
+ 14. test/Sequel/WithExplicitDeterministicEventOrdering.rb
45
+ 15. test/Sequel/WithExplicitNonDeterministicEventOrdering.rb
46
+ 16. test/Sequel/WithExplicitGlobalNonDeterministicEventOrdering.rb
47
+ 17. test/Sequel/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb
48
+
49
+ ### ~
50
+ 1. lib/Stateful.rb: Auto-detect Sequel::Model in Stateful.extended, between ActiveRecord and Poro.
51
+ 2. test/Stateful.rb: + Sequel specs; - $LOAD\_PATH.unshift(test\_dir) to avoid case-insensitive collision between test/Sequel.rb and require 'sequel'.
52
+ 3. stateful.rb.gemspec: + sequel dev dependency; update description to include Sequel.
53
+ 4. lib/Stateful/VERSION.rb: /2.1.0/2.2.0/
54
+
55
+
56
+ ## 2.1.0 (20260412): Add mRuby support.
57
+ -----------------------------------------------------------------------------------------------------------------------
58
+
59
+ ### +
60
+ 1. mrbgem.rake: mrbgem specification with spec.rbfiles pointing at lib/ directly.
61
+ 2. mruby/stub\_require\_relative.rb: Stub to silence require_relative calls at mRuby build time.
62
+ 3. mruby/smoke_test.rb: Smoke test for mRuby compatibility.
63
+ 4. TODO (See notes.txt.)
64
+
65
+ ### -
66
+ 1. CHANGELOG.txt as it was left empty after creating CHANGELOG.md.
67
+ 2. notes.txt (Now TODO.)
68
+
69
+ ### ~
70
+ 1. lib/Stateful.rb: + Object.const_defined?(:ActiveRecord) for mRuby compatibility. In mRuby defined?() raises NameError instead of returning nil as with CRuby.
71
+ 2. lib/Stateful/VERSION.rb: /2.0.0/2.1.0/
72
+
73
+
74
+ ## 2.0.0 (20260216): Allow multiple state machines per class.
75
+ -----------------------------------------------------------------------------------------------------------------------
76
+
77
+ ### +
78
+ 1. Stateful::ClassMethods#state_machines: For indexing named `StateMachine` instances.
79
+ 2. Stateful::ClassMethods#state_machine
80
+ 3. Poro::ClassMethods#define_named_machine_methods
81
+ 4. ActiveRecord::ClassMethods#define_named_machine_methods
82
+ 5. StateMachine accepts parameter for `global_non_deterministic_event_ordering
83
+ 6. ~/test/ActiveRecord/test_helper.rb
84
+
85
+ ### -
86
+ 1. StateMachine#stateful
87
+
88
+ ### ~
89
+ 1. Stateful::ClassMethods#stateful: Use class_eval instead of delegating to StateMachine#stateful, which is now removed.
90
+ 2. /Stateful::States/Stateful::StateMachine/
91
+ 3. /stateful_states/stateful_state_machine/
92
+ 4. Test infrastructure migrated from PostgreSQL to SQLite (in-memory)
93
+ 5. stateful.rb.gemspec: /pg/sqlite3/
94
+ 6. Stateful::StateMachine#final_states: final_state?, final_states? weren't being defined.
95
+ 7. lib/Stateful/VERSION.rb: /1.0.2/2.1.0/
96
+
97
+
98
+ ## 1.0.2 (20260216): Fix non-deterministic event ordering.
99
+ -----------------------------------------------------------------------------------------------------------------------
100
+
101
+ ### ~
102
+ 1. Stateful::State#non_deterministic_event_ordering?: Fixed logic bug where (@options[:deterministic] && !@options[:deterministic]) always evaluated to false.
103
+ 2. Stateful::States#global_non_deterministic_event_ordering?: Same fix as above.
104
+ 3. Stateful::State: /shuffle/shuffle!/non-deterministic ordering actually mutates the transitions array.
105
+ 4. Stateful::State: Removed require_relative for lib/Array/shuffle.rb.
106
+ 5. test/ActiveRecord/WithPersistenceSpecificExtend.rb: Fixed delete_all guard to check 'active_record_machine2s' instead of 'active_record_machine1s'.
107
+ 6. test/ActiveRecord/WithMultipleFinalStates.rb: Fixed comment header (was Poro/WithMultipleFinalStates.rb).
108
+ 7. test/Poro/WithVariableName.rb: Fixed comment header (was ActiveRecord/WithVariableName.rb).
109
+ 8. test/Poro/WithExplicitNonDeterministicEventOrdering.rb: Changed transition order assertions to use must_include instead of must_equal.
110
+ 9. test/ActiveRecord/WithExplicitNonDeterministicEventOrdering.rb: Same as above.
111
+ 10. test/ActiveRecord/WithExplicitGlobalNonDeterministicEventOrdering.rb: Same as above.
112
+ 11. test/ActiveRecord/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb: Same as above.
113
+ 12. lib/Stateful/VERSION.rb: /1.0.1/1.0.2/
114
+
115
+ ### -
116
+ 1. Array#shuffle: Use stdlib.
117
+
118
+
119
+ ## 1.0.1 (20260216): Fix date and version.
120
+ -----------------------------------------------------------------------------------------------------------------------
121
+
122
+ ### ~
123
+ 1. stateful.rb.gemspec: Insert the correct date.
124
+ 2. lib/Stateful/VERSION.rb: /1.0.0/1.0.1/
125
+
126
+
127
+ ## 1.0.0 (20260216): Version number bump to 1.0.
128
+ -----------------------------------------------------------------------------------------------------------------------
129
+
130
+ ### ~
131
+ 1. lib/Stateful/VERSION.rb: /0.14.12/1.0.0/
132
+
133
+
134
+ ## 0.14.12 (20260216): Change file mode on lib/Array/shuffle.rb.
135
+ -----------------------------------------------------------------------------------------------------------------------
136
+
137
+ ### ~
138
+ 1. lib/Array/shuffle.rb: File mode change to read-only from being executable.
139
+ 2. lib/Stateful/VERSION.rb: /0.14.11/0.14.12/
140
+
141
+
142
+ ## 0.14.11 (20200419): Fix copypasta error in README.md.
143
+ -----------------------------------------------------------------------------------------------------------------------
144
+
145
+ ### ~
146
+ 1. README.md: Fixed copypasta error in Installation section (was 'git.rb', now 'stateful.rb')
147
+ 2. lib/Stateful/VERSION.rb: /0.14.10/0.14.11/
148
+
149
+
150
+ ## 0.14.10 (20150207, 20200331): Allow Poro::ClassMethods to handle its own initialisation when extended.
151
+ -----------------------------------------------------------------------------------------------------------------------
152
+
153
+ ### +
154
+ 1. I finally implemented a change which I'd had sitting in a file never committed named shouldgetthisworking.rb since 2015-02-07 which entails moving the instance method generation methods from each of the application-specific files (lib/Poro.rb and lib/ActiveRecord.rb) to the application-specific class methods files (lib/Poro/ClassMethods.rb and lib/ActiveRecord/ClassMethods.rb).
155
+ 2. README.md
156
+ 3. CHANGES.txt
157
+
158
+ ### ~
159
+ 1. .gitignore: + *.gem
160
+ 2. lib/Stateful/VERSION.rb: /0.14.9/0.14.10/
161
+ 3. stateful.rb.gemspec to reflect the updated version
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://www.rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,309 @@
1
+ # Stateful
2
+
3
+ ## Description
4
+
5
+ A Ruby state machine which allows you to easily add state to Poro and ActiveRecord objects. Works with both CRuby and mRuby.
6
+
7
+ ## Installation
8
+
9
+ ### CRuby
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'stateful.rb'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```shell
20
+ $ bundle install
21
+ ```
22
+
23
+ Or install it yourself as:
24
+
25
+ ```shell
26
+ $ gem install stateful.rb
27
+ ```
28
+
29
+ ### mRuby
30
+
31
+ Add this to your `build_config.rb`:
32
+
33
+ ```ruby
34
+ conf.gem github: 'thoran/Stateful'
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ### 1. Poro with persistence-specific extend and a stateful block declaration
40
+
41
+ ```ruby
42
+ class PoroMachine
43
+ extend Stateful::Poro
44
+
45
+ stateful do
46
+ initial_state :initial_state
47
+
48
+ state :initial_state do
49
+ on :an_event => :next_state
50
+ on :another_event => :final_state
51
+ end
52
+
53
+ state :next_state do
54
+ on :yet_another_event => :final_state
55
+ end
56
+
57
+ final_state :final_state
58
+ end
59
+ end
60
+
61
+ poro_machine = PoroMachine.new
62
+ poro_machine.initial_state.name
63
+ # => :initial_state
64
+ poro_machine.final_state.name
65
+ # => :final_state
66
+ ```
67
+
68
+ ### 2. Poro with persistence non-specific extend, without a stateful block declaration, an initial_state block, and multiple final states
69
+
70
+ ```ruby
71
+ class PoroMachine
72
+ extend Stateful
73
+
74
+ initial_state :initial_state do
75
+ on :an_event => :next_state
76
+ on :another_event => :final_state0
77
+ end
78
+
79
+ state :next_state do
80
+ on :yet_another_event => :final_state1
81
+ end
82
+
83
+ final_state :final_state0, :final_state1
84
+ end
85
+
86
+ poro_machine = PoroMachine.new
87
+ poro_machine.current_state.name
88
+ # => :initial_state
89
+ poro_machine.an_event
90
+ poro_machine.current_state.name
91
+ # => :next_state
92
+ ```
93
+
94
+ ### 3. ActiveRecord with persistence-specific extend and a stateful block declaration
95
+
96
+ ```ruby
97
+ class ActiveRecordMachine < ActiveRecord::Base
98
+ extend Stateful::ActiveRecord
99
+
100
+ stateful do
101
+ initial_state :initial_state
102
+
103
+ state :initial_state do
104
+ on :an_event => :next_state
105
+ on :another_event => :final_state
106
+ end
107
+
108
+ state :next_state do
109
+ on :yet_another_event => :final_state
110
+ end
111
+
112
+ final_state :final_state
113
+ end
114
+ end
115
+
116
+ active_record_machine = ActiveRecordMachine.new
117
+ active_record_machine.initial_state?
118
+ # => true
119
+ active_record_machine.an_event
120
+ active_record_machine.next_state?
121
+ # => true
122
+ ```
123
+
124
+ ### 4. ActiveRecord with persistence non-specific extend, without a stateful block declaration, an initial_state block, and multiple final states
125
+
126
+ ```ruby
127
+ class ActiveRecordMachine < ActiveRecord::Base
128
+ extend Stateful
129
+
130
+ initial_state :initial_state do
131
+ on :an_event => :next_state
132
+ on :another_event => :final_state0
133
+ end
134
+
135
+ state :next_state do
136
+ on :yet_another_event => :final_state0
137
+ on :and_yet_another_event => :final_state1
138
+ end
139
+
140
+ final_state :final_state0, :final_state1
141
+ end
142
+
143
+ active_record_machine = ActiveRecordMachine.new
144
+ active_record_machine.an_event
145
+ active_record_machine.final_state?
146
+ # => false
147
+ active_record_machine.yet_another_event
148
+ active_record_machine.final_state?
149
+ # => true
150
+ ```
151
+
152
+ ### 5. Custom variable/column name and non-deterministic event ordering
153
+
154
+ It's also possible to define a state machine (either Poro or ActiveRecord) which has a custom variable (Poro) or column (ActiveRecord) name, which fires off the events in a random or non-deterministic order, and which has no final state.
155
+
156
+ ```ruby
157
+ class ActiveRecordMachine < ActiveRecord::Base
158
+
159
+ @stateful_column_name = 'status' # This needs to be set before the class is extended. The default is 'current_state'.
160
+
161
+ extend Stateful
162
+
163
+ initial_state :initial_state, non_deterministic: true do
164
+ on :an_event => :another_state
165
+ on :another_event => :yet_another_state
166
+ end
167
+
168
+ state :another_state do
169
+ on :another_event => :yet_another_state
170
+ end
171
+
172
+ state :yet_another_state do
173
+ on :yet_another_event => :another_state
174
+ end
175
+ end
176
+
177
+ active_record_machine = ActiveRecordMachine.new
178
+ # Supposing that both :an_event or :another_event messages are able to fire, then if the order of the evaluation of the transitions is non-deterministic, then the state change is also. Non-deterministic event ordering really only makes sense in the context of the sibling library thoran/Eventful, which will evaluate the transitions automatically and will do so in the order as presented by Stateful.
179
+ active_record_machine.another_event # With non-deterministic event firing it could check for :another_event before checking :an_event.
180
+ active_record_machine.current_state.name
181
+ # => :yet_another_state
182
+ active_record_machine.an_event
183
+ active_record_machine.current_state.name
184
+ # => :yet_another_state
185
+ active_record_machine.final_state?
186
+ # => false
187
+ active_record_machine.yet_another_event
188
+ active_record_machine.final_state?
189
+ # => false
190
+ ```
191
+
192
+ ### 6. Multiple state machines per class
193
+
194
+ A single class can have multiple independent (orthogonal) state machines. Each machine gets its own namespaced methods, while domain predicates and event methods remain unnamespaced.
195
+
196
+ ```ruby
197
+ class Order
198
+ extend Stateful
199
+
200
+ state_machine :fulfilment do
201
+ initial_state :pending do
202
+ on :items_collected => :ready
203
+ end
204
+
205
+ state :ready do
206
+ on :dispatch => :dispatched
207
+ end
208
+
209
+ final_state :dispatched
210
+ end
211
+
212
+ state_machine :payment do
213
+ initial_state :unpaid do
214
+ on :pay => :paid
215
+ end
216
+
217
+ state :paid do
218
+ on :refund => :refunded
219
+ end
220
+
221
+ final_state :refunded
222
+ end
223
+ end
224
+
225
+ order = Order.new
226
+
227
+ order.fulfilment_state.name # state accessors are namespaced.
228
+ # => :pending
229
+ order.payment_state.name
230
+ # => :unpaid
231
+
232
+ order.fulfilment_initial_state? # General/overlapping predicates are namespaced.
233
+ # => true
234
+ order.payment_final_state?
235
+ # => false
236
+
237
+ order.fulfilment_transitions.first.event_name # Transitions are namespaced.
238
+ # => :items_collected
239
+
240
+ order.pending? # Domain predicates are not namespaced.
241
+ # => true
242
+ order.unpaid?
243
+ # => true
244
+
245
+ order.pay # Event methods are not namespaced.
246
+ order.paid?
247
+ # => true
248
+
249
+ # State machines are orthogonal
250
+ order.fulfilment_initial_state?
251
+ # => true
252
+ ```
253
+
254
+ Multiple state machines can also be declared inside a stateful block:
255
+
256
+ ```ruby
257
+ class Order
258
+ extend Stateful
259
+
260
+ stateful do
261
+ state_machine :fulfilment do
262
+ initial_state :pending do
263
+ on :items_collected => :ready
264
+ end
265
+
266
+ state :ready do
267
+ on :dispatch => :dispatched
268
+ end
269
+
270
+ final_state :dispatched
271
+ end
272
+
273
+ state_machine :payment do
274
+ initial_state :unpaid do
275
+ on :pay => :paid
276
+ end
277
+
278
+ state :paid do
279
+ on :refund => :refunded
280
+ end
281
+
282
+ final_state :refunded
283
+ end
284
+ end
285
+ end
286
+ ```
287
+
288
+ For ActiveRecord, each machine uses a column named `<machine_name>_state`:
289
+
290
+ ```ruby
291
+ class CreateOrders < ActiveRecord::Migration[6.0]
292
+ def change
293
+ create_table :orders do |t|
294
+ t.string :fulfilment_state
295
+ t.string :payment_state
296
+ end
297
+ end
298
+ end
299
+ ```
300
+
301
+ For Poro, each machine uses an instance variable named `@<machine_name>_state`.
302
+
303
+ ## Contributing
304
+
305
+ 1. Fork it: https://github.com/thoran/Stateful/fork
306
+ 2. Create your feature branch: `git checkout -b my-new-feature`
307
+ 3. Commit your changes: `git commit -am 'Add some feature'`
308
+ 4. Push to the branch: `git push origin my-new-feature`
309
+ 5. Create a new pull request
@@ -6,8 +6,8 @@ module Stateful
6
6
  module ClassMethods
7
7
  class << self
8
8
  def extended(klass)
9
- klass.define_stateful_column_name_setter_method
10
- klass.define_stateful_column_name_getter_method
9
+ klass.define_stateful_attribute_setter_method
10
+ klass.define_stateful_attribute_getter_method
11
11
  klass.define_next_state_method
12
12
  klass.define_transitions_method
13
13
  klass.define_initial_stateQ_method
@@ -15,15 +15,6 @@ module Stateful
15
15
  end
16
16
  end # class << self
17
17
 
18
- def define_named_machine_methods(machine_name)
19
- define_stateful_column_name_setter_method(machine_name: machine_name)
20
- define_stateful_column_name_getter_method(machine_name: machine_name)
21
- define_next_state_method(machine_name: machine_name)
22
- define_transitions_method(machine_name: machine_name)
23
- define_initial_stateQ_method(machine_name: machine_name)
24
- define_final_stateQ_method(machine_name: machine_name)
25
- end
26
-
27
18
  def stateful_column_name=(stateful_column_name)
28
19
  @stateful_column_name = stateful_column_name
29
20
  end
@@ -32,7 +23,7 @@ module Stateful
32
23
  @stateful_column_name
33
24
  end
34
25
 
35
- def column_name_for_machine(machine_name = nil)
26
+ def stateful_attribute_name_for_machine(machine_name = nil)
36
27
  if machine_name
37
28
  "#{machine_name}_state"
38
29
  else
@@ -40,24 +31,8 @@ module Stateful
40
31
  end
41
32
  end
42
33
 
43
- def define_event_method(transition, machine_name: nil)
44
- stateful_column_name = column_name_for_machine(machine_name)
45
- define_method(transition.event_name) do
46
- next_state_name = self.send(stateful_column_name).next_state_name(transition.event_name)
47
- next_state = self.class.stateful_state_machine(machine_name).find(next_state_name)
48
- self.send("#{stateful_column_name}=", next_state)
49
- end
50
- end
51
-
52
- def define_status_predicate_method(state_name, machine_name: nil)
53
- stateful_column_name = column_name_for_machine(machine_name)
54
- define_method("#{state_name}?") do
55
- self.send(stateful_column_name).name == state_name
56
- end
57
- end
58
-
59
- def define_stateful_column_name_setter_method(machine_name: nil)
60
- stateful_column_name = column_name_for_machine(machine_name)
34
+ def define_stateful_attribute_setter_method(machine_name: nil)
35
+ stateful_column_name = stateful_attribute_name_for_machine(machine_name)
61
36
  define_method("#{stateful_column_name}=") do |state|
62
37
  instance_variable_set("@#{stateful_column_name}", self.class.stateful_state_machine(machine_name).find(state))
63
38
  write_attribute(stateful_column_name, instance_variable_get("@#{stateful_column_name}").name)
@@ -66,8 +41,8 @@ module Stateful
66
41
  end
67
42
  end
68
43
 
69
- def define_stateful_column_name_getter_method(machine_name: nil)
70
- stateful_column_name = column_name_for_machine(machine_name)
44
+ def define_stateful_attribute_getter_method(machine_name: nil)
45
+ stateful_column_name = stateful_attribute_name_for_machine(machine_name)
71
46
  define_method(stateful_column_name) do
72
47
  instance_variable_set("@#{stateful_column_name}", self.class.stateful_state_machine(machine_name).find(read_attribute(stateful_column_name)))
73
48
  if state = instance_variable_get("@#{stateful_column_name}")
@@ -79,39 +54,6 @@ module Stateful
79
54
  end
80
55
  end
81
56
  end
82
-
83
- def define_next_state_method(machine_name: nil)
84
- stateful_column_name = column_name_for_machine(machine_name)
85
- method_name = machine_name ? "#{machine_name}_next_state" : :next_state
86
- define_method(method_name) do |event_name|
87
- next_state_name = self.send(stateful_column_name).next_state_name(event_name)
88
- self.class.stateful_state_machine(machine_name).find(next_state_name)
89
- end
90
- end
91
-
92
- def define_transitions_method(machine_name: nil)
93
- stateful_column_name = column_name_for_machine(machine_name)
94
- method_name = machine_name ? "#{machine_name}_transitions" : :transitions
95
- define_method(method_name) do
96
- self.send(stateful_column_name).transitions
97
- end
98
- end
99
-
100
- def define_initial_stateQ_method(machine_name: nil)
101
- stateful_column_name = column_name_for_machine(machine_name)
102
- method_name = machine_name ? "#{machine_name}_initial_state?" : :initial_state?
103
- define_method(method_name) do
104
- self.send(stateful_column_name) == self.class.stateful_state_machine(machine_name).initial_state
105
- end
106
- end
107
-
108
- def define_final_stateQ_method(machine_name: nil)
109
- stateful_column_name = column_name_for_machine(machine_name)
110
- method_name = machine_name ? "#{machine_name}_final_state?" : :final_state?
111
- define_method(method_name) do
112
- self.class.stateful_state_machine(machine_name).final_states.include?(self.send(stateful_column_name))
113
- end
114
- end
115
57
  end
116
58
  end
117
59
  end
@@ -51,6 +51,66 @@ module Stateful
51
51
 
52
52
  # end DSL
53
53
 
54
+ # adapter methods
55
+
56
+ def define_named_machine_methods(machine_name)
57
+ define_stateful_attribute_setter_method(machine_name: machine_name)
58
+ define_stateful_attribute_getter_method(machine_name: machine_name)
59
+ define_next_state_method(machine_name: machine_name)
60
+ define_transitions_method(machine_name: machine_name)
61
+ define_initial_stateQ_method(machine_name: machine_name)
62
+ define_final_stateQ_method(machine_name: machine_name)
63
+ end
64
+
65
+ def define_event_method(transition, machine_name: nil)
66
+ attribute_name = stateful_attribute_name_for_machine(machine_name)
67
+ define_method(transition.event_name) do
68
+ next_state_name = self.send(attribute_name).next_state_name(transition.event_name)
69
+ next_state = self.class.stateful_state_machine(machine_name).find(next_state_name)
70
+ self.send("#{attribute_name}=", next_state)
71
+ end
72
+ end
73
+
74
+ def define_status_predicate_method(state_name, machine_name: nil)
75
+ attribute_name = stateful_attribute_name_for_machine(machine_name)
76
+ define_method("#{state_name}?") do
77
+ self.send(attribute_name).name == state_name
78
+ end
79
+ end
80
+
81
+ def define_next_state_method(machine_name: nil)
82
+ attribute_name = stateful_attribute_name_for_machine(machine_name)
83
+ method_name = machine_name ? "#{machine_name}_next_state" : :next_state
84
+ define_method(method_name) do |event_name|
85
+ next_state_name = self.send(attribute_name).next_state_name(event_name)
86
+ self.class.stateful_state_machine(machine_name).find(next_state_name)
87
+ end
88
+ end
89
+
90
+ def define_transitions_method(machine_name: nil)
91
+ attribute_name = stateful_attribute_name_for_machine(machine_name)
92
+ method_name = machine_name ? "#{machine_name}_transitions" : :transitions
93
+ define_method(method_name) do
94
+ self.send(attribute_name).transitions
95
+ end
96
+ end
97
+
98
+ def define_initial_stateQ_method(machine_name: nil)
99
+ attribute_name = stateful_attribute_name_for_machine(machine_name)
100
+ method_name = machine_name ? "#{machine_name}_initial_state?" : :initial_state?
101
+ define_method(method_name) do
102
+ self.send(attribute_name) == self.class.stateful_state_machine(machine_name).initial_state
103
+ end
104
+ end
105
+
106
+ def define_final_stateQ_method(machine_name: nil)
107
+ attribute_name = stateful_attribute_name_for_machine(machine_name)
108
+ method_name = machine_name ? "#{machine_name}_final_state?" : :final_state?
109
+ define_method(method_name) do
110
+ self.class.stateful_state_machine(machine_name).final_states.include?(self.send(attribute_name))
111
+ end
112
+ end
113
+
54
114
  # predicate methods
55
115
 
56
116
  def final_state?