statesman 9.0.1 → 10.0.0

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: 145d2a6b60abe5fb64e8010676cb63b62ba1869daa4343eb538bdf1d261c885e
4
- data.tar.gz: cbcc8f735327409a89a2b238fefdfb6ef4b9e9595b983f7a648df800ca8c2aed
3
+ metadata.gz: 172b3bbeecd2cbd2df8fc9a5485c548c0d729cf0994fda1771d25c8e44f41877
4
+ data.tar.gz: 3556cc13e2433e920ce9bd275bce155a095f6a0e64547a5ff003e0f5fa4dfc74
5
5
  SHA512:
6
- metadata.gz: 9eeef7b26150628a370117dccbcde1e40bc1dbd15957fca96f18232e167b5bcca58a42f47a2718a6927bfcb08449b4f7515fa48ef68ed281f79e12fad96d40f2
7
- data.tar.gz: b793c7af514ce22564326d697880179caceadff2bebc6c03b02aa5a523e040ffd17e5dff8475b9fad7ed567f6f0f8849fe97ddb6745a7b3d86cd7aceb71e0264
6
+ metadata.gz: 21f91dd2537c3e5e480dc0f5adb592ebbeaef9974cfbede1d94aa8b6946c26e9394199029e0281372a2ec4452e1173cb5cba86f0bf1507de19677c398df20350
7
+ data.tar.gz: 58ee880508df011be1caea1f85a475760cf79e07c1ad60f241f4ef4146983997560f17fe1d3a93f5856c6e519831d71eae6c4bd8f7e70f6d0d4ce49253f1ac3f
data/.circleci/config.yml CHANGED
@@ -46,15 +46,15 @@ references:
46
46
  path: /tmp/circle_artifacts/
47
47
 
48
48
  ruby_versions: &ruby_versions
49
- - "2.5"
50
- - "2.6"
51
49
  - "2.7"
52
50
  - "3.0"
51
+ - "3.1"
53
52
 
54
53
  rails_versions: &rails_versions
55
- - "5.2.6"
54
+ - "5.2.7"
56
55
  - "6.0.4"
57
- - "6.1.4"
56
+ - "6.1.5"
57
+ - "7.0.2"
58
58
  - "main"
59
59
 
60
60
  mysql_versions: &mysql_versions
data/.gitignore CHANGED
@@ -1,18 +1,71 @@
1
1
  *.gem
2
2
  *.rbc
3
- .bundle
4
- .config
5
- .rspec
6
- .yardoc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
7
48
  Gemfile.lock
8
- InstalledFiles
9
- _yardoc
10
- coverage
11
- doc/
12
- lib/bundler/man
13
- pkg
14
- rdoc
15
- spec/reports
16
- test/tmp
17
- test/version_tmp
18
- tmp
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
57
+
58
+ # Project-specific ignores
59
+ .rspec
60
+
61
+ # VSCode
62
+ .vscode
63
+
64
+ # Local History for Visual Studio Code
65
+ .history/
66
+
67
+ # Built Visual Studio Code Extensions
68
+ *.vsix
69
+
70
+ # JetBrains
71
+ .idea
data/.rubocop.yml CHANGED
@@ -5,3 +5,12 @@ inherit_gem:
5
5
 
6
6
  AllCops:
7
7
  TargetRubyVersion: 3.0
