nxt_state_machine 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|