finite_machine 0.11.3 → 0.12.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 (106) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +564 -569
  4. data/Rakefile +5 -1
  5. data/benchmarks/memory_profile.rb +11 -0
  6. data/benchmarks/memory_usage.rb +16 -9
  7. data/finite_machine.gemspec +10 -3
  8. data/lib/finite_machine.rb +34 -46
  9. data/lib/finite_machine/async_call.rb +5 -21
  10. data/lib/finite_machine/callable.rb +4 -4
  11. data/lib/finite_machine/catchable.rb +4 -2
  12. data/lib/finite_machine/choice_merger.rb +19 -19
  13. data/lib/finite_machine/const.rb +16 -0
  14. data/lib/finite_machine/definition.rb +2 -2
  15. data/lib/finite_machine/dsl.rb +66 -149
  16. data/lib/finite_machine/env.rb +4 -2
  17. data/lib/finite_machine/event_definition.rb +7 -15
  18. data/lib/finite_machine/{events_chain.rb → events_map.rb} +39 -51
  19. data/lib/finite_machine/hook_event.rb +60 -61
  20. data/lib/finite_machine/hooks.rb +44 -36
  21. data/lib/finite_machine/listener.rb +2 -2
  22. data/lib/finite_machine/logger.rb +5 -4
  23. data/lib/finite_machine/message_queue.rb +39 -30
  24. data/lib/finite_machine/observer.rb +55 -37
  25. data/lib/finite_machine/safety.rb +12 -10
  26. data/lib/finite_machine/state_definition.rb +3 -5
  27. data/lib/finite_machine/state_machine.rb +83 -64
  28. data/lib/finite_machine/state_parser.rb +51 -79
  29. data/lib/finite_machine/subscribers.rb +1 -1
  30. data/lib/finite_machine/threadable.rb +3 -1
  31. data/lib/finite_machine/transition.rb +30 -31
  32. data/lib/finite_machine/transition_builder.rb +23 -32
  33. data/lib/finite_machine/transition_event.rb +12 -11
  34. data/lib/finite_machine/two_phase_lock.rb +3 -1
  35. data/lib/finite_machine/undefined_transition.rb +5 -6
  36. data/lib/finite_machine/version.rb +2 -2
  37. data/spec/integration/system_spec.rb +36 -38
  38. data/spec/performance/benchmark_spec.rb +13 -21
  39. data/spec/unit/alias_target_spec.rb +22 -41
  40. data/spec/unit/async_callbacks_spec.rb +8 -13
  41. data/spec/unit/auto_methods_spec.rb +44 -0
  42. data/spec/unit/callable/call_spec.rb +1 -3
  43. data/spec/unit/callbacks_spec.rb +372 -463
  44. data/spec/unit/can_spec.rb +13 -23
  45. data/spec/unit/cancel_callbacks_spec.rb +46 -0
  46. data/spec/unit/choice_spec.rb +105 -141
  47. data/spec/unit/define_spec.rb +31 -31
  48. data/spec/unit/definition_spec.rb +24 -41
  49. data/spec/unit/event_names_spec.rb +6 -10
  50. data/spec/unit/events_map/add_spec.rb +23 -0
  51. data/spec/unit/events_map/choice_transition_spec.rb +25 -0
  52. data/spec/unit/events_map/clear_spec.rb +13 -0
  53. data/spec/unit/events_map/events_spec.rb +16 -0
  54. data/spec/unit/events_map/inspect_spec.rb +22 -0
  55. data/spec/unit/{events_chain → events_map}/match_transition_spec.rb +12 -14
  56. data/spec/unit/{events_chain → events_map}/move_to_spec.rb +14 -17
  57. data/spec/unit/events_map/states_for_spec.rb +17 -0
  58. data/spec/unit/events_spec.rb +91 -160
  59. data/spec/unit/handlers_spec.rb +34 -66
  60. data/spec/unit/hook_event/any_state_or_event_spec.rb +13 -0
  61. data/spec/unit/hook_event/build_spec.rb +1 -3
  62. data/spec/unit/hook_event/eql_spec.rb +1 -3
  63. data/spec/unit/hook_event/initialize_spec.rb +2 -4
  64. data/spec/unit/hook_event/notify_spec.rb +2 -4
  65. data/spec/unit/hooks/clear_spec.rb +1 -1
  66. data/spec/unit/hooks/{call_spec.rb → find_spec.rb} +4 -9
  67. data/spec/unit/hooks/inspect_spec.rb +16 -8
  68. data/spec/unit/hooks/register_spec.rb +4 -9
  69. data/spec/unit/if_unless_spec.rb +76 -115
  70. data/spec/unit/initial_spec.rb +50 -82
  71. data/spec/unit/inspect_spec.rb +14 -9
  72. data/spec/unit/is_spec.rb +12 -18
  73. data/spec/unit/log_transitions_spec.rb +4 -10
  74. data/spec/unit/logger_spec.rb +1 -3
  75. data/spec/unit/{event_queue_spec.rb → message_queue_spec.rb} +15 -8
  76. data/spec/unit/new_spec.rb +50 -0
  77. data/spec/unit/respond_to_spec.rb +2 -6
  78. data/spec/unit/state_parser/parse_spec.rb +9 -12
  79. data/spec/unit/states_spec.rb +12 -18
  80. data/spec/unit/subscribers_spec.rb +1 -3
  81. data/spec/unit/target_spec.rb +60 -93
  82. data/spec/unit/terminated_spec.rb +15 -25
  83. data/spec/unit/transition/check_conditions_spec.rb +16 -15
  84. data/spec/unit/transition/inspect_spec.rb +6 -6
  85. data/spec/unit/transition/matches_spec.rb +5 -7
  86. data/spec/unit/transition/states_spec.rb +5 -7
  87. data/spec/unit/transition/to_state_spec.rb +5 -13
  88. data/spec/unit/trigger_spec.rb +5 -9
  89. data/spec/unit/undefined_transition/eql_spec.rb +1 -3
  90. metadata +86 -49
  91. data/.gitignore +0 -18
  92. data/.rspec +0 -5
  93. data/.travis.yml +0 -27
  94. data/Gemfile +0 -16
  95. data/assets/finite_machine_logo.png +0 -0
  96. data/lib/finite_machine/async_proxy.rb +0 -55
  97. data/spec/unit/async_events_spec.rb +0 -107
  98. data/spec/unit/events_chain/add_spec.rb +0 -25
  99. data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
  100. data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
  101. data/spec/unit/events_chain/clear_spec.rb +0 -15
  102. data/spec/unit/events_chain/events_spec.rb +0 -18
  103. data/spec/unit/events_chain/inspect_spec.rb +0 -24
  104. data/spec/unit/events_chain/states_for_spec.rb +0 -17
  105. data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
  106. data/spec/unit/state_parser/inspect_spec.rb +0 -25