8
+
9
+ Metrics/AbcSize:
10
+ Max: 60
11
+
12
+ Metrics/CyclomaticComplexity:
13
+ Max: 10
14
+
15
+ Metrics/PerceivedComplexity:
16
+ Max: 11
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## v10.0.0 17th May 2022
2
+
3
+ ### Changed
4
+ - Added support for Ruby 3.1 [#462](https://github.com/gocardless/statesman/pull/462)
5
+ - Removed support for Ruby 2.5 and 2.6 [#462](https://github.com/gocardless/statesman/pull/462)
6
+ - Added `remove_state` and `remove_transitions` methods to `Statesman::Machine` [#464](https://github.com/gocardless/statesman/pull/464)
7
+
1
8
  ## v9.0.1 4th February 2021
2
9
 
3
10
  ### Changed
data/README.md CHANGED
@@ -30,7 +30,7 @@ protection.
30
30
  To get started, just add Statesman to your `Gemfile`, and then run `bundle`:
31
31
 
32
32
  ```ruby
33
- gem 'statesman', '~> 8.0.3'
33
+ gem 'statesman', '~> 10.0.0'
34
34
  ```
35
35
 
36
36
  ## Usage
@@ -116,6 +116,52 @@ Order.in_state(:cancelled) # => [#<Order id: "123">]
116
116
  Order.not_in_state(:checking_out) # => [#<Order id: "123">]
117
117
  ```
118
118
 
119
+ If you'd like, you can also define a template for a generic state machine, then alter classes which extend it as required:
120
+
121
+ ```ruby
122
+ module Template
123
+ def define_states
124
+ state :a, initial: true
125
+ state :b
126
+ state :c
127
+ end
128
+
129
+ def define_transitions
130
+ transition from: :a, to: :b
131
+ transition from: :b, to: :c
132
+ transition from: :c, to: :a
133
+ end
134
+ end
135
+
136
+ class Circular
137
+ include Statesman::Machine
138
+ extend Template
139
+
140
+ define_states
141
+ define_transitions
142
+ end
143
+
144
+ class Linear
145
+ include Statesman::Machine
146
+ extend Template
147
+
148
+ define_states
149
+ define_transitions
150
+
151
+ remove_transitions from: :c, to: :a
152
+ end
153
+
154
+ class Shorter
155
+ include Statesman::Machine
156
+ extend Template
157
+
158
+ define_states
159
+ define_transitions
160
+
161
+ remove_state :c
162
+ end
163
+ ```
164
+
119
165
  ## Persistence
120
166
 
121
167
  By default Statesman stores transition history in memory only. It can be
@@ -42,6 +42,17 @@ module Statesman
42
42
  states << name
43
43
  end
44
44
 
45
+ def remove_state(state_name)
46
+ state_name = state_name.to_s
47
+
48
+ remove_transitions(from: state_name)
49
+ remove_transitions(to: state_name)
50
+ remove_callbacks(from: state_name)
51
+ remove_callbacks(to: state_name)
52
+
53
+ @states.delete(state_name.to_s)
54
+ end
55
+
45
56
  def successors
46
57
  @successors ||= {}
47
58
  end
@@ -70,6 +81,20 @@ module Statesman
70
81
  successors[from] += to
71
82
  end
72
83
 
84
+ def remove_transitions(from: nil, to: nil)
85
+ raise ArgumentError, "Both from and to can't be nil!" if from.nil? && to.nil?
86
+ return if successors.nil?
87
+
88
+ if from.present?
89
+ @successors[from.to_s].delete(to.to_s) if to.present?
90
+ @successors.delete(from.to_s) if to.nil? || successors[from.to_s].empty?
91
+ elsif to.present?
92
+ @successors.
93
+ transform_values! { |to_states| to_states - [to.to_s] }.
94
+ filter! { |_from_state, to_states| to_states.any? }
95
+ end
96
+ end
97
+
73
98
  def before_transition(options = {}, &block)
74
99
  add_callback(callback_type: :before, callback_class: Callback,
75
100
  from: options[:from], to: options[:to], &block)
@@ -151,6 +176,33 @@ module Statesman
151
176
  callback_class.new(from: from, to: to, callback: block)
152
177
  end
153
178
 
179
+ def remove_callbacks(from: nil, to: nil)
180
+ raise ArgumentError, "Both from and to can't be nil!" if from.nil? && to.nil?
181
+ return if callbacks.nil?
182
+
183
+ @callbacks.transform_values! do |callbacks|
184
+ filter_callbacks(callbacks, from: from, to: to)
185
+ end
186
+ end
187
+
188
+ def filter_callbacks(callbacks, from: nil, to: nil)
189
+ callbacks.filter_map do |callback|
190
+ next if callback.from == from && to.nil?
191
+
192
+ if callback.to.include?(to) && (from.nil? || callback.from == from)
193
+ next if callback.to == [to]
194
+
195
+ callback = Statesman::Callback.new({
196
+ from: callback.from,
197
+ to: callback.to - [to],
198
+ callback: callback.callback,
199
+ })
200
+ end
201
+
202
+ callback
203
+ end
204
+ end
205
+
154
206
  def validate_callback_type_and_class(callback_type, callback_class)
155
207
  raise ArgumentError, "missing keyword: callback_type" if callback_type.nil?
156
208
  raise ArgumentError, "missing keyword: callback_class" if callback_class.nil?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Statesman
4
- VERSION = "9.0.1"
4
+ VERSION = "10.0.0"
5
5
  end
@@ -27,6 +27,112 @@ describe Statesman::Machine do
27
27
  end
28
28
  end
29
29
 
30
+ describe ".remove_state" do
31
+ subject(:remove_state) { -> { machine.remove_state(:x) } }
32
+
33
+ before do
34
+ machine.class_eval do
35
+ state :x
36
+ state :y
37
+ state :z
38
+ end
39
+ end
40
+
41
+ it "removes the state" do
42
+ expect(remove_state).
43
+ to change(machine, :states).
44
+ from(match_array(%w[x y z])).
45
+ to(%w[y z])
46
+ end
47
+
48
+ context "with a transition from the removed state" do
49
+ before { machine.transition from: :x, to: :y }
50
+
51
+ it "removes the transition" do
52
+ expect(remove_state).
53
+ to change(machine, :successors).
54
+ from({ "x" => ["y"] }).
55
+ to({})
56
+ end
57
+
58
+ context "with multiple transitions" do
59
+ before { machine.transition from: :x, to: :z }
60
+
61
+ it "removes all transitions" do
62
+ expect(remove_state).
63
+ to change(machine, :successors).
64
+ from({ "x" => %w[y z] }).
65
+ to({})
66
+ end
67
+ end
68
+ end
69
+
70
+ context "with a transition to the removed state" do
71
+ before { machine.transition from: :y, to: :x }
72
+
73
+ it "removes the transition" do
74
+ expect(remove_state).
75
+ to change(machine, :successors).
76
+ from({ "y" => ["x"] }).
77
+ to({})
78
+ end
79
+
80
+ context "with multiple transitions" do
81
+ before { machine.transition from: :z, to: :x }
82
+
83
+ it "removes all transitions" do
84
+ expect(remove_state).
85
+ to change(machine, :successors).
86
+ from({ "y" => ["x"], "z" => ["x"] }).
87
+ to({})
88
+ end
89
+ end
90
+ end
91
+
92
+ context "with a callback from the removed state" do
93
+ before do
94
+ machine.class_eval do
95
+ transition from: :x, to: :y
96
+ transition from: :x, to: :z
97
+ guard_transition(from: :x) { return false }
98
+ guard_transition(from: :x, to: :z) { return true }
99
+ end
100
+ end
101
+
102
+ let(:guards) do
103
+ [having_attributes(from: "x", to: []), having_attributes(from: "x", to: ["z"])]
104
+ end
105
+
106
+ it "removes the guard" do
107
+ expect(remove_state).
108
+ to change(machine, :callbacks).
109
+ from(a_hash_including(guards: match_array(guards))).
110
+ to(a_hash_including(guards: []))
111
+ end
112
+ end
113
+
114
+ context "with a callback to the removed state" do
115
+ before do
116
+ machine.class_eval do
117
+ transition from: :y, to: :x
118
+ guard_transition(to: :x) { return false }
119
+ guard_transition(from: :y, to: :x) { return true }
120
+ end
121
+ end
122
+
123
+ let(:guards) do
124
+ [having_attributes(from: nil, to: ["x"]), having_attributes(from: "y", to: ["x"])]
125
+ end
126
+
127
+ it "removes the guard" do
128
+ expect(remove_state).
129
+ to change(machine, :callbacks).
130
+ from(a_hash_including(guards: match_array(guards))).
131
+ to(a_hash_including(guards: []))
132
+ end
133
+ end
134
+ end
135
+
30
136
  describe ".retry_conflicts" do
31
137
  subject(:transition_state) do
32
138
  described_class.retry_conflicts(retry_attempts) do
@@ -170,6 +276,42 @@ describe Statesman::Machine do
170
276
  end
171
277
  end
172
278
 
279
+ describe ".remove_transitions" do
280
+ before do
281
+ machine.class_eval do
282
+ state :x
283
+ state :y
284
+ state :z
285
+ transition from: :x, to: :y
286
+ transition from: :x, to: :z
287
+ transition from: :y, to: :z
288
+ end
289
+ end
290
+
291
+ let(:initial_successors) { { "x" => %w[y z], "y" => ["z"] } }
292
+
293
+ it "removes the correct transitions when given a from state" do
294
+ expect { machine.remove_transitions(from: :x) }.
295
+ to change(machine, :successors).
296
+ from(initial_successors).
297
+ to({ "y" => ["z"] })
298
+ end
299
+
300
+ it "removes the correct transitions when given a to state" do
301
+ expect { machine.remove_transitions(to: :z) }.
302
+ to change(machine, :successors).
303
+ from(initial_successors).
304
+ to({ "x" => ["y"] })
305
+ end
306
+
307
+ it "removes the correct transitions when given a from and to state" do
308
+ expect { machine.remove_transitions(from: :x, to: :z) }.
309
+ to change(machine, :successors).
310
+ from(initial_successors).
311
+ to({ "x" => ["y"], "y" => ["z"] })
312
+ end
313
+ end
314
+
173
315
  describe ".validate_callback_condition" do
174
316
  before do
175
317
  machine.class_eval do
data/statesman.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.required_ruby_version = ">= 2.5"
24
+ spec.required_ruby_version = ">= 2.7"
25
25
 
26
26
  spec.add_development_dependency "ammeter", "~> 1.1"
27
27
  spec.add_development_dependency "bundler", "~> 2"
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency "rake", "~> 13.0.0"
34
34
  spec.add_development_dependency "rspec", "~> 3.1"
35
35
  spec.add_development_dependency "rspec-its", "~> 1.1"
36
- spec.add_development_dependency "rspec_junit_formatter", "~> 0.4.0"
36
+ spec.add_development_dependency "rspec_junit_formatter", "~> 0.5.1"
37
37
  spec.add_development_dependency "rspec-rails", "~> 3.1"
38
38
  spec.add_development_dependency "sqlite3", "~> 1.4.2"
39
39
  spec.add_development_dependency "timecop", "~> 0.9.1"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statesman
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.0.1
4
+ version: 10.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GoCardless
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-04 00:00:00.000000000 Z
11
+ date: 2022-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ammeter
@@ -168,14 +168,14 @@ dependencies:
168
168
  requirements:
169
169
  - - "~>"
170
170
  - !ruby/object:Gem::Version
171
- version: 0.4.0
171
+ version: 0.5.1
172
172
  type: :development
173
173
  prerelease: false
174
174
  version_requirements: !ruby/object:Gem::Requirement
175
175
  requirements:
176
176
  - - "~>"
177
177
  - !ruby/object:Gem::Version
178
- version: 0.4.0
178
+ version: 0.5.1
179
179
  - !ruby/object:Gem::Dependency
180
180
  name: rspec-rails
181
181
  requirement: !ruby/object:Gem::Requirement
@@ -298,7 +298,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
298
298
  requirements:
299
299
  - - ">="
300
300
  - !ruby/object:Gem::Version
301
- version: '2.5'
301
+ version: '2.7'
302
302
  required_rubygems_version: !ruby/object:Gem::Requirement
303
303
  requirements:
304
304
  - - ">="