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 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