@@ -1,6 +1,4 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
1
+ # frozen_string_literal: true
4
2
 
5
3
  RSpec.describe FiniteMachine, 'can?' do
6
4
  before(:each) {
@@ -12,15 +10,13 @@ RSpec.describe FiniteMachine, 'can?' do
12
10
  }
13
11
 
14
12
  it "allows to check if event can be fired" do
15
- fsm = FiniteMachine.define do
13
+ fsm = FiniteMachine.new do
16
14
  initial :green
17
15
 
18
- events {
19
- event :slow, :green => :yellow
20
- event :stop, :yellow => :red
21
- event :ready, :red => :yellow
22
- event :go, :yellow => :green
23
- }
16
+ event :slow, :green => :yellow
17
+ event :stop, :yellow => :red
18
+ event :ready, :red => :yellow
19
+ event :go, :yellow => :green
24
20
  end
25
21
 
26
22
  expect(fsm.current).to eql(:green)
@@ -57,13 +53,11 @@ RSpec.describe FiniteMachine, 'can?' do
57
53
 
58
54
  context 'with conditionl transition' do
59
55
  it "evalutes condition with parameters" do
60
- fsm = FiniteMachine.define do
56
+ fsm = FiniteMachine.new do
61
57
  initial :green
62
58
 
63
- events {
64
- event :slow, :green => :yellow
65
- event :stop, :yellow => :red, if: proc { |_, state| state }
66
- }
59
+ event :slow, :green => :yellow
60
+ event :stop, :yellow => :red, if: proc { |_, state| state }
67
61
  end
