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 +4 -4
- data/.circleci/config.yml +4 -4
- data/.gitignore +68 -15
- data/.rubocop.yml +9 -0
- data/CHANGELOG.md +7 -0
- data/README.md +47 -1
- data/lib/statesman/machine.rb +52 -0
- data/lib/statesman/version.rb +1 -1
- data/spec/statesman/machine_spec.rb +142 -0
- data/statesman.gemspec +2 -2
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 172b3bbeecd2cbd2df8fc9a5485c548c0d729cf0994fda1771d25c8e44f41877
|
4
|
+
data.tar.gz: 3556cc13e2433e920ce9bd275bce155a095f6a0e64547a5ff003e0f5fa4dfc74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
54
|
+
- "5.2.7"
|
56
55
|
- "6.0.4"
|
57
|
-
- "6.1.
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
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', '~>
|
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
|
data/lib/statesman/machine.rb
CHANGED
@@ -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?
|
data/lib/statesman/version.rb
CHANGED
@@ -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.
|
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.
|
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:
|
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-
|
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.
|
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.
|
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.
|
301
|
+
version: '2.7'
|
302
302
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
303
303
|
requirements:
|
304
304
|
- - ">="
|