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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c035f94c9cd82caa184fde21b45cba13ad4791b655a34f8cb5140898f7e0760a
4
- data.tar.gz: a7237abef06bf937730f6f848a57e276c82dbf6d605f6a34889303402c498655
3
+ metadata.gz: 7e66b8a54f30382001a2c1292ac799c92335cb55835e051efb2a3d450409d67f
4
+ data.tar.gz: 255c9799a3732ea5ffa3b87048c5d9b6a3f185b43d1d24fb047ceefef89a9514
5
5
  SHA512:
6
- metadata.gz: 67822ebb580c7398d81e6be7c228fc5de573e3ec757843a8f501ea10e7926ae14b5194ce994e79927a5557bc4bbb200f1d05e9081c470b7ac9a9d202a4cc2f21
7
- data.tar.gz: 384e7c4e66c21a3b7fe6a043bc1b8936f6061605a4de0d7788f003b9982f96ecb709954c3988165936a3dad0bcfb82c5c781bc0815070ca9ad43a8d46ba1d354
6
+ metadata.gz: dcc9b059691980d6121fd97c2f5814bff20e717054d2c7d4b4e7e678c5b7e299906c4310ac46ebe6a11ac55b443a31248e289fd8b9858e7dbc2247341e981a83
7
+ data.tar.gz: 4eb4a0fdfbb88b5014c021279a89cff8638f0e01874b15da7bb45718f0de2c3050d1676ed722e6f0d537885669c43bc32c4bcfdda19319e6388325b1055dd394
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nxt_state_machine (0.1.4)
4
+ nxt_state_machine (0.1.5)
5
5
  activesupport
6
6
  nxt_registry (~> 0.1.3)
7
7
 
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!
@@ -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
@@ -23,6 +23,7 @@ module NxtStateMachine
23
23
  :any_state,
24
24
  :all_states,
25
25
  :all_states_except,
26
+ :defuse,
26
27
  to: :state_machine
27
28
 
28
29
  def transitions(from:, to:, &block)
@@ -21,34 +21,21 @@ module NxtStateMachine
21
21
  end
22
22
 
23
23
  machine.set_state_with do |target, transition|
24
- target.with_lock do
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.with_lock do
43
- transition.run_before_callbacks
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
- result
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, method)
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(method) || halt_transition
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
@@ -1,3 +1,3 @@
1
1
  module NxtStateMachine
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.5"
3
3
  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
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-07 00:00:00.000000000 Z
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