nxt_state_machine 0.1.8 → 0.1.9

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: 89b1ad3a07fcc60e4b3541b770f1e1f287713e973d3a038f3496be0a145006c5
4
- data.tar.gz: f213f914ffe2ff39a0d90d8c61f574857b312c9065e1241e9e09dce16cf6cc3a
3
+ metadata.gz: 6128e445380f2ed2b836b4c23ed58556efd2ff0728add08c2cbd45e97812e068
4
+ data.tar.gz: 05c8861e99afefec3c654366050b225d7a93c10ff984e508cd14d6345b1d0ff3
5
5
  SHA512:
6
- metadata.gz: 48b17f521db0a19ccc500fff321b459f4e962875792e047923185a45691c20b81bfec5bbab03b77cf6eaeb10be2b8191ec73bfbc4d4b05c9e518389b1d9cc497
7
- data.tar.gz: 286382b6dd9512abc0a0b1c1f177196edab5aced50bc8888d0febf4a58a86697ac152b8a7617c238f3268cbbada15f9767248b333264cf1608421d6a9b9c2b93
6
+ metadata.gz: d3038d32da3a7bfa5a49b4537e7c6f163259ddd0f3d2b48a05bfa838021350f648d75bfc493125529c9e1374dc34aa2c9abe94325790fe4ba82a09f01e2afdc8
7
+ data.tar.gz: 64178102f125d3b180f31b1a0bbebd766226b44351bbef906d907cc6339f2ff213c743c51b52238706827cfd597e410b828eb0824f8a4747f363c9b2b79a0ce9
@@ -1,3 +1,12 @@
1
+ # v0.1.9 2020-09-23
2
+
3
+ ### Added
4
+
5
+ - Allow to toggle locking of transitions for active record adapter per event or globally
6
+
7
+ [Compare v0.1.8...v0.1.9](https://github.com/nxt-insurance/nxt_state_machine/compare/v0.1.8...v0.1.9)
8
+
9
+
1
10
  # v0.1.8 2020-0-05
2
11
 
3
12
  ### Added
@@ -1,32 +1,32 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nxt_state_machine (0.1.8)
4
+ nxt_state_machine (0.1.9)
5
5
  activesupport
6
- nxt_registry (~> 0.1.3)
6
+ nxt_registry (~> 0.3.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
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)
11
+ activemodel (6.0.3.3)
12
+ activesupport (= 6.0.3.3)
13
+ activerecord (6.0.3.3)
14
+ activemodel (= 6.0.3.3)
15
+ activesupport (= 6.0.3.3)
16
+ activesupport (6.0.3.3)
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
21
  zeitwerk (~> 2.2, >= 2.2.2)
22
- coderay (1.1.2)
23
- concurrent-ruby (1.1.6)
24
- diff-lcs (1.3)
25
- i18n (1.8.2)
22
+ coderay (1.1.3)
23
+ concurrent-ruby (1.1.7)
24
+ diff-lcs (1.4.4)
25
+ i18n (1.8.5)
26
26
  concurrent-ruby (~> 1.0)
27
27
  method_source (1.0.0)
28
- minitest (5.14.1)
29
- nxt_registry (0.1.5)
28
+ minitest (5.14.2)
29
+ nxt_registry (0.3.2)
30
30
  activesupport
31
31
  pry (0.13.1)
32
32
  coderay (~> 1.1)
@@ -37,15 +37,15 @@ GEM
37
37
  rspec-core (~> 3.9.0)
38
38
  rspec-expectations (~> 3.9.0)
39
39
  rspec-mocks (~> 3.9.0)
40
- rspec-core (3.9.0)
41
- rspec-support (~> 3.9.0)
42
- rspec-expectations (3.9.0)
40
+ rspec-core (3.9.2)
41
+ rspec-support (~> 3.9.3)
42
+ rspec-expectations (3.9.2)
43
43
  diff-lcs (>= 1.2.0, < 2.0)
44
44
  rspec-support (~> 3.9.0)
45
- rspec-mocks (3.9.0)
45
+ rspec-mocks (3.9.1)
46
46
  diff-lcs (>= 1.2.0, < 2.0)
47
47
  rspec-support (~> 3.9.0)
48
- rspec-support (3.9.0)
48
+ rspec-support (3.9.3)
49
49
  rspec_junit_formatter (0.4.1)
50
50
  rspec-core (>= 2, < 4, != 2.12.0)
51
51
  ruby-graphviz (1.2.5)
@@ -54,7 +54,7 @@ GEM
54
54
  thread_safe (0.3.6)
55
55
  tzinfo (1.2.7)
56
56
  thread_safe (~> 0.1)
57
- zeitwerk (2.3.0)
57
+ zeitwerk (2.4.0)
58
58
 
59
59
  PLATFORMS
60
60
  ruby
data/README.md CHANGED
@@ -241,12 +241,14 @@ article.approve(approved_at: Time.current)
241
241
  article.approve!(approved_at: Time.current)
242
242
  ```
243
243
 
244
- **NOTE:** Transitions run in transactions that acquire a lock to prevent concurrency issues. Transactions will be
245
- rolled back in case of an exception or if your target cannot be saved due to validation errors.
246
- The state is set back to the state before the transition! If you try to transitions on records with unpersisted changes
244
+ **NOTE:** Transitions run in transactions that acquire a lock to prevent concurrency issues per default.
245
+ Transactions will be rolled back in case of an exception or if your target cannot be saved due to validation errors.
246
+ The state is set back to the state before the transition! If you try to transition on records with unpersisted changes
247
247
  you will get a `RuntimeError: Locking a record with unpersisted changes is not supported.` error saying something
248
248
  like `Use :save to persist the changes, or :reload to discard them explicitly.` since it's not possible to acquire a
249
- lock on modified records.
249
+ lock on modified records. You can also switch of locking and transactions for events by passing in the `lock_transitions: false`
250
+ option when defining an event or globally on the state machine with the `lock_transitions: false` option. Currently
251
+ there is no option to toggle locking at runtime.
250
252
 
251
253
  ### Transitions
252
254
 
@@ -342,7 +344,10 @@ You want to break out of your transition (which is wrapped inside a lock)?
342
344
  You can raise an error, have everything rolled back and then have your error handler take over.
343
345
  **NOTE:** Unless you reload your model all assignments you did, previous to the error, should still be available in your
344
346
  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.
347
+ transition and you can actually persist changes to your model before the defused error is raised and handled. You can
348
+ also switch off locking (and transactions) for events by passing the `lock_transitions: false` option when defining an event. This
349
+ can also by set globally for a state_machine by passing the `lock_transitions: false` option when setting up the state
350
+ machine.
346
351
 
347
352
  ```ruby
348
353
  state_machine do
@@ -360,6 +365,14 @@ state_machine do
360
365
  raise CustomError, 'This does not rollback the headline update above'
361
366
  end
362
367
  end
368
+
369
+ event :approve_without_lock, lock_transitions: false do
370
+ transition from: %i[written submitted deleted], to: :approved do |headline:|
371
+ # This will be saved to the database because the event does not wrap the transition in a transaction
372
+ article.update!(headline: headline)
373
+ raise StandardError, 'This does not rollback the headline update above'
374
+ end
375
+ end
363
376
 
364
377
  on_error! CustomError from: any_state, to: :approved do |error, transition|
365
378
  # You can still handle the defused Error if you want to
@@ -25,7 +25,6 @@ require "nxt_state_machine/transition/interface"
25
25
  require "nxt_state_machine/transition"
26
26
  require "nxt_state_machine/transition/factory"
27
27
  require "nxt_state_machine/transition/proxy"
28
- require "nxt_state_machine/transition/store"
29
28
  require "nxt_state_machine/transition/around_callback_chain"
30
29
  require "nxt_state_machine/state_machine"
31
30
  require "nxt_state_machine/integrations/active_record"
@@ -38,7 +37,7 @@ module NxtStateMachine
38
37
  include NxtRegistry
39
38
 
40
39
  def state_machine(name = :default, **opts, &config)
41
- state_machines.resolve!(name) || state_machines.register(
40
+ state_machines.resolve(name) || state_machines.register(
42
41
  name,
43
42
  StateMachine.new(name, self, state_machine_event_registry, **opts).configure(&config)
44
43
  )
@@ -76,15 +75,15 @@ module NxtStateMachine
76
75
  end
77
76
 
78
77
  def state_machine(name = :default)
79
- @state_machine ||= self.class.state_machines.resolve(name)
78
+ @state_machine ||= self.class.state_machines.resolve!(name)
80
79
  end
81
80
 
82
81
  def current_state_name(name = :default)
83
- state_machines.resolve(name).current_state_name(self)
82
+ state_machines.resolve!(name).current_state_name(self)
84
83
  end
85
84
 
86
85
  def current_state(name = :default)
87
- state_machines.resolve(name).states.resolve(current_state_name(name))
86
+ state_machines.resolve!(name).states.resolve!(current_state_name(name))
88
87
  end
89
88
 
90
89
  def halt_transition(*args, **opts)
@@ -8,13 +8,13 @@ module NxtStateMachine
8
8
 
9
9
  Array(from).each do |from_state|
10
10
  Array(to).each do |to_state|
11
- callbacks.from(from_state).to(to_state).kind(kind) << method_or_block
11
+ callbacks.from!(from_state).to!(to_state).kind!(kind) << method_or_block
12
12
  end
13
13
  end
14
14
  end
15
15
 
16
- def resolve(transition, kind = nil)
17
- all_callbacks = callbacks.from(transition.from.enum).to(transition.to.enum)
16
+ def resolve!(transition, kind = nil)
17
+ all_callbacks = callbacks.from!(transition.from.enum).to!(transition.to.enum)
18
18
  return all_callbacks unless kind
19
19
 
20
20
  all_callbacks.kind(kind)
@@ -24,8 +24,8 @@ module NxtStateMachine
24
24
 
25
25
  def callbacks
26
26
  @callbacks ||= registry :from do
27
- nested :to do
28
- nested :kind, default: -> { [] } do
27
+ level :to do
28
+ level :kind, default: -> { [] } do
29
29
  attrs :before, :after
30
30
  end
31
31
  end
@@ -11,15 +11,15 @@ module NxtStateMachine
11
11
  end
12
12
  end
13
13
 
14
- def resolve(transition)
15
- errors.from(transition.from.enum).to(transition.to.enum)
14
+ def resolve!(transition)
15
+ errors.from!(transition.from.enum).to!(transition.to.enum)
16
16
  end
17
17
 
18
18
  private
19
19
 
20
20
  def errors
21
21
  @errors ||= registry :from do
22
- nested :to, default: -> { [] }
22
+ level :to, default: -> { [] }
23
23
  end
24
24
  end
25
25
  end
@@ -29,8 +29,8 @@ module NxtStateMachine
29
29
 
30
30
  def callbacks
31
31
  @callbacks ||= registry :from do
32
- nested :to do
33
- nested :error, transform_keys: false, call: false
32
+ level :to do
33
+ level :error, transform_keys: false, call: false
34
34
  end
35
35
  end
36
36
  end
@@ -7,13 +7,14 @@ module NxtStateMachine
7
7
  @name = name
8
8
  @event_transitions = registry("#{name} event transitions")
9
9
  @names = Event::Names.build(name)
10
+ @options = options.with_indifferent_access
10
11
 
11
12
  configure(&block)
12
13
 
13
14
  ensure_event_has_transitions
14
15
  end
15
16
 
16
- attr_reader :name, :state_machine, :event_transitions, :names
17
+ attr_reader :name, :state_machine, :event_transitions, :names, :options
17
18
 
18
19
  delegate :before_transition,
19
20
  :after_transition,
@@ -38,7 +39,7 @@ module NxtStateMachine
38
39
  alias_method :transition, :transitions
39
40
 
40
41
  def transitions_from?(state)
41
- event_transitions.resolve!(state).present?
42
+ event_transitions.resolve(state).present?
42
43
  end
43
44
 
44
45
  def to_s
@@ -38,7 +38,6 @@ module NxtStateMachine
38
38
  end
39
39
 
40
40
  def add_nodes(state_machine)
41
- binding.pry
42
41
  state_machine.states.values.each do |state|
43
42
  add_node(state)
44
43
  end
@@ -1,11 +1,12 @@
1
1
  module NxtStateMachine
2
2
  module ActiveRecord
3
3
  module ClassMethods
4
- def state_machine(name = :default, state_attr: :state, target: nil, &config)
4
+ def state_machine(name = :default, state_attr: :state, target: nil, lock_transitions: true, &config)
5
5
  machine = super(
6
6
  name,
7
7
  state_attr: state_attr,
8
8
  target: target,
9
+ lock_transitions: lock_transitions,
9
10
  &config
10
11
  )
11
12
 
@@ -49,14 +50,14 @@ module NxtStateMachine
49
50
  result = nil
50
51
  defused_error = nil
51
52
 
52
- target.with_lock do
53
+ with_conditional_lock(target, transition.event) do
53
54
  transition.run_before_callbacks
54
55
  result = execute_transition(target, transition, state_attr, save_with_method)
55
56
  transition.run_after_callbacks
56
57
 
57
58
  result
58
59
  rescue StandardError => error
59
- if machine.defuse_registry.resolve(transition).find { |error_class| error.is_a?(error_class) }
60
+ if machine.defuse_registry.resolve!(transition).find { |error_class| error.is_a?(error_class) }
60
61
  defused_error = error
61
62
  else
62
63
  raise error
@@ -82,6 +83,16 @@ module NxtStateMachine
82
83
  block ? result : set_state_result
83
84
  end
84
85
  end
86
+
87
+ def with_conditional_lock(target, event, &block)
88
+ return block.call unless lock_transition?(event)
89
+
90
+ target.with_lock { block.call }
91
+ end
92
+
93
+ def lock_transition?(event)
94
+ event.options.fetch(:lock_transitions) { state_machine.options.fetch(:lock_transitions) }
95
+ end
85
96
  end
86
97
 
87
98
  def self.included(base)
@@ -6,7 +6,7 @@ module NxtStateMachine
6
6
  @options = opts
7
7
 
8
8
  @states = NxtStateMachine::StateRegistry.new
9
- @transitions = Transition::Store.new
9
+ @transitions = []
10
10
  @events = event_registry
11
11
  @callbacks = CallbackRegistry.new
12
12
  @error_callback_registry = ErrorCallbackRegistry.new
@@ -89,9 +89,9 @@ module NxtStateMachine
89
89
  Event::Names.set_state_method_map(name).each do |event_name, set_state_method|
90
90
  class_context.define_method event_name do |*args, **opts|
91
91
  event.state_machine.can_transition!(name, event.state_machine.current_state_name(self))
92
- transition = event.event_transitions.resolve(event.state_machine.current_state_name(self))
92
+ transition = event.event_transitions.resolve!(event.state_machine.current_state_name(self))
93
93
  # Transition is build every time and thus should be thread safe!
94
- transition.build_transition(event_name, self, set_state_method, *args, **opts)
94
+ transition.build_transition(event, self, set_state_method, *args, **opts)
95
95
  end
96
96
  end
97
97
 
@@ -101,7 +101,7 @@ module NxtStateMachine
101
101
  end
102
102
 
103
103
  def can_transition?(event_name, from)
104
- event = events.resolve(event_name)
104
+ event = events.resolve!(event_name)
105
105
  event && event.event_transitions.key?(from)
106
106
  end
107
107
 
@@ -161,7 +161,7 @@ module NxtStateMachine
161
161
  end
162
162
 
163
163
  def run_callbacks(transition, kind, context)
164
- current_callbacks = callbacks.resolve(transition, kind)
164
+ current_callbacks = callbacks.resolve!(transition, kind)
165
165
  return unless current_callbacks.any?
166
166
 
167
167
  current_callbacks.each do |callback|
@@ -5,8 +5,8 @@ module NxtStateMachine
5
5
  def initialize(name, event:, from:, to:, state_machine:, context:, set_state_method:, &block)
6
6
  @name = name
7
7
  @event = event
8
- @from = state_machine.states.resolve(from)
9
- @to = state_machine.states.resolve(to)
8
+ @from = state_machine.states.resolve!(from)
9
+ @to = state_machine.states.resolve!(to)
10
10
  @state_machine = state_machine
11
11
  @set_state_method = set_state_method
12
12
  @context = context
@@ -18,7 +18,7 @@ module NxtStateMachine
18
18
  private
19
19
 
20
20
  def callbacks
21
- @callbacks ||= state_machine.callbacks.resolve(transition).kind(:around)
21
+ @callbacks ||= state_machine.callbacks.resolve!(transition).kind(:around)
22
22
  end
23
23
 
24
24
  attr_reader :transition, :context, :state_machine
@@ -4,8 +4,8 @@ module NxtStateMachine
4
4
 
5
5
  def initialize(name, from:, to:, state_machine:, &block)
6
6
  @name = name
7
- @from = state_machine.states.resolve(from)
8
- @to = state_machine.states.resolve(to)
7
+ @from = state_machine.states.resolve!(from)
8
+ @to = state_machine.states.resolve!(to)
9
9
  @state_machine = state_machine
10
10
  @block = block
11
11
 
@@ -28,7 +28,7 @@ module NxtStateMachine
28
28
  set_state_method: set_state_method
29
29
  }
30
30
 
31
- transition = Transition.new(name, **options)
31
+ transition = Transition.new(event.name, **options)
32
32
 
33
33
  if block
34
34
  # if the transition takes a block we make it available through a proxy on the transition itself!
@@ -1,3 +1,3 @@
1
1
  module NxtStateMachine
2
- VERSION = "0.1.8"
2
+ VERSION = '0.1.9'
3
3
  end
@@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.require_paths = ["lib"]
36
36
 
37
37
  spec.add_dependency "activesupport"
38
- spec.add_dependency "nxt_registry", "~> 0.1.3"
38
+ spec.add_dependency "nxt_registry", "~> 0.3.0"
39
39
 
40
40
  spec.add_development_dependency "bundler", "~> 2.0"
41
41
  spec.add_development_dependency "rake", "~> 12.0"
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.8
4
+ version: 0.1.9
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-06-08 00:00:00.000000000 Z
14
+ date: 2020-09-30 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activesupport
@@ -33,14 +33,14 @@ dependencies:
33
33
  requirements:
34
34
  - - "~>"
35
35
  - !ruby/object:Gem::Version
36
- version: 0.1.3
36
+ version: 0.3.0
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - "~>"
42
42
  - !ruby/object:Gem::Version
43
- version: 0.1.3
43
+ version: 0.3.0
44
44
  - !ruby/object:Gem::Dependency
45
45
  name: bundler
46
46
  requirement: !ruby/object:Gem::Requirement
@@ -193,7 +193,6 @@ files:
193
193
  - lib/nxt_state_machine/transition/factory.rb
194
194
  - lib/nxt_state_machine/transition/interface.rb
195
195
  - lib/nxt_state_machine/transition/proxy.rb
196
- - lib/nxt_state_machine/transition/store.rb
197
196
  - lib/nxt_state_machine/version.rb
198
197
  - lib/railtie.rb
199
198
  - lib/tasks/draw_graph.rake
@@ -1,19 +0,0 @@
1
- module NxtStateMachine
2
- class Transition::Store < Array
3
- def <<(transition)
4
- ensure_transition_unique(transition)
5
- super
6
- end
7
-
8
- alias_method :add, :<<
9
-
10
- private
11
-
12
- def ensure_transition_unique(transition)
13
- return unless find { |other| other.from.enum == transition.from.enum && other.to.enum == transition.to.enum }
14
-
15
- raise NxtStateMachine::Errors::TransitionAlreadyRegistered,
16
- "A transition from :#{transition.from.enum} to :#{transition.to.enum} was already registered"
17
- end
18
- end
19
- end