68
62
  expect(fsm.current).to eq(:green)
69
63
  expect(fsm.can?(:slow)).to be(true)
@@ -77,16 +71,12 @@ RSpec.describe FiniteMachine, 'can?' do
77
71
 
78
72
  it "checks against target and grouped events" do
79
73
  bug = Bug.new
80
- fsm = FiniteMachine.define do
74
+ fsm = FiniteMachine.new(bug) do
81
75
  initial :initial
82
76
 
83
- target bug
84
-
85
- events {
86
- event :bump, :initial => :low
87
- event :bump, :low => :medium, if: :pending?
88
- event :bump, :medium => :high
89
- }
77
+ event :bump, :initial => :low
78
+ event :bump, :low => :medium, if: :pending?
79
+ event :bump, :medium => :high
90
80
  end
91
81
  expect(fsm.current).to eq(:initial)
92
82
 
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FiniteMachine, '#cancel_event' do
4
+ it "cancels transition on event callback" do
5
+ fsm = FiniteMachine.new do
6
+ initial :green
7
+
8
+ event :slow, :green => :yellow
9
+ event :go, :yellow => :green
10
+
11
+ on_exit :green do |event|
12
+ cancel_event(event)
13
+ end
14
+ end
15
+
16
+ expect(fsm.current).to eql(:green)
17
+ fsm.slow
18
+ expect(fsm.current).to eql(:green)
19
+ end
20
+
21
+ it "stops executing callbacks when cancelled" do
22
+ called = []
23
+
24
+ fsm = FiniteMachine.new do
25
+ initial :initial
26
+
27
+ event :bump, initial: :low
28
+
29
+ on_before do |event|
30
+ called << "enter_#{event.name}_#{event.from}_#{event.to}"
31
+
32
+ cancel_event(event)
33
+ end
34
+
35
+ on_exit :initial do |event| called << "exit_initial" end
36
+ on_exit do |event| called << "exit_any" end
37
+ on_enter :low do |event| called << "enter_low" end
38
+ on_after :bump do |event| called << "after_#{event.name}" end
39
+ on_after do |event| called << "after_any" end
40
+ end
41
+
42
+ fsm.bump
43
+
44
+ expect(called).to eq(['enter_bump_initial_low'])
45
+ end
46
+ end
@@ -1,6 +1,4 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
1
+ # frozen_string_literal: true
4
2
 
5
3
  RSpec.describe FiniteMachine, '#choice' do
