nxt_state_machine 0.1.3 → 0.1.8
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/.circleci/config.yml +8 -2
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +24 -20
- data/README.md +49 -16
- data/Rakefile +1 -1
- data/lib/nxt_state_machine.rb +2 -0
- data/lib/nxt_state_machine/defuse_registry.rb +26 -0
- data/lib/nxt_state_machine/event.rb +6 -0
- data/lib/nxt_state_machine/graph.rb +77 -0
- data/lib/nxt_state_machine/integrations/active_record.rb +41 -25
- data/lib/nxt_state_machine/integrations/attr_accessor.rb +2 -2
- data/lib/nxt_state_machine/integrations/hash.rb +2 -2
- data/lib/nxt_state_machine/state_machine.rb +18 -1
- data/lib/nxt_state_machine/transition.rb +14 -7
- data/lib/nxt_state_machine/transition/factory.rb +1 -1
- data/lib/nxt_state_machine/version.rb +1 -1
- data/lib/railtie.rb +8 -0
- data/lib/tasks/draw_graph.rake +8 -0
- data/nxt_state_machine.gemspec +2 -1
- data/state_machine.png +0 -0
- metadata +24 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89b1ad3a07fcc60e4b3541b770f1e1f287713e973d3a038f3496be0a145006c5
|
4
|
+
data.tar.gz: f213f914ffe2ff39a0d90d8c61f574857b312c9065e1241e9e09dce16cf6cc3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48b17f521db0a19ccc500fff321b459f4e962875792e047923185a45691c20b81bfec5bbab03b77cf6eaeb10be2b8191ec73bfbc4d4b05c9e518389b1d9cc497
|
7
|
+
data.tar.gz: 286382b6dd9512abc0a0b1c1f177196edab5aced50bc8888d0febf4a58a86697ac152b8a7617c238f3268cbbada15f9767248b333264cf1608421d6a9b9c2b93
|
data/.circleci/config.yml
CHANGED
@@ -16,11 +16,17 @@ jobs:
|
|
16
16
|
steps:
|
17
17
|
- checkout
|
18
18
|
|
19
|
+
- run:
|
20
|
+
name: Install apt dependencies
|
21
|
+
command: |
|
22
|
+
sudo apt update -q \
|
23
|
+
&& sudo apt upgrade -q \
|
24
|
+
&& sudo apt install -qq graphviz
|
19
25
|
# Download and cache dependencies
|
20
26
|
- restore_cache:
|
21
27
|
keys:
|
22
28
|
- v1-dependencies-{{ checksum "Gemfile.lock" }}
|
23
|
-
|
29
|
+
|
24
30
|
- run: gem install bundler --version $BUNDLER_VERSION
|
25
31
|
|
26
32
|
- run:
|
@@ -53,4 +59,4 @@ jobs:
|
|
53
59
|
path: /tmp/test-results
|
54
60
|
- store_artifacts:
|
55
61
|
path: /tmp/test-results
|
56
|
-
destination: test-results
|
62
|
+
destination: test-results
|
data/CHANGELOG.md
ADDED
data/Gemfile.lock
CHANGED
@@ -1,37 +1,38 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nxt_state_machine (0.1.
|
4
|
+
nxt_state_machine (0.1.8)
|
5
5
|
activesupport
|
6
6
|
nxt_registry (~> 0.1.3)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activemodel (6.0.
|
12
|
-
activesupport (= 6.0.
|
13
|
-
activerecord (6.0.
|
14
|
-
activemodel (= 6.0.
|
15
|
-
activesupport (= 6.0.
|
16
|
-
activesupport (6.0.
|
11
|
+
activemodel (6.0.3.1)
|
12
|
+
activesupport (= 6.0.3.1)
|
13
|
+
activerecord (6.0.3.1)
|
14
|
+
activemodel (= 6.0.3.1)
|
15
|
+
activesupport (= 6.0.3.1)
|
16
|
+
activesupport (6.0.3.1)
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
18
|
i18n (>= 0.7, < 2)
|
19
19
|
minitest (~> 5.1)
|
20
20
|
tzinfo (~> 1.1)
|
21
|
-
zeitwerk (~> 2.2)
|
21
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
22
22
|
coderay (1.1.2)
|
23
|
-
concurrent-ruby (1.1.
|
23
|
+
concurrent-ruby (1.1.6)
|
24
24
|
diff-lcs (1.3)
|
25
25
|
i18n (1.8.2)
|
26
26
|
concurrent-ruby (~> 1.0)
|
27
|
-
method_source (0.
|
28
|
-
minitest (5.14.
|
29
|
-
nxt_registry (0.1.
|
27
|
+
method_source (1.0.0)
|
28
|
+
minitest (5.14.1)
|
29
|
+
nxt_registry (0.1.5)
|
30
30
|
activesupport
|
31
|
-
pry (0.
|
32
|
-
coderay (~> 1.1
|
33
|
-
method_source (~>
|
34
|
-
rake (
|
31
|
+
pry (0.13.1)
|
32
|
+
coderay (~> 1.1)
|
33
|
+
method_source (~> 1.0)
|
34
|
+
rake (12.3.3)
|
35
|
+
rexml (3.2.4)
|
35
36
|
rspec (3.9.0)
|
36
37
|
rspec-core (~> 3.9.0)
|
37
38
|
rspec-expectations (~> 3.9.0)
|
@@ -47,11 +48,13 @@ GEM
|
|
47
48
|
rspec-support (3.9.0)
|
48
49
|
rspec_junit_formatter (0.4.1)
|
49
50
|
rspec-core (>= 2, < 4, != 2.12.0)
|
51
|
+
ruby-graphviz (1.2.5)
|
52
|
+
rexml
|
50
53
|
sqlite3 (1.4.2)
|
51
54
|
thread_safe (0.3.6)
|
52
|
-
tzinfo (1.2.
|
55
|
+
tzinfo (1.2.7)
|
53
56
|
thread_safe (~> 0.1)
|
54
|
-
zeitwerk (2.
|
57
|
+
zeitwerk (2.3.0)
|
55
58
|
|
56
59
|
PLATFORMS
|
57
60
|
ruby
|
@@ -61,10 +64,11 @@ DEPENDENCIES
|
|
61
64
|
bundler (~> 2.0)
|
62
65
|
nxt_state_machine!
|
63
66
|
pry
|
64
|
-
rake (~>
|
67
|
+
rake (~> 12.0)
|
65
68
|
rspec (~> 3.0)
|
66
69
|
rspec_junit_formatter
|
70
|
+
ruby-graphviz
|
67
71
|
sqlite3
|
68
72
|
|
69
73
|
BUNDLED WITH
|
70
|
-
2.
|
74
|
+
2.1.4
|
data/README.md
CHANGED
@@ -83,6 +83,10 @@ class ArticleWorkflow
|
|
83
83
|
puts 'around transition exit'
|
84
84
|
end
|
85
85
|
|
86
|
+
on_success from: any_state, to: :approved do |transition|
|
87
|
+
# This is the last callback in the chain - It runs outside of the active record transaction
|
88
|
+
end
|
89
|
+
|
86
90
|
on_error CustomError from: any_state, to: :approved do |error, transition|
|
87
91
|
end
|
88
92
|
end
|
@@ -277,10 +281,10 @@ Transitions can be halted in callbacks and during the transition itself simply b
|
|
277
281
|
|
278
282
|
### Callbacks
|
279
283
|
|
280
|
-
You can register `before_transition`, `around_transition` and `
|
281
|
-
:from and :to states you decide on which transitions the callback actually runs. Around callbacks need
|
282
|
-
proc object that they get passed in. Registering callbacks inside an event block or on the state_machine top
|
283
|
-
behaves exactly the same way and is only a matter of structure. The only thing that defines when callbacks run is
|
284
|
+
You can register `before_transition`, `around_transition`, `after_transition` and `on_success` callbacks.
|
285
|
+
By defining the :from and :to states you decide on which transitions the callback actually runs. Around callbacks need
|
286
|
+
to call the proc object that they get passed in. Registering callbacks inside an event block or on the state_machine top
|
287
|
+
level behaves exactly the same way and is only a matter of structure. The only thing that defines when callbacks run is
|
284
288
|
the :from and :to parameters with which they are registered.
|
285
289
|
|
286
290
|
|
@@ -298,6 +302,11 @@ event :approve do
|
|
298
302
|
block.call
|
299
303
|
puts 'around transition exit'
|
300
304
|
end
|
305
|
+
|
306
|
+
# Use this to trigger another event after the transaction around the transition completed
|
307
|
+
on_success from: any_state, to: :approved do |transition|
|
308
|
+
# This is the last callback in the chain - It runs outside of the active record transaction
|
309
|
+
end
|
301
310
|
end
|
302
311
|
```
|
303
312
|
|
@@ -327,7 +336,42 @@ state_machine do
|
|
327
336
|
end
|
328
337
|
```
|
329
338
|
|
330
|
-
###
|
339
|
+
### ActiveRecord transaction, rollback and locks - breaking the flow by defusing errors
|
340
|
+
|
341
|
+
You want to break out of your transition (which is wrapped inside a lock)?
|
342
|
+
You can raise an error, have everything rolled back and then have your error handler take over.
|
343
|
+
**NOTE:** Unless you reload your model all assignments you did, previous to the error, should still be available in your
|
344
|
+
error handler. You can also defuse errors. This means they will not cause a rollback of the transaction during the
|
345
|
+
transition and you can actually persist changes to your model before the defused error is raised and handled.
|
346
|
+
|
347
|
+
```ruby
|
348
|
+
state_machine do
|
349
|
+
# ...
|
350
|
+
#
|
351
|
+
defuse CustomError, from: any_state, to: all_states
|
352
|
+
|
353
|
+
event :approve do
|
354
|
+
# You can also defuse on event level
|
355
|
+
# defuse CustomError, from: %i[written submitted deleted], to: :approved
|
356
|
+
|
357
|
+
transition from: %i[written submitted deleted], to: :approved do |headline:|
|
358
|
+
# This will be save to the database even if defused CustomError is raised after
|
359
|
+
article.update!(headline: headline)
|
360
|
+
raise CustomError, 'This does not rollback the headline update above'
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
on_error! CustomError from: any_state, to: :approved do |error, transition|
|
365
|
+
# You can still handle the defused Error if you want to
|
366
|
+
# You should probably reload your model here to not accidentally save changes that
|
367
|
+
# were made to the model during the transition before a non defused error was raised
|
368
|
+
article.reload
|
369
|
+
# The error callback does not run inside the transaction. No more strings attached here.
|
370
|
+
# You can now persist changes to your model again.
|
371
|
+
article.update!(error: error.message)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
```
|
331
375
|
|
332
376
|
In theory you can also have multiple state_machines in the same class. To do so you have to give each
|
333
377
|
state_machine a name. Events need to be unique globally in order to determine which state_machine will be called.
|
@@ -349,17 +393,6 @@ class Article < ApplicationRecord
|
|
349
393
|
end
|
350
394
|
```
|
351
395
|
|
352
|
-
|
353
|
-
## TODO
|
354
|
-
- Test implementations for Hash, AttrAccessor
|
355
|
-
- Thread safety spec!
|
356
|
-
- Spec locks?
|
357
|
-
- Explain locking in readme!
|
358
|
-
- Should we clone machines for each context?
|
359
|
-
- What about inheritance? => What would be the expected behaviour? (dup vs. no dup)
|
360
|
-
=> Might also make sense to walk the ancestors chain and collect configure blocks
|
361
|
-
|
362
|
-
|
363
396
|
## Development
|
364
397
|
|
365
398
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Rakefile
CHANGED
data/lib/nxt_state_machine.rb
CHANGED
@@ -16,6 +16,7 @@ require "nxt_state_machine/callable"
|
|
16
16
|
require "nxt_state_machine/state_registry"
|
17
17
|
require "nxt_state_machine/callback_registry"
|
18
18
|
require "nxt_state_machine/error_callback_registry"
|
19
|
+
require "nxt_state_machine/defuse_registry"
|
19
20
|
require "nxt_state_machine/event_registry"
|
20
21
|
require "nxt_state_machine/state"
|
21
22
|
require "nxt_state_machine/event"
|
@@ -30,6 +31,7 @@ require "nxt_state_machine/state_machine"
|
|
30
31
|
require "nxt_state_machine/integrations/active_record"
|
31
32
|
require "nxt_state_machine/integrations/attr_accessor"
|
32
33
|
require "nxt_state_machine/integrations/hash"
|
34
|
+
require "nxt_state_machine/graph"
|
33
35
|
|
34
36
|
module NxtStateMachine
|
35
37
|
module ClassMethods
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module NxtStateMachine
|
2
|
+
class DefuseRegistry
|
3
|
+
include ::NxtRegistry
|
4
|
+
|
5
|
+
def register(from, to, kind)
|
6
|
+
Array(from).each do |from_state|
|
7
|
+
Array(to).each do |to_state|
|
8
|
+
defusing_errors = errors.from(from_state).to(to_state)
|
9
|
+
Array(kind).each_with_object(defusing_errors) { |error, acc| acc << error }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def resolve(transition)
|
15
|
+
errors.from(transition.from.enum).to(transition.to.enum)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def errors
|
21
|
+
@errors ||= registry :from do
|
22
|
+
nested :to, default: -> { [] }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -18,11 +18,13 @@ module NxtStateMachine
|
|
18
18
|
delegate :before_transition,
|
19
19
|
:after_transition,
|
20
20
|
:around_transition,
|
21
|
+
:on_success,
|
21
22
|
:on_error,
|
22
23
|
:on_error!,
|
23
24
|
:any_state,
|
24
25
|
:all_states,
|
25
26
|
:all_states_except,
|
27
|
+
:defuse,
|
26
28
|
to: :state_machine
|
27
29
|
|
28
30
|
def transitions(from:, to:, &block)
|
@@ -39,6 +41,10 @@ module NxtStateMachine
|
|
39
41
|
event_transitions.resolve!(state).present?
|
40
42
|
end
|
41
43
|
|
44
|
+
def to_s
|
45
|
+
"#{self.class.name}[:#{name}]"
|
46
|
+
end
|
47
|
+
|
42
48
|
private
|
43
49
|
|
44
50
|
def configure(&block)
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module NxtStateMachine
|
2
|
+
class Graph
|
3
|
+
def initialize(state_machines, **options)
|
4
|
+
@state_machines = state_machines
|
5
|
+
@options = default_options.merge(**options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def draw
|
9
|
+
require 'ruby-graphviz'
|
10
|
+
|
11
|
+
state_machines.each do |_, state_machine|
|
12
|
+
add_nodes(state_machine)
|
13
|
+
add_edges(state_machine)
|
14
|
+
end
|
15
|
+
|
16
|
+
filename = File.join(options[:path], "#{options[:name]}.#{options[:format]}")
|
17
|
+
|
18
|
+
graph.output options[:format] => filename
|
19
|
+
|
20
|
+
puts '----------------------------------------------'
|
21
|
+
puts 'Please run the following to open the generated file:'
|
22
|
+
puts "open '#{filename}'"
|
23
|
+
puts '----------------------------------------------'
|
24
|
+
|
25
|
+
graph
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :options, :state_machines
|
31
|
+
|
32
|
+
def graph
|
33
|
+
@graph ||= ::GraphViz.new(
|
34
|
+
'G',
|
35
|
+
rankdir: options[:orientation] == 'landscape' ? 'LR' : 'TB',
|
36
|
+
ratio: options[:ratio]
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_nodes(state_machine)
|
41
|
+
binding.pry
|
42
|
+
state_machine.states.values.each do |state|
|
43
|
+
add_node(state)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_node(state)
|
48
|
+
node_options = {
|
49
|
+
label: state.to_s,
|
50
|
+
width: '1',
|
51
|
+
height: '1',
|
52
|
+
shape: 'ellipse'
|
53
|
+
}
|
54
|
+
|
55
|
+
graph.add_nodes(state.to_s, node_options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_edges(state_machine)
|
59
|
+
state_machine.events.values.each do |event|
|
60
|
+
event.event_transitions.values.each do |transition|
|
61
|
+
graph.add_edges(transition.from.to_s, transition.to.to_s, label: event.name)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_options
|
67
|
+
{
|
68
|
+
name: 'state_machine',
|
69
|
+
path: '.',
|
70
|
+
orientation: 'landscape',
|
71
|
+
ratio: 'fill',
|
72
|
+
format: 'png',
|
73
|
+
font: 'Helvetica'
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -21,34 +21,21 @@ module NxtStateMachine
|
|
21
21
|
end
|
22
22
|
|
23
23
|
machine.set_state_with do |target, transition|
|
24
|
-
target
|
25
|
-
transition.run_before_callbacks
|
26
|
-
result = set_state(target, transition, state_attr, :save)
|
27
|
-
transition.run_after_callbacks
|
28
|
-
|
29
|
-
result
|
30
|
-
end
|
31
|
-
rescue StandardError => error
|
32
|
-
target.assign_attributes(state_attr => transition.from.to_s)
|
33
|
-
|
34
|
-
if error.is_a?(NxtStateMachine::Errors::TransitionHalted)
|
35
|
-
false
|
36
|
-
else
|
37
|
-
raise
|
38
|
-
end
|
24
|
+
set_state(machine, target, transition, state_attr, :save)
|
39
25
|
end
|
40
26
|
|
41
27
|
machine.set_state_with! do |target, transition|
|
42
|
-
target
|
43
|
-
|
44
|
-
result = set_state(target, transition, state_attr, :save!)
|
45
|
-
transition.run_after_callbacks
|
28
|
+
set_state(machine, target, transition, state_attr, :save!)
|
29
|
+
end
|
46
30
|
|
47
|
-
|
31
|
+
machine.define_singleton_method :add_state_methods_to_model do |model_class|
|
32
|
+
model_class.class_eval do
|
33
|
+
machine.states.keys.each do |state_name|
|
34
|
+
define_method "#{state_name}?" do
|
35
|
+
send(machine.options.fetch(:state_attr)) == state_name
|
36
|
+
end
|
37
|
+
end
|
48
38
|
end
|
49
|
-
rescue StandardError
|
50
|
-
target.assign_attributes(state_attr => transition.from.to_s)
|
51
|
-
raise
|
52
39
|
end
|
53
40
|
|
54
41
|
machine
|
@@ -58,11 +45,40 @@ module NxtStateMachine
|
|
58
45
|
module InstanceMethods
|
59
46
|
private
|
60
47
|
|
61
|
-
def set_state(target, transition, state_attr,
|
48
|
+
def set_state(machine, target, transition, state_attr, save_with_method)
|
49
|
+
result = nil
|
50
|
+
defused_error = nil
|
51
|
+
|
52
|
+
target.with_lock do
|
53
|
+
transition.run_before_callbacks
|
54
|
+
result = execute_transition(target, transition, state_attr, save_with_method)
|
55
|
+
transition.run_after_callbacks
|
56
|
+
|
57
|
+
result
|
58
|
+
rescue StandardError => error
|
59
|
+
if machine.defuse_registry.resolve(transition).find { |error_class| error.is_a?(error_class) }
|
60
|
+
defused_error = error
|
61
|
+
else
|
62
|
+
raise error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
raise defused_error if defused_error
|
67
|
+
|
68
|
+
transition.run_success_callbacks || result
|
69
|
+
rescue StandardError => error
|
70
|
+
target.assign_attributes(state_attr => transition.from.to_s)
|
71
|
+
|
72
|
+
raise unless save_with_method == :save && error.is_a?(NxtStateMachine::Errors::TransitionHalted)
|
73
|
+
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
def execute_transition(target, transition, state_attr, save_with_method)
|
62
78
|
transition.execute do |block|
|
63
79
|
result = block ? block.call : nil
|
64
80
|
target.assign_attributes(state_attr => transition.to.to_s)
|
65
|
-
set_state_result = target.send(
|
81
|
+
set_state_result = target.send(save_with_method) || halt_transition
|
66
82
|
block ? result : set_state_result
|
67
83
|
end
|
68
84
|
end
|
@@ -22,7 +22,7 @@ module NxtStateMachine
|
|
22
22
|
result = set_state(target, transition, state_attr)
|
23
23
|
transition.run_after_callbacks
|
24
24
|
|
25
|
-
result
|
25
|
+
transition.run_success_callbacks || result
|
26
26
|
rescue StandardError => error
|
27
27
|
target.send("#{state_attr}=", transition.from.enum)
|
28
28
|
|
@@ -38,7 +38,7 @@ module NxtStateMachine
|
|
38
38
|
result = set_state(target, transition, state_attr)
|
39
39
|
transition.run_after_callbacks
|
40
40
|
|
41
|
-
result
|
41
|
+
transition.run_success_callbacks || result
|
42
42
|
rescue StandardError
|
43
43
|
target.send("#{state_attr}=", transition.from.enum)
|
44
44
|
raise
|
@@ -21,7 +21,7 @@ module NxtStateMachine
|
|
21
21
|
transition.run_before_callbacks
|
22
22
|
result = set_state(current_target, transition, state_attr)
|
23
23
|
transition.run_after_callbacks
|
24
|
-
result
|
24
|
+
transition.run_success_callbacks || result
|
25
25
|
rescue StandardError => error
|
26
26
|
current_target[state_attr] = transition.from.enum
|
27
27
|
|
@@ -37,7 +37,7 @@ module NxtStateMachine
|
|
37
37
|
result = set_state(current_target, transition, state_attr)
|
38
38
|
transition.run_after_callbacks
|
39
39
|
|
40
|
-
result
|
40
|
+
transition.run_success_callbacks || result
|
41
41
|
rescue StandardError
|
42
42
|
current_target[state_attr] = transition.from.enum
|
43
43
|
raise
|
@@ -10,11 +10,12 @@ module NxtStateMachine
|
|
10
10
|
@events = event_registry
|
11
11
|
@callbacks = CallbackRegistry.new
|
12
12
|
@error_callback_registry = ErrorCallbackRegistry.new
|
13
|
+
@defuse_registry = DefuseRegistry.new
|
13
14
|
|
14
15
|
@initial_state = nil
|
15
16
|
end
|
16
17
|
|
17
|
-
attr_reader :class_context, :
|
18
|
+
attr_reader :class_context, :events, :options, :callbacks, :name, :error_callback_registry, :defuse_registry
|
18
19
|
attr_accessor :initial_state
|
19
20
|
|
20
21
|
def get_state_with(method = nil, &block)
|
@@ -89,6 +90,7 @@ module NxtStateMachine
|
|
89
90
|
class_context.define_method event_name do |*args, **opts|
|
90
91
|
event.state_machine.can_transition!(name, event.state_machine.current_state_name(self))
|
91
92
|
transition = event.event_transitions.resolve(event.state_machine.current_state_name(self))
|
93
|
+
# Transition is build every time and thus should be thread safe!
|
92
94
|
transition.build_transition(event_name, self, set_state_method, *args, **opts)
|
93
95
|
end
|
94
96
|
end
|
@@ -116,6 +118,14 @@ module NxtStateMachine
|
|
116
118
|
callbacks.register(from, to, :after, run, block)
|
117
119
|
end
|
118
120
|
|
121
|
+
def on_success(from:, to:, run: nil, &block)
|
122
|
+
callbacks.register(from, to, :success, run, block)
|
123
|
+
end
|
124
|
+
|
125
|
+
def defuse(errors = [], from:, to:)
|
126
|
+
defuse_registry.register(from, to, errors)
|
127
|
+
end
|
128
|
+
|
119
129
|
def on_error(error = StandardError, from:, to:, run: nil, &block)
|
120
130
|
error_callback_registry.register(from, to, error, run, block)
|
121
131
|
end
|
@@ -142,16 +152,23 @@ module NxtStateMachine
|
|
142
152
|
run_callbacks(transition, :after, context)
|
143
153
|
end
|
144
154
|
|
155
|
+
def run_success_callbacks(transition, context)
|
156
|
+
run_callbacks(transition, :success, context)
|
157
|
+
end
|
158
|
+
|
145
159
|
def find_error_callback(error, transition)
|
146
160
|
error_callback_registry.resolve(error, transition)
|
147
161
|
end
|
148
162
|
|
149
163
|
def run_callbacks(transition, kind, context)
|
150
164
|
current_callbacks = callbacks.resolve(transition, kind)
|
165
|
+
return unless current_callbacks.any?
|
151
166
|
|
152
167
|
current_callbacks.each do |callback|
|
153
168
|
Callable.new(callback).bind(context).call(transition)
|
154
169
|
end
|
170
|
+
|
171
|
+
true
|
155
172
|
end
|
156
173
|
|
157
174
|
def current_state_name(context)
|
@@ -11,20 +11,18 @@ module NxtStateMachine
|
|
11
11
|
@set_state_method = set_state_method
|
12
12
|
@context = context
|
13
13
|
@block = block
|
14
|
+
@result = nil
|
14
15
|
end
|
15
16
|
|
16
|
-
attr_reader :name, :from, :to, :block, :event
|
17
|
+
attr_reader :name, :from, :to, :block, :event, :result
|
17
18
|
|
18
|
-
|
19
|
+
# This triggers the set state method
|
20
|
+
def trigger
|
19
21
|
Callable.new(
|
20
22
|
state_machine.send(set_state_method)
|
21
23
|
).bind(
|
22
24
|
context
|
23
25
|
).call(state_machine.target(context), self)
|
24
|
-
end
|
25
|
-
|
26
|
-
def execute(&block)
|
27
|
-
Transition::Proxy.new(event, state_machine,self, context).call(&block)
|
28
26
|
rescue StandardError => error
|
29
27
|
callback = state_machine.find_error_callback(error, self)
|
30
28
|
raise unless callback
|
@@ -32,6 +30,11 @@ module NxtStateMachine
|
|
32
30
|
Callable.new(callback).bind(context).call(error, self)
|
33
31
|
end
|
34
32
|
|
33
|
+
# This must be used in set_state method to actually execute the transition within the around callback chain
|
34
|
+
def execute(&block)
|
35
|
+
self.result = Transition::Proxy.new(event, state_machine,self, context).call(&block)
|
36
|
+
end
|
37
|
+
|
35
38
|
alias_method :with_around_callbacks, :execute
|
36
39
|
|
37
40
|
def run_before_callbacks
|
@@ -42,9 +45,13 @@ module NxtStateMachine
|
|
42
45
|
state_machine.run_after_callbacks(self, context)
|
43
46
|
end
|
44
47
|
|
48
|
+
def run_success_callbacks
|
49
|
+
state_machine.run_success_callbacks(self, context)
|
50
|
+
end
|
51
|
+
|
45
52
|
private
|
46
53
|
|
47
54
|
attr_reader :state_machine, :set_state_method, :context
|
48
|
-
attr_writer :block
|
55
|
+
attr_writer :block, :result
|
49
56
|
end
|
50
57
|
end
|
data/lib/railtie.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
namespace :graph do
|
2
|
+
desc 'draw the graph of a state machine'
|
3
|
+
task :draw, [:state_machine_class] => [:environment] do |_, args|
|
4
|
+
state_machine_class = Object.const_get(args.fetch(:state_machine_class))
|
5
|
+
state_machine = state_machine_class.state_machine
|
6
|
+
NxtStateMachine::Graph.new(state_machine).draw
|
7
|
+
end
|
8
|
+
end
|
data/nxt_state_machine.gemspec
CHANGED
@@ -38,9 +38,10 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.add_dependency "nxt_registry", "~> 0.1.3"
|
39
39
|
|
40
40
|
spec.add_development_dependency "bundler", "~> 2.0"
|
41
|
-
spec.add_development_dependency "rake", "~>
|
41
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
42
42
|
spec.add_development_dependency "rspec", "~> 3.0"
|
43
43
|
spec.add_development_dependency "pry"
|
44
44
|
spec.add_development_dependency "activerecord"
|
45
45
|
spec.add_development_dependency "sqlite3"
|
46
|
+
spec.add_development_dependency "ruby-graphviz"
|
46
47
|
end
|
data/state_machine.png
ADDED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nxt_state_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Robecke
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2020-
|
14
|
+
date: 2020-06-08 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
@@ -61,14 +61,14 @@ dependencies:
|
|
61
61
|
requirements:
|
62
62
|
- - "~>"
|
63
63
|
- !ruby/object:Gem::Version
|
64
|
-
version: '
|
64
|
+
version: '12.0'
|
65
65
|
type: :development
|
66
66
|
prerelease: false
|
67
67
|
version_requirements: !ruby/object:Gem::Requirement
|
68
68
|
requirements:
|
69
69
|
- - "~>"
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version: '
|
71
|
+
version: '12.0'
|
72
72
|
- !ruby/object:Gem::Dependency
|
73
73
|
name: rspec
|
74
74
|
requirement: !ruby/object:Gem::Requirement
|
@@ -125,6 +125,20 @@ dependencies:
|
|
125
125
|
- - ">="
|
126
126
|
- !ruby/object:Gem::Version
|
127
127
|
version: '0'
|
128
|
+
- !ruby/object:Gem::Dependency
|
129
|
+
name: ruby-graphviz
|
130
|
+
requirement: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
type: :development
|
136
|
+
prerelease: false
|
137
|
+
version_requirements: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
128
142
|
description: A state machine library that can be used with ActiveRecord or in plain
|
129
143
|
ruby and should be easy to customize for other integrations
|
130
144
|
email:
|
@@ -139,6 +153,7 @@ files:
|
|
139
153
|
- ".rspec"
|
140
154
|
- ".ruby-version"
|
141
155
|
- ".travis.yml"
|
156
|
+
- CHANGELOG.md
|
142
157
|
- Gemfile
|
143
158
|
- Gemfile.lock
|
144
159
|
- LICENSE.txt
|
@@ -149,6 +164,7 @@ files:
|
|
149
164
|
- lib/nxt_state_machine.rb
|
150
165
|
- lib/nxt_state_machine/callable.rb
|
151
166
|
- lib/nxt_state_machine/callback_registry.rb
|
167
|
+
- lib/nxt_state_machine/defuse_registry.rb
|
152
168
|
- lib/nxt_state_machine/error_callback_registry.rb
|
153
169
|
- lib/nxt_state_machine/errors/error.rb
|
154
170
|
- lib/nxt_state_machine/errors/event_already_registered.rb
|
@@ -165,6 +181,7 @@ files:
|
|
165
181
|
- lib/nxt_state_machine/event.rb
|
166
182
|
- lib/nxt_state_machine/event/names.rb
|
167
183
|
- lib/nxt_state_machine/event_registry.rb
|
184
|
+
- lib/nxt_state_machine/graph.rb
|
168
185
|
- lib/nxt_state_machine/integrations/active_record.rb
|
169
186
|
- lib/nxt_state_machine/integrations/attr_accessor.rb
|
170
187
|
- lib/nxt_state_machine/integrations/hash.rb
|
@@ -178,7 +195,10 @@ files:
|
|
178
195
|
- lib/nxt_state_machine/transition/proxy.rb
|
179
196
|
- lib/nxt_state_machine/transition/store.rb
|
180
197
|
- lib/nxt_state_machine/version.rb
|
198
|
+
- lib/railtie.rb
|
199
|
+
- lib/tasks/draw_graph.rake
|
181
200
|
- nxt_state_machine.gemspec
|
201
|
+
- state_machine.png
|
182
202
|
homepage: https://github.com/nxt-insurance/nxt_state_machine
|
183
203
|
licenses:
|
184
204
|
- MIT
|