nxt_state_machine 0.1.4 → 0.1.5
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/Gemfile.lock +1 -1
- data/README.md +29 -0
- data/lib/nxt_state_machine.rb +1 -0
- data/lib/nxt_state_machine/defuse_registry.rb +26 -0
- data/lib/nxt_state_machine/event.rb +1 -0
- data/lib/nxt_state_machine/integrations/active_record.rb +41 -25
- data/lib/nxt_state_machine/state_machine.rb +6 -1
- data/lib/nxt_state_machine/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e66b8a54f30382001a2c1292ac799c92335cb55835e051efb2a3d450409d67f
|
4
|
+
data.tar.gz: 255c9799a3732ea5ffa3b87048c5d9b6a3f185b43d1d24fb047ceefef89a9514
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dcc9b059691980d6121fd97c2f5814bff20e717054d2c7d4b4e7e678c5b7e299906c4310ac46ebe6a11ac55b443a31248e289fd8b9858e7dbc2247341e981a83
|
7
|
+
data.tar.gz: 4eb4a0fdfbb88b5014c021279a89cff8638f0e01874b15da7bb45718f0de2c3050d1676ed722e6f0d537885669c43bc32c4bcfdda19319e6388325b1055dd394
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -327,8 +327,36 @@ state_machine do
|
|
327
327
|
end
|
328
328
|
```
|
329
329
|
|
330
|
+
### ActiveRecord transaction, rollback and locks - breaking the flow by defusing errors
|
331
|
+
|
332
|
+
You want to break out of your transition (which is wrapped inside a lock)?
|
333
|
+
You can raise an error, have everything rolled back and then have your error handler take over.
|
334
|
+
**NOTE:** Unless you reload your model all assignments you did, previous to the error, should still be available in your
|
335
|
+
error handler. You can also defuse errors. This means they will not cause a rollback of the transaction during the
|
336
|
+
transition and you can actually persist changes to your model before the defused error is raised and handled.
|
337
|
+
|
330
338
|
### Multiple state machines in the same class
|
331
339
|
|
340
|
+
```ruby
|
341
|
+
state_machine do
|
342
|
+
# ...
|
343
|
+
#
|
344
|
+
defuse CustomError, from: any_state, to: all_states
|
345
|
+
|
346
|
+
event :approve do
|
347
|
+
transition from: %i[written submitted deleted], to: :approved do |headline:|
|
348
|
+
# This will be save to the database even if defused CustomError is raised after
|
349
|
+
article.update!(headline: headline)
|
350
|
+
raise CustomError, 'This does not rollback the headline update above'
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
on_error! CustomError from: any_state, to: :approved do |error, transition|
|
355
|
+
# You can still handle the defused Error if you want to
|
356
|
+
end
|
357
|
+
end
|
358
|
+
```
|
359
|
+
|
332
360
|
In theory you can also have multiple state_machines in the same class. To do so you have to give each
|
333
361
|
state_machine a name. Events need to be unique globally in order to determine which state_machine will be called.
|
334
362
|
You can also trigger events from one another.
|
@@ -352,6 +380,7 @@ end
|
|
352
380
|
|
353
381
|
## TODO
|
354
382
|
- Test implementations for Hash, AttrAccessor
|
383
|
+
- Decide on how to include state methods in model classes
|
355
384
|
- Thread safety spec!
|
356
385
|
- Spec locks?
|
357
386
|
- Explain locking in readme!
|
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"
|
@@ -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
|
@@ -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
|
+
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
|
@@ -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, :transitions, :events, :options, :callbacks, :name, :error_callback_registry
|
18
|
+
attr_reader :class_context, :transitions, :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)
|
@@ -116,6 +117,10 @@ module NxtStateMachine
|
|
116
117
|
callbacks.register(from, to, :after, run, block)
|
117
118
|
end
|
118
119
|
|
120
|
+
def defuse(errors = [], from:, to:)
|
121
|
+
defuse_registry.register(from, to, errors)
|
122
|
+
end
|
123
|
+
|
119
124
|
def on_error(error = StandardError, from:, to:, run: nil, &block)
|
120
125
|
error_callback_registry.register(from, to, error, run, block)
|
121
126
|
end
|
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.5
|
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-02-
|
14
|
+
date: 2020-02-14 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
@@ -149,6 +149,7 @@ files:
|
|
149
149
|
- lib/nxt_state_machine.rb
|
150
150
|
- lib/nxt_state_machine/callable.rb
|
151
151
|
- lib/nxt_state_machine/callback_registry.rb
|
152
|
+
- lib/nxt_state_machine/defuse_registry.rb
|
152
153
|
- lib/nxt_state_machine/error_callback_registry.rb
|
153
154
|
- lib/nxt_state_machine/errors/error.rb
|
154
155
|
- lib/nxt_state_machine/errors/event_already_registered.rb
|