statesman 9.0.1 → 10.0.0

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: 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
  - - ">="