6
4
  before(:each) {
@@ -13,21 +11,17 @@ RSpec.describe FiniteMachine, '#choice' do
13
11
 
14
12
  it "allows for static choice based on conditional branching" do
15
13
  called = []
16
- fsm = FiniteMachine.define do
14
+ fsm = FiniteMachine.new do
17
15
  initial :company_form
18
16
 
19
- events {
20
- event :next, from: :company_form do
21
- choice :agreement_form, if: -> { false }
22
- choice :promo_form, if: -> { false }
23
- choice :official_form, if: -> { true }
24
- end
25
- }
26
-
27
- callbacks {
28
- on_exit do |event| called << "on_exit_#{event.from}" end
29
- on_enter do |event| called << "on_enter_#{event.to}" end
30
- }
17
+ event :next, from: :company_form do
18
+ choice :agreement_form, if: -> { false }
19
+ choice :promo_form, if: -> { false }
20
+ choice :official_form, if: -> { true }
21
+ end
22
+
23
+ on_exit do |event| called << "on_exit_#{event.from}" end
24
+ on_enter do |event| called << "on_enter_#{event.to}" end
31
25
  end
32
26
  expect(fsm.current).to eq(:company_form)
33
27
  fsm.next
@@ -39,16 +33,14 @@ RSpec.describe FiniteMachine, '#choice' do
39
33
  end
40
34
 
41
35
  it "allows for dynamic choice based on conditional branching" do
42
- fsm = FiniteMachine.define do
36
+ fsm = FiniteMachine.new do
43
37
  initial :company_form
44
38
 
45
- events {
46
- event :next, from: :company_form do
47
- choice :agreement_form, if: proc { |_, a| a < 1 }
48
- choice :promo_form, if: proc { |_, a| a == 1 }
49
- choice :official_form, if: proc { |_, a| a > 1 }
50
- end
51
- }
39
+ event :next, from: :company_form do
40
+ choice :agreement_form, if: proc { |_, a| a < 1 }
41
+ choice :promo_form, if: proc { |_, a| a == 1 }
42
+ choice :official_form, if: proc { |_, a| a > 1 }
43
+ end
52
44
  end
53
45
  expect(fsm.current).to eq(:company_form)
54
46
  fsm.next(0)
@@ -65,17 +57,13 @@ RSpec.describe FiniteMachine, '#choice' do
65
57
 
66
58
  it "allows for dynamic choice based on conditional branching and target" do
67
59
  user = User.new
68
- fsm = FiniteMachine.define do
60
+ fsm = FiniteMachine.new(user) do
69
61
  initial :company_form
70
62
 
71
- target user
72
-
73
- events {
74
- event :next, from: :company_form do
75
- choice :agreement_form, if: proc { |_user, token| _user.promo?(token) }
76
- choice :promo_form, unless: proc { |_user, token| _user.promo?(token) }
77
- end
78
- }
63
+ event :next, from: :company_form do
64
+ choice :agreement_form, if: proc { |_user, token| _user.promo?(token) }
65
+ choice :promo_form, unless: proc { |_user, token| _user.promo?(token) }
66
+ end
79
67
  end
80
68
  expect(fsm.current).to eq(:company_form)
81
69
  fsm.next(:no)
@@ -86,16 +74,14 @@ RSpec.describe FiniteMachine, '#choice' do
86
74
  end
87
75
 
88
76
  it "chooses state when skipped if/unless" do
89
- fsm = FiniteMachine.define do
77
+ fsm = FiniteMachine.new do
90
78
  initial :company_form
91
79
 
92
- events {
93
- event :next, from: :company_form do
94
- choice :agreement_form, if: -> { false }
95
- choice :promo_form
96
- choice :official_form, if: -> { true }
97
- end
98
- }
80
+ event :next, from: :company_form do
81
+ choice :agreement_form, if: -> { false }
82
+ choice :promo_form
83
+ choice :official_form, if: -> { true }
84
+ end
99
85
  end
100
86
  expect(fsm.current).to eq(:company_form)
101
87
  fsm.next
@@ -103,16 +89,14 @@ RSpec.describe FiniteMachine, '#choice' do
103
89
  end
104
90
 
105
91
  it "chooses default state when branching conditions don't match" do
106
- fsm = FiniteMachine.define do
92
+ fsm = FiniteMachine.new do
107
93
  initial :company_form
108
94
 
109
- events {
110
- event :next, from: :company_form do
111
- choice :agreement_form, if: -> { false }
112
- choice :promo_form, if: -> { false }
113
- default :official_form
114
- end
115
- }
95
+ event :next, from: :company_form do
96
+ choice :agreement_form, if: -> { false }
97
+ choice :promo_form, if: -> { false }
98
+ default :official_form
99
+ end
116
100
  end
117
101
  expect(fsm.current).to eq(:company_form)
118
102
  fsm.next
@@ -120,15 +104,13 @@ RSpec.describe FiniteMachine, '#choice' do
120
104
  end
121
105
 
122
106
  it "fails to transition when no condition matches without default state" do
123
- fsm = FiniteMachine.define do
107
+ fsm = FiniteMachine.new do
124
108
  initial :company_form
125
109
 
126
- events {
127
- event :next, from: :company_form do
128
- choice :agreement_form, if: -> { false }
129
- choice :promo_form, if: -> { false }
130
- end
131
- }
110
+ event :next, from: :company_form do
111
+ choice :agreement_form, if: -> { false }
112
+ choice :promo_form, if: -> { false }
113
+ end
132
114
  end
133
115
  expect(fsm.current).to eq(:company_form)
134
116
  fsm.next
@@ -136,7 +118,7 @@ RSpec.describe FiniteMachine, '#choice' do
136
118
  end
137
119
 
138
120
  it "allows to transition from multiple states to choice pseudostate" do
139
- fsm = FiniteMachine.define do
121
+ fsm = FiniteMachine.new do
140
122
  initial :red
141
123
 
142
124
  event :go, from: [:yellow, :red] do
@@ -154,10 +136,10 @@ RSpec.describe FiniteMachine, '#choice' do
154
136
  end
155
137
 
156
138
  it "allows to transition from any state to choice pseudo state" do
157
- fsm = FiniteMachine.define do
139
+ fsm = FiniteMachine.new do
158
140
  initial :red
159
141
 
160
- event :go, from: :any do
142
+ event :go, from: any_state do
161
143
  choice :pink, if: -> { false }
162
144
  choice :green
163
145
  end
@@ -168,7 +150,7 @@ RSpec.describe FiniteMachine, '#choice' do
168
150
  end
169
151
 
170
152
  it "groups correctly events under the same name" do
171
- fsm = FiniteMachine.define do
153
+ fsm = FiniteMachine.new do
172
154
  initial :red
173
155
 
174
156
  event :next, from: :yellow, to: :green
@@ -187,24 +169,20 @@ RSpec.describe FiniteMachine, '#choice' do
187
169
 
188
170
  it "performs matching transitions for multiple event definitions with the same name" do
189
171
  ticket = double(:ticket, :pending? => true, :finished? => true)
190
- fsm = FiniteMachine.define do
172
+ fsm = FiniteMachine.new(ticket) do
191
173
  initial :inactive
192
174
 
193
- target ticket
194
-
195
- events {
196
- event :advance, from: [:inactive, :paused, :fulfilled] do
197
- choice :active, if: proc { |_ticket| !_ticket.pending? }
198
- end
175
+ event :advance, from: [:inactive, :paused, :fulfilled] do
176
+ choice :active, if: proc { |_ticket| !_ticket.pending? }
177
+ end
199
178
 
200
- event :advance, from: [:inactive, :active, :fulfilled] do
201
- choice :paused, if: proc { |_ticket| _ticket.pending? }
202
- end
179
+ event :advance, from: [:inactive, :active, :fulfilled] do
180
+ choice :paused, if: proc { |_ticket| _ticket.pending? }
181
+ end
203
182
 
204
- event :advance, from: [:inactive, :active, :paused] do
205
- choice :fulfilled, if: proc { |_ticket| _ticket.finished? }
206
- end
207
- }
183
+ event :advance, from: [:inactive, :active, :paused] do
184
+ choice :fulfilled, if: proc { |_ticket| _ticket.finished? }
185
+ end
208
186
  end
209
187
  expect(fsm.current).to eq(:inactive)
210
188
  fsm.advance
@@ -216,29 +194,23 @@ RSpec.describe FiniteMachine, '#choice' do
216
194
  it "does not transition when no matching choice for multiple event definitions" do
217
195
  ticket = double(:ticket, :pending? => true, :finished? => false)
218
196
  called = []
219
- fsm = FiniteMachine.define do
197
+ fsm = FiniteMachine.new(ticket) do
220
198
  initial :inactive
221
199
 
222
- target ticket
223
-
224
- events {
225
- event :advance, from: [:inactive, :paused, :fulfilled] do
226
- choice :active, if: proc { |_ticket| !_ticket.pending? }
227
- end
200
+ event :advance, from: [:inactive, :paused, :fulfilled] do
201
+ choice :active, if: proc { |_ticket| !_ticket.pending? }
202
+ end
228
203
 
229
- event :advance, from: [:inactive, :active, :fulfilled] do
230
- choice :paused, if: proc { |_ticket| _ticket.pending? }
231
- end
204
+ event :advance, from: [:inactive, :active, :fulfilled] do
205
+ choice :paused, if: proc { |_ticket| _ticket.pending? }
206
+ end
232
207
 
233
- event :advance, from: [:inactive, :active, :paused] do
234
- choice :fulfilled, if: proc { |_ticket| _ticket.finished? }
235
- end
236
- }
208
+ event :advance, from: [:inactive, :active, :paused] do
209
+ choice :fulfilled, if: proc { |_ticket| _ticket.finished? }
210
+ end
237
211
 
238
- callbacks {
239
- on_before(:advance) { called << 'on_before_advance' }
240
- on_after(:advance) { called << 'on_after_advance' }
241
- }
212
+ on_before(:advance) { called << 'on_before_advance' }
213
+ on_after(:advance) { called << 'on_after_advance' }
242
214
  end
243
215
  expect(fsm.current).to eq(:inactive)
244
216
  fsm.advance
@@ -265,57 +237,49 @@ RSpec.describe FiniteMachine, '#choice' do
265
237
  target.expect(c).to target.eql(expected[:c])
266
238
  }
267
239
 
268
- context = self
269
-
270
- fsm = FiniteMachine.define do
240
+ fsm = FiniteMachine.new(self) do
271
241
  initial :red
272
242
 
273
- target context
274
-
275
- events {
276
- event :next, from: :red do
277
- choice :green, if: -> { false }
278
- choice :yellow
279
- end
280
-
281
- event :next, from: :yellow do
282
- choice :green, if: -> { true }
283
- choice :yellow
284
- end
285
-
286
- event :finish, from: :any do
287
- choice :green, if: -> { false }
288
- choice :red
289
- end
290
- }
291
-
292
- callbacks {
293
- # generic state callbacks
294
- on_enter(&callback)
295
- on_transition(&callback)
296
- on_exit(&callback)
297
-
298
- # generic event callbacks
299
- on_before(&callback)
300
- on_after(&callback)
301
-
302
- # state callbacks
303
- on_enter :green, &callback
304
- on_enter :yellow, &callback
305
- on_enter :red, &callback
306
-
307
- on_transition :green, &callback
308
- on_transition :yellow, &callback
309
- on_transition :red, &callback
310
-
311
- on_exit :green, &callback
312
- on_exit :yellow, &callback
313
- on_exit :red, &callback
314
-
315
- # event callbacks
316
- on_before :next, &callback
317
- on_after :next, &callback
318
- }
243
+ event :next, from: :red do
244
+ choice :green, if: -> { false }
245
+ choice :yellow
246
+ end
247
+
248
+ event :next, from: :yellow do
249
+ choice :green, if: -> { true }
250
+ choice :yellow
251
+ end
252
+
253
+ event :finish, from: any_state do
254
+ choice :green, if: -> { false }
255
+ choice :red
256
+ end
257
+
258
+ # generic state callbacks
259
+ on_enter(&callback)
260
+ on_transition(&callback)
261
+ on_exit(&callback)
262
+
263
+ # generic event callbacks
264
+ on_before(&callback)
265
+ on_after(&callback)
266
+
267
+ # state callbacks
268
+ on_enter :green, &callback
269
+ on_enter :yellow, &callback
270
+ on_enter :red, &callback
271
+
272
+ on_transition :green, &callback
273
+ on_transition :yellow, &callback
274
+ on_transition :red, &callback
275
+
276
+ on_exit :green, &callback
277
+ on_exit :yellow, &callback
278
+ on_exit :red, &callback
279
+
280
+ # event callbacks
281
+ on_before :next, &callback
282
+ on_after :next, &callback
319
283
  end
320
284
  expect(fsm.current).to eq(:red)
321
285