statesman 7.4.0 → 9.0.1
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 +100 -160
- data/.github/dependabot.yml +7 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +37 -28
- data/.ruby-version +1 -0
- data/CHANGELOG.md +55 -6
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +2 -5
- data/README.md +77 -5
- data/lib/generators/statesman/generator_helpers.rb +10 -2
- data/lib/statesman/adapters/active_record.rb +32 -12
- data/lib/statesman/adapters/active_record_queries.rb +5 -1
- data/lib/statesman/adapters/memory.rb +4 -0
- data/lib/statesman/exceptions.rb +4 -0
- data/lib/statesman/machine.rb +8 -0
- data/lib/statesman/version.rb +1 -1
- data/spec/statesman/adapters/active_record_queries_spec.rb +28 -4
- data/spec/statesman/adapters/active_record_spec.rb +38 -0
- data/spec/statesman/adapters/shared_examples.rb +1 -0
- data/spec/statesman/exceptions_spec.rb +9 -0
- data/spec/statesman/machine_spec.rb +31 -5
- data/spec/support/active_record.rb +11 -6
- data/statesman.gemspec +4 -4
- metadata +16 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 145d2a6b60abe5fb64e8010676cb63b62ba1869daa4343eb538bdf1d261c885e
|
|
4
|
+
data.tar.gz: cbcc8f735327409a89a2b238fefdfb6ef4b9e9595b983f7a648df800ca8c2aed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9eeef7b26150628a370117dccbcde1e40bc1dbd15957fca96f18232e167b5bcca58a42f47a2718a6927bfcb08449b4f7515fa48ef68ed281f79e12fad96d40f2
|
|
7
|
+
data.tar.gz: b793c7af514ce22564326d697880179caceadff2bebc6c03b02aa5a523e040ffd17e5dff8475b9fad7ed567f6f0f8849fe97ddb6745a7b3d86cd7aceb71e0264
|
data/.circleci/config.yml
CHANGED
|
@@ -1,187 +1,127 @@
|
|
|
1
1
|
---
|
|
2
|
-
version: 2
|
|
2
|
+
version: 2.1
|
|
3
3
|
|
|
4
4
|
references:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- run: gem install bundler -v 2.1.4
|
|
16
|
-
|
|
17
|
-
- run: bundle install --path vendor/bundle
|
|
5
|
+
bundle_install: &bundle_install
|
|
6
|
+
run:
|
|
7
|
+
name: Bundle
|
|
8
|
+
command: |
|
|
9
|
+
gem install bundler --no-document && \
|
|
10
|
+
bundle config set no-cache 'true' && \
|
|
11
|
+
bundle config set jobs '4' && \
|
|
12
|
+
bundle config set retry '3' && \
|
|
13
|
+
bundle install
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
cache_bundle: &cache_bundle
|
|
16
|
+
save_cache:
|
|
17
|
+
key: bundle-<< parameters.ruby_version >>-<< parameters.rails_version >>-{{ checksum "statesman.gemspec" }}-{{ checksum "Gemfile" }}
|
|
21
18
|
paths:
|
|
22
19
|
- vendor/bundle
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
restore_bundle: &restore_bundle
|
|
22
|
+
restore_cache:
|
|
23
|
+
key: bundle-<< parameters.ruby_version >>-<< parameters.rails_version >>-{{ checksum "statesman.gemspec" }}-{{ checksum "Gemfile" }}
|
|
25
24
|
|
|
25
|
+
steps: &steps
|
|
26
|
+
- add_ssh_keys
|
|
27
|
+
- checkout
|
|
28
|
+
- run:
|
|
29
|
+
name: "Add dependencies"
|
|
30
|
+
command: |
|
|
31
|
+
sudo apt-get update && sudo apt-get install -y sqlite3 libsqlite3-dev
|
|
32
|
+
- *restore_bundle
|
|
33
|
+
- *bundle_install
|
|
34
|
+
- *cache_bundle
|
|
26
35
|
- run: dockerize -wait tcp://localhost:$DATABASE_DEPENDENCY_PORT -timeout 1m
|
|
36
|
+
- run:
|
|
37
|
+
name: Run specs
|
|
38
|
+
command: |
|
|
39
|
+
bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) --profile --format progress --format RspecJunitFormatter -o /tmp/circle_artifacts/rspec.xml
|
|
40
|
+
- run:
|
|
41
|
+
name: "Rubocop"
|
|
42
|
+
command: bundle exec rubocop --extra-details --display-style-guide --parallel --force-exclusion
|
|
43
|
+
- store_artifacts:
|
|
44
|
+
path: /tmp/circle_artifacts/
|
|
45
|
+
- store_test_results:
|
|
46
|
+
path: /tmp/circle_artifacts/
|
|
27
47
|
|
|
28
|
-
|
|
48
|
+
ruby_versions: &ruby_versions
|
|
49
|
+
- "2.5"
|
|
50
|
+
- "2.6"
|
|
51
|
+
- "2.7"
|
|
52
|
+
- "3.0"
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
rails_versions: &rails_versions
|
|
55
|
+
- "5.2.6"
|
|
56
|
+
- "6.0.4"
|
|
57
|
+
- "6.1.4"
|
|
58
|
+
- "main"
|
|
32
59
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
docker:
|
|
36
|
-
- image: circleci/ruby:2.4.9-node
|
|
37
|
-
environment:
|
|
38
|
-
- RAILS_VERSION=5.2.4
|
|
39
|
-
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
40
|
-
- DATABASE_DEPENDENCY_PORT=3306
|
|
41
|
-
- image: circleci/mysql:5.7.18
|
|
42
|
-
environment:
|
|
43
|
-
- MYSQL_ALLOW_EMPTY_PASSWORD=true
|
|
44
|
-
- MYSQL_USER=root
|
|
45
|
-
- MYSQL_PASSWORD=
|
|
46
|
-
- MYSQL_DATABASE=statesman_test
|
|
47
|
-
steps: *steps
|
|
48
|
-
build-ruby249-rails-524-postgres:
|
|
49
|
-
docker:
|
|
50
|
-
- image: circleci/ruby:2.4.9-node
|
|
51
|
-
environment:
|
|
52
|
-
- RAILS_VERSION=5.2.4
|
|
53
|
-
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
54
|
-
- DATABASE_DEPENDENCY_PORT=5432
|
|
55
|
-
- image: circleci/postgres:9.6
|
|
56
|
-
environment:
|
|
57
|
-
- POSTGRES_USER=postgres
|
|
58
|
-
- POSTGRES_DB=statesman_test
|
|
59
|
-
- POSTGRES_PASSWORD=statesman
|
|
60
|
-
steps: *steps
|
|
60
|
+
mysql_versions: &mysql_versions
|
|
61
|
+
- "5.7"
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- image: circleci/ruby:2.6.5-node
|
|
65
|
-
environment:
|
|
66
|
-
- RAILS_VERSION=6.0.2
|
|
67
|
-
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
68
|
-
- DATABASE_DEPENDENCY_PORT=3306
|
|
69
|
-
- image: circleci/mysql:5.7.18
|
|
70
|
-
environment:
|
|
71
|
-
- MYSQL_ALLOW_EMPTY_PASSWORD=true
|
|
72
|
-
- MYSQL_USER=root
|
|
73
|
-
- MYSQL_PASSWORD=
|
|
74
|
-
- MYSQL_DATABASE=statesman_test
|
|
75
|
-
steps: *steps
|
|
76
|
-
build-ruby265-rails-602-postgres:
|
|
77
|
-
docker:
|
|
78
|
-
- image: circleci/ruby:2.6.5-node
|
|
79
|
-
environment:
|
|
80
|
-
- RAILS_VERSION=6.0.2
|
|
81
|
-
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
82
|
-
- DATABASE_DEPENDENCY_PORT=5432
|
|
83
|
-
- image: circleci/postgres:9.6
|
|
84
|
-
environment:
|
|
85
|
-
- POSTGRES_USER=postgres
|
|
86
|
-
- POSTGRES_DB=statesman_test
|
|
87
|
-
- POSTGRES_PASSWORD=statesman
|
|
88
|
-
steps: *steps
|
|
89
|
-
build-ruby265-rails-master-mysql:
|
|
90
|
-
docker:
|
|
91
|
-
- image: circleci/ruby:2.6.5-node
|
|
92
|
-
environment:
|
|
93
|
-
- RAILS_VERSION=master
|
|
94
|
-
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
95
|
-
- DATABASE_DEPENDENCY_PORT=3306
|
|
96
|
-
- image: circleci/mysql:5.7.18
|
|
97
|
-
environment:
|
|
98
|
-
- MYSQL_ALLOW_EMPTY_PASSWORD=true
|
|
99
|
-
- MYSQL_USER=root
|
|
100
|
-
- MYSQL_PASSWORD=
|
|
101
|
-
- MYSQL_DATABASE=statesman_test
|
|
102
|
-
steps: *steps
|
|
103
|
-
build-ruby265-rails-master-postgres:
|
|
104
|
-
docker:
|
|
105
|
-
- image: circleci/ruby:2.6.5-node
|
|
106
|
-
environment:
|
|
107
|
-
- RAILS_VERSION=master
|
|
108
|
-
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
109
|
-
- EXCLUDE_MONGOID=true
|
|
110
|
-
- DATABASE_DEPENDENCY_PORT=5432
|
|
111
|
-
- image: circleci/postgres:9.6
|
|
112
|
-
environment:
|
|
113
|
-
- POSTGRES_USER=postgres
|
|
114
|
-
- POSTGRES_DB=statesman_test
|
|
115
|
-
- POSTGRES_PASSWORD=statesman
|
|
116
|
-
steps: *steps
|
|
63
|
+
psql_versions: &psql_versions
|
|
64
|
+
- "9.6"
|
|
117
65
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
- MYSQL_USER=root
|
|
129
|
-
- MYSQL_PASSWORD=
|
|
130
|
-
- MYSQL_DATABASE=statesman_test
|
|
131
|
-
steps: *steps
|
|
132
|
-
build-ruby270-rails-602-postgres:
|
|
133
|
-
docker:
|
|
134
|
-
- image: circleci/ruby:2.7.0-node
|
|
135
|
-
environment:
|
|
136
|
-
- RAILS_VERSION=6.0.2
|
|
137
|
-
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
138
|
-
- DATABASE_DEPENDENCY_PORT=5432
|
|
139
|
-
- image: circleci/postgres:9.6
|
|
140
|
-
environment:
|
|
141
|
-
- POSTGRES_USER=postgres
|
|
142
|
-
- POSTGRES_DB=statesman_test
|
|
143
|
-
- POSTGRES_PASSWORD=statesman
|
|
144
|
-
steps: *steps
|
|
145
|
-
build-ruby270-rails-master-mysql:
|
|
66
|
+
jobs:
|
|
67
|
+
rspec_mysql:
|
|
68
|
+
working_directory: /mnt/ramdisk
|
|
69
|
+
parameters:
|
|
70
|
+
ruby_version:
|
|
71
|
+
type: string
|
|
72
|
+
rails_version:
|
|
73
|
+
type: string
|
|
74
|
+
mysql_version:
|
|
75
|
+
type: string
|
|
146
76
|
docker:
|
|
147
|
-
- image:
|
|
77
|
+
- image: cimg/ruby:<< parameters.ruby_version >>
|
|
148
78
|
environment:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
- image:
|
|
79
|
+
CIRCLE_TEST_REPORTS: /tmp/circle_artifacts/
|
|
80
|
+
DATABASE_URL: mysql2://foobar:password@127.0.0.1/statesman_test
|
|
81
|
+
DATABASE_DEPENDENCY_PORT: "3306"
|
|
82
|
+
- image: cimg/mysql:<< parameters.mysql_version >>
|
|
153
83
|
environment:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
84
|
+
MYSQL_ROOT_PASSWORD: password
|
|
85
|
+
MYSQL_USER: foobar
|
|
86
|
+
MYSQL_PASSWORD: password
|
|
87
|
+
MYSQL_DATABASE: statesman_test
|
|
158
88
|
steps: *steps
|
|
159
|
-
|
|
89
|
+
|
|
90
|
+
rspec_postgres:
|
|
91
|
+
working_directory: /mnt/ramdisk
|
|
92
|
+
parameters:
|
|
93
|
+
ruby_version:
|
|
94
|
+
type: string
|
|
95
|
+
rails_version:
|
|
96
|
+
type: string
|
|
97
|
+
psql_version:
|
|
98
|
+
type: string
|
|
160
99
|
docker:
|
|
161
|
-
- image:
|
|
100
|
+
- image: cimg/ruby:<< parameters.ruby_version >>
|
|
162
101
|
environment:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
- image: circleci/postgres:9.6
|
|
102
|
+
CIRCLE_TEST_REPORTS: /tmp/circle_artifacts/
|
|
103
|
+
DATABASE_URL: postgres://postgres@localhost/statesman_test
|
|
104
|
+
DATABASE_DEPENDENCY_PORT: "5432"
|
|
105
|
+
- image: circleci/postgres:<< parameters.psql_version >>
|
|
168
106
|
environment:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
107
|
+
POSTGRES_USER: postgres
|
|
108
|
+
POSTGRES_DB: statesman_test
|
|
109
|
+
POSTGRES_PASSWORD: statesman
|
|
172
110
|
steps: *steps
|
|
173
111
|
|
|
174
112
|
workflows:
|
|
175
113
|
version: 2
|
|
176
114
|
tests:
|
|
177
115
|
jobs:
|
|
178
|
-
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
116
|
+
- rspec_mysql:
|
|
117
|
+
matrix:
|
|
118
|
+
parameters:
|
|
119
|
+
mysql_version: *mysql_versions
|
|
120
|
+
ruby_version: *ruby_versions
|
|
121
|
+
rails_version: *rails_versions
|
|
122
|
+
- rspec_postgres:
|
|
123
|
+
matrix:
|
|
124
|
+
parameters:
|
|
125
|
+
psql_version: *psql_versions
|
|
126
|
+
ruby_version: *ruby_versions
|
|
127
|
+
rails_version: *rails_versions
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on
|
|
3
|
+
# on 2021-08-09 15:32:40 UTC using RuboCop version 1.18.4.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
|
8
8
|
|
|
9
|
+
# Offense count: 1
|
|
10
|
+
# Configuration parameters: Include.
|
|
11
|
+
# Include: **/*.gemspec
|
|
9
12
|
Gemspec/RequiredRubyVersion:
|
|
10
|
-
|
|
13
|
+
Exclude:
|
|
14
|
+
- 'statesman.gemspec'
|
|
15
|
+
|
|
16
|
+
# Offense count: 1
|
|
17
|
+
Lint/MissingSuper:
|
|
18
|
+
Exclude:
|
|
19
|
+
- 'lib/statesman/adapters/active_record_queries.rb'
|
|
11
20
|
|
|
12
21
|
# Offense count: 5
|
|
22
|
+
# Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
|
|
13
23
|
Metrics/AbcSize:
|
|
14
|
-
Max:
|
|
24
|
+
Max: 20
|
|
25
|
+
|
|
26
|
+
# Offense count: 1
|
|
27
|
+
# Configuration parameters: IgnoredMethods.
|
|
28
|
+
Metrics/CyclomaticComplexity:
|
|
29
|
+
Max: 8
|
|
15
30
|
|
|
16
|
-
# Offense count:
|
|
17
|
-
# Configuration parameters: CountComments, ExcludedMethods.
|
|
31
|
+
# Offense count: 3
|
|
32
|
+
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
|
|
18
33
|
Metrics/MethodLength:
|
|
19
34
|
Max: 14
|
|
20
35
|
|
|
21
|
-
# Offense count:
|
|
22
|
-
#
|
|
23
|
-
# Configuration parameters: SkipBlocks, EnforcedStyle.
|
|
24
|
-
# SupportedStyles: described_class, explicit
|
|
25
|
-
RSpec/DescribedClass:
|
|
26
|
-
Exclude:
|
|
27
|
-
- 'spec/statesman/adapters/active_record_queries_spec.rb'
|
|
28
|
-
|
|
29
|
-
# Offense count: 7
|
|
30
|
-
# Configuration parameters: Max.
|
|
36
|
+
# Offense count: 11
|
|
37
|
+
# Configuration parameters: CountAsOne.
|
|
31
38
|
RSpec/ExampleLength:
|
|
32
|
-
|
|
33
|
-
- 'spec/statesman/adapters/active_record_spec.rb'
|
|
34
|
-
- 'spec/statesman/adapters/shared_examples.rb'
|
|
35
|
-
- 'spec/statesman/machine_spec.rb'
|
|
39
|
+
Max: 14
|
|
36
40
|
|
|
37
41
|
# Offense count: 7
|
|
38
42
|
RSpec/ExpectInHook:
|
|
@@ -40,11 +44,12 @@ RSpec/ExpectInHook:
|
|
|
40
44
|
- 'spec/statesman/adapters/active_record_spec.rb'
|
|
41
45
|
- 'spec/statesman/machine_spec.rb'
|
|
42
46
|
|
|
43
|
-
# Offense count:
|
|
44
|
-
|
|
47
|
+
# Offense count: 1
|
|
48
|
+
# Configuration parameters: Include, CustomTransform, IgnoreMethods, SpecSuffixOnly.
|
|
49
|
+
# Include: **/*_spec*rb*, **/spec/**/*
|
|
50
|
+
RSpec/FilePath:
|
|
45
51
|
Exclude:
|
|
46
|
-
- 'spec/statesman/
|
|
47
|
-
- 'spec/statesman/adapters/shared_examples.rb'
|
|
52
|
+
- 'spec/statesman/exceptions_spec.rb'
|
|
48
53
|
|
|
49
54
|
# Offense count: 1
|
|
50
55
|
# Configuration parameters: AssignmentOnly.
|
|
@@ -75,23 +80,27 @@ RSpec/MessageSpies:
|
|
|
75
80
|
Exclude:
|
|
76
81
|
- 'spec/statesman/callback_spec.rb'
|
|
77
82
|
|
|
78
|
-
# Offense count:
|
|
79
|
-
# Configuration parameters: AggregateFailuresByDefault.
|
|
83
|
+
# Offense count: 14
|
|
80
84
|
RSpec/MultipleExpectations:
|
|
81
85
|
Max: 3
|
|
82
86
|
|
|
83
|
-
# Offense count:
|
|
87
|
+
# Offense count: 49
|
|
84
88
|
RSpec/NestedGroups:
|
|
85
89
|
Max: 6
|
|
86
90
|
|
|
87
|
-
# Offense count:
|
|
91
|
+
# Offense count: 2
|
|
92
|
+
RSpec/RepeatedExampleGroupBody:
|
|
93
|
+
Exclude:
|
|
94
|
+
- 'spec/statesman/exceptions_spec.rb'
|
|
95
|
+
|
|
96
|
+
# Offense count: 12
|
|
88
97
|
RSpec/ScatteredSetup:
|
|
89
98
|
Exclude:
|
|
90
99
|
- 'spec/statesman/adapters/active_record_spec.rb'
|
|
91
100
|
- 'spec/statesman/adapters/shared_examples.rb'
|
|
92
101
|
- 'spec/statesman/machine_spec.rb'
|
|
93
102
|
|
|
94
|
-
# Offense count:
|
|
103
|
+
# Offense count: 7
|
|
95
104
|
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
|
|
96
105
|
RSpec/VerifiedDoubles:
|
|
97
106
|
Exclude:
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.0.2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
+
## v9.0.1 4th February 2021
|
|
2
|
+
|
|
3
|
+
### Changed
|
|
4
|
+
- Deprecate `ActiveRecord::Base.default_timezone` in favour of `ActiveRecord.default_timezone` [#446](https://github.com/gocardless/statesman/pull/446)
|
|
5
|
+
|
|
6
|
+
## v9.0.0 9th August 2021
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- Added Ruby 3.0 support
|
|
10
|
+
|
|
11
|
+
### Breaking changes
|
|
12
|
+
|
|
13
|
+
- Removed Ruby 2.4
|
|
14
|
+
|
|
15
|
+
## v8.0.3 8th June 2021
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Implement `Machine#last_transition_to`, to find the last transition to a given state
|
|
19
|
+
[#438](https://github.com/gocardless/statesman/pull/438)
|
|
20
|
+
|
|
21
|
+
## v8.0.2 30th March 2021
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Fixed a bug where the `history` of a model was left in an incorrect state after a transition
|
|
26
|
+
conflict [#433](https://github.com/gocardless/statesman/pull/433)
|
|
27
|
+
|
|
28
|
+
## v8.0.1 20th January 2021
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Fixed `no implicit conversion of nil into String` error when quoting null values
|
|
33
|
+
[#427](https://github.com/gocardless/statesman/pull/427)
|
|
34
|
+
|
|
35
|
+
## v8.0.0 6th January 2021
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- Use AR Arel table to type cast booleans in order to avoid deprecation warning [#421](https://github.com/gocardless/statesman/pull/421)
|
|
40
|
+
- Support relationships that doesn't use `id` as a Primary Key
|
|
41
|
+
[#422](https://github.com/gocardless/statesman/pull/422)
|
|
42
|
+
|
|
43
|
+
## v7.4.1 11th November 2020
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- Add #reset method to state machine and adapter interfaces
|
|
48
|
+
[#417](https://github.com/gocardless/statesman/pull/417)
|
|
49
|
+
|
|
1
50
|
## v7.4.0 26th August 2020
|
|
2
51
|
|
|
3
52
|
### Added
|
|
@@ -9,16 +58,16 @@
|
|
|
9
58
|
|
|
10
59
|
### Changed
|
|
11
60
|
|
|
12
|
-
- Use correct Arel for null [#409](https://github.com/gocardless/statesman/pull
|
|
61
|
+
- Use correct Arel for null [#409](https://github.com/gocardless/statesman/pull/409)
|
|
13
62
|
|
|
14
63
|
## v7.2.0, 19th May 2020
|
|
15
64
|
|
|
16
65
|
### Changed
|
|
17
66
|
|
|
18
|
-
- Set non-empty password for postgres tests [#398](https://github.com/gocardless/statesman/pull
|
|
19
|
-
- Handle transitions differently for MySQL [#399](https://github.com/gocardless/statesman/pull
|
|
20
|
-
- pg requirement from >= 0.18, <= 1.1 to >= 0.18, <= 1.3 [#400](https://github.com/gocardless/statesman/pull
|
|
21
|
-
- Lazily enable mysql gaplock protection [#402](https://github.com/gocardless/statesman/pull
|
|
67
|
+
- Set non-empty password for postgres tests [#398](https://github.com/gocardless/statesman/pull/398)
|
|
68
|
+
- Handle transitions differently for MySQL [#399](https://github.com/gocardless/statesman/pull/399)
|
|
69
|
+
- pg requirement from >= 0.18, <= 1.1 to >= 0.18, <= 1.3 [#400](https://github.com/gocardless/statesman/pull/400)
|
|
70
|
+
- Lazily enable mysql gaplock protection [#402](https://github.com/gocardless/statesman/pull/402)
|
|
22
71
|
|
|
23
72
|
## v7.1.0, 10th Feb 2020
|
|
24
73
|
|
|
@@ -71,7 +120,7 @@
|
|
|
71
120
|
to
|
|
72
121
|
```ruby
|
|
73
122
|
include Statesman::Adapters::ActiveRecordQueries[
|
|
74
|
-
initial_state: :
|
|
123
|
+
initial_state: :initial,
|
|
75
124
|
transition_class: MyTransition
|
|
76
125
|
]
|
|
77
126
|
```
|
data/CONTRIBUTING.md
CHANGED
|
@@ -19,3 +19,21 @@ request passes by running `rubocop`.
|
|
|
19
19
|
|
|
20
20
|
Please add a section to the readme for any new feature additions or behaviour
|
|
21
21
|
changes.
|
|
22
|
+
|
|
23
|
+
## Releasing
|
|
24
|
+
|
|
25
|
+
We publish new versions of Stateman using [RubyGems](https://guides.rubygems.org/publishing/). Once
|
|
26
|
+
the relevant changes have been merged and `VERSION` has been appropriately bumped to the new
|
|
27
|
+
version, we run the following command.
|
|
28
|
+
```
|
|
29
|
+
$ gem build statesman.gemspec
|
|
30
|
+
```
|
|
31
|
+
This builds a `.gem` file locally that will be named something like `statesman-X` where `X` is the
|
|
32
|
+
new version. For example, if we are releasing version 9.0.0, the file would be
|
|
33
|
+
`statesman-9.0.0.gem`.
|
|
34
|
+
|
|
35
|
+
To publish, run `gem push` with the new `.gem` file we just generated. This requires a OTP that is currently only available
|
|
36
|
+
to GoCardless engineers. For example, if we were to continue to publish version 9.0.0, we would run:
|
|
37
|
+
```
|
|
38
|
+
$ gem push statesman-9.0.0.gem
|
|
39
|
+
```
|
data/Gemfile
CHANGED
|
@@ -4,14 +4,11 @@ source 'https://rubygems.org'
|
|
|
4
4
|
|
|
5
5
|
gemspec
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
gem "rails", git: "https://github.com/rails/rails"
|
|
7
|
+
if ENV['RAILS_VERSION'] == 'main'
|
|
8
|
+
gem "rails", git: "https://github.com/rails/rails", branch: "main"
|
|
10
9
|
elsif ENV['RAILS_VERSION']
|
|
11
10
|
gem "rails", "~> #{ENV['RAILS_VERSION']}"
|
|
12
11
|
end
|
|
13
|
-
# rubocop:enable Bundler/DuplicatedGem
|
|
14
|
-
|
|
15
12
|
group :development do
|
|
16
13
|
# test/unit is no longer bundled with Ruby 2.2, but required by Rails
|
|
17
14
|
gem "test-unit", "~> 3.3" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.0")
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<p align="center"><img src="
|
|
1
|
+
<p align="center"><img src="https://user-images.githubusercontent.com/110275/106792848-96e4ee80-664e-11eb-8fd1-16ff24b41eb2.png" alt="Statesman" width="512"></p>
|
|
2
2
|
|
|
3
3
|
A statesmanlike state machine library.
|
|
4
4
|
|
|
@@ -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', '~> 8.0.3'
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## Usage
|
|
@@ -109,6 +109,8 @@ Order.first.state_machine.allowed_transitions # => ["checking_out", "cancelled"]
|
|
|
109
109
|
Order.first.state_machine.can_transition_to?(:cancelled) # => true/false
|
|
110
110
|
Order.first.state_machine.transition_to(:cancelled, optional: :metadata) # => true/false
|
|
111
111
|
Order.first.state_machine.transition_to!(:cancelled) # => true/exception
|
|
112
|
+
Order.first.state_machine.last_transition # => transition model or nil
|
|
113
|
+
Order.first.state_machine.last_transition_to(:pending) # => transition model or nil
|
|
112
114
|
|
|
113
115
|
Order.in_state(:cancelled) # => [#<Order id: "123">]
|
|
114
116
|
Order.not_in_state(:checking_out) # => [#<Order id: "123">]
|
|
@@ -159,7 +161,8 @@ class Order < ActiveRecord::Base
|
|
|
159
161
|
|
|
160
162
|
# Optionally delegate some methods
|
|
161
163
|
|
|
162
|
-
delegate :can_transition_to?,
|
|
164
|
+
delegate :can_transition_to?,
|
|
165
|
+
:current_state, :history, :last_transition, :last_transition_to,
|
|
163
166
|
:transition_to!, :transition_to, :in_state?, to: :state_machine
|
|
164
167
|
end
|
|
165
168
|
```
|
|
@@ -322,6 +325,10 @@ Machine.successors
|
|
|
322
325
|
#### `Machine#current_state`
|
|
323
326
|
Returns the current state based on existing transition objects.
|
|
324
327
|
|
|
328
|
+
Takes an optional keyword argument to force a reload of data from the
|
|
329
|
+
database.
|
|
330
|
+
e.g `current_state(force_reload: true)`
|
|
331
|
+
|
|
325
332
|
#### `Machine#in_state?(:state_1, :state_2, ...)`
|
|
326
333
|
Returns true if the machine is in any of the given states.
|
|
327
334
|
|
|
@@ -331,6 +338,9 @@ Returns a sorted array of all transition objects.
|
|
|
331
338
|
#### `Machine#last_transition`
|
|
332
339
|
Returns the most recent transition object.
|
|
333
340
|
|
|
341
|
+
#### `Machine#last_transition_to(:state)`
|
|
342
|
+
Returns the most recent transition object to a given state.
|
|
343
|
+
|
|
334
344
|
#### `Machine#allowed_transitions`
|
|
335
345
|
Returns an array of states you can `transition_to` from current state.
|
|
336
346
|
|
|
@@ -347,6 +357,66 @@ Transition to the passed state, returning `true` on success. Swallows all
|
|
|
347
357
|
Statesman exceptions and returns false on failure. (NB. if your guard or
|
|
348
358
|
callback code throws an exception, it will not be caught.)
|
|
349
359
|
|
|
360
|
+
|
|
361
|
+
## Errors
|
|
362
|
+
|
|
363
|
+
### Initialization errors
|
|
364
|
+
These errors are raised when the Machine and/or Model is initialized. A simple spec like
|
|
365
|
+
```ruby
|
|
366
|
+
expect { OrderStateMachine.new(Order.new, transition_class: OrderTransition) }.to_not raise_error
|
|
367
|
+
```
|
|
368
|
+
will expose these errors as part of your test suite
|
|
369
|
+
|
|
370
|
+
#### InvalidStateError
|
|
371
|
+
Raised if:
|
|
372
|
+
* Attempting to define a transition without a `to` state.
|
|
373
|
+
* Attempting to define a transition with a non-existent state.
|
|
374
|
+
* Attempting to define multiple states as `initial`.
|
|
375
|
+
|
|
376
|
+
#### InvalidTransitionError
|
|
377
|
+
Raised if:
|
|
378
|
+
* Attempting to define a callback `from` a state that has no valid transitions (A terminal state).
|
|
379
|
+
* Attempting to define a callback `to` the `initial` state if that state has no transitions to it.
|
|
380
|
+
* Attempting to define a callback with `from` and `to` where any of the pairs have no transition between them.
|
|
381
|
+
|
|
382
|
+
#### InvalidCallbackError
|
|
383
|
+
Raised if:
|
|
384
|
+
* Attempting to define a callback without a block.
|
|
385
|
+
|
|
386
|
+
#### UnserializedMetadataError
|
|
387
|
+
Raised if:
|
|
388
|
+
* ActiveRecord is configured to not serialize the `metadata` attribute into
|
|
389
|
+
to Database column backing it. See the `Using PostgreSQL JSON column` section.
|
|
390
|
+
|
|
391
|
+
#### IncompatibleSerializationError
|
|
392
|
+
Raised if:
|
|
393
|
+
* There is a mismatch between the column type of the `metadata` in the
|
|
394
|
+
Database and the model. See the `Using PostgreSQL JSON column` section.
|
|
395
|
+
|
|
396
|
+
#### MissingTransitionAssociation
|
|
397
|
+
Raised if:
|
|
398
|
+
* The model that `Statesman::Adapters::ActiveRecordQueries` is included in
|
|
399
|
+
does not have a `has_many` association to the `transition_class`.
|
|
400
|
+
|
|
401
|
+
### Runtime errors
|
|
402
|
+
These errors are raised by `transition_to!`. Using `transition_to` will
|
|
403
|
+
supress `GuardFailedError` and `TransitionFailedError` and return `false` instead.
|
|
404
|
+
|
|
405
|
+
#### GuardFailedError
|
|
406
|
+
Raised if:
|
|
407
|
+
* A guard callback between `from` and `to` state returned a falsey value.
|
|
408
|
+
|
|
409
|
+
#### TransitionFailedError
|
|
410
|
+
Raised if:
|
|
411
|
+
* A transition is attempted but `current_state -> new_state` is not a valid pair.
|
|
412
|
+
|
|
413
|
+
#### TransitionConflictError
|
|
414
|
+
Raised if:
|
|
415
|
+
* A database conflict affecting the `sort_key` or `most_recent` columns occurs
|
|
416
|
+
when attempting a transition.
|
|
417
|
+
Retried automatically if it occurs wrapped in `retry_conflicts`.
|
|
418
|
+
|
|
419
|
+
|
|
350
420
|
## Model scopes
|
|
351
421
|
|
|
352
422
|
A mixin is provided for the ActiveRecord adapter which adds scopes to easily
|
|
@@ -404,10 +474,12 @@ Model.in_state(:state_1).or(
|
|
|
404
474
|
#### Storing the state on the model object
|
|
405
475
|
|
|
406
476
|
If you wish to store the model state on the model directly, you can keep it up
|
|
407
|
-
to date using an `after_transition` hook
|
|
477
|
+
to date using an `after_transition` hook.
|
|
478
|
+
Combine it with the `after_commit` option to ensure the model state will only be
|
|
479
|
+
saved once the transition has made it irreversibly to the database:
|
|
408
480
|
|
|
409
481
|
```ruby
|
|
410
|
-
after_transition do |model, transition|
|
|
482
|
+
after_transition(after_commit: true) do |model, transition|
|
|
411
483
|
model.state = transition.to_state
|
|
412
484
|
model.save!
|
|
413
485
|
end
|
|
@@ -39,8 +39,16 @@ module Statesman
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def mysql?
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
configuration.try(:[], "adapter").try(:match, /mysql/)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# [] is deprecated and will be removed in 6.2
|
|
46
|
+
def configuration
|
|
47
|
+
if ActiveRecord::Base.configurations.respond_to?(:configs_for)
|
|
48
|
+
ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first
|
|
49
|
+
else
|
|
50
|
+
ActiveRecord::Base.configurations[Rails.env]
|
|
51
|
+
end
|
|
44
52
|
end
|
|
45
53
|
|
|
46
54
|
def database_supports_partial_indexes?
|
|
@@ -42,7 +42,13 @@ module Statesman
|
|
|
42
42
|
def create(from, to, metadata = {})
|
|
43
43
|
create_transition(from.to_s, to.to_s, metadata)
|
|
44
44
|
rescue ::ActiveRecord::RecordNotUnique => e
|
|
45
|
-
|
|
45
|
+
if transition_conflict_error? e
|
|
46
|
+
# The history has the invalid transition on the end of it, which means
|
|
47
|
+
# `current_state` would then be incorrect. We force a reload of the history to
|
|
48
|
+
# avoid this.
|
|
49
|
+
transitions_for_parent.reload
|
|
50
|
+
raise TransitionConflictError, e.message
|
|
51
|
+
end
|
|
46
52
|
|
|
47
53
|
raise
|
|
48
54
|
ensure
|
|
@@ -59,6 +65,7 @@ module Statesman
|
|
|
59
65
|
end
|
|
60
66
|
end
|
|
61
67
|
|
|
68
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
|
62
69
|
def last(force_reload: false)
|
|
63
70
|
if force_reload
|
|
64
71
|
@last_transition = history(force_reload: true).last
|
|
@@ -66,6 +73,11 @@ module Statesman
|
|
|
66
73
|
@last_transition ||= history.last
|
|
67
74
|
end
|
|
68
75
|
end
|
|
76
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
|
77
|
+
|
|
78
|
+
def reset
|
|
79
|
+
@last_transition = nil
|
|
80
|
+
end
|
|
69
81
|
|
|
70
82
|
private
|
|
71
83
|
|
|
@@ -292,34 +304,42 @@ module Statesman
|
|
|
292
304
|
return nil if column.nil?
|
|
293
305
|
|
|
294
306
|
[
|
|
295
|
-
column,
|
|
307
|
+
column, default_timezone == :utc ? Time.now.utc : Time.now
|
|
296
308
|
]
|
|
297
309
|
end
|
|
298
310
|
|
|
311
|
+
def default_timezone
|
|
312
|
+
# Rails 7 deprecates ActiveRecord::Base.default_timezone
|
|
313
|
+
# in favour of ActiveRecord.default_timezone
|
|
314
|
+
if ::ActiveRecord.respond_to?(:default_timezone)
|
|
315
|
+
return ::ActiveRecord.default_timezone
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
::ActiveRecord::Base.default_timezone
|
|
319
|
+
end
|
|
320
|
+
|
|
299
321
|
def mysql_gaplock_protection?
|
|
300
322
|
Statesman.mysql_gaplock_protection?
|
|
301
323
|
end
|
|
302
324
|
|
|
303
325
|
def db_true
|
|
304
|
-
|
|
305
|
-
true,
|
|
306
|
-
transition_class.columns_hash["most_recent"],
|
|
307
|
-
)
|
|
308
|
-
::ActiveRecord::Base.connection.quote(value)
|
|
326
|
+
::ActiveRecord::Base.connection.quote(type_cast(true))
|
|
309
327
|
end
|
|
310
328
|
|
|
311
329
|
def db_false
|
|
312
|
-
|
|
313
|
-
false,
|
|
314
|
-
transition_class.columns_hash["most_recent"],
|
|
315
|
-
)
|
|
316
|
-
::ActiveRecord::Base.connection.quote(value)
|
|
330
|
+
::ActiveRecord::Base.connection.quote(type_cast(false))
|
|
317
331
|
end
|
|
318
332
|
|
|
319
333
|
def db_null
|
|
320
334
|
Arel::Nodes::SqlLiteral.new("NULL")
|
|
321
335
|
end
|
|
322
336
|
|
|
337
|
+
# Type casting against a column is deprecated and will be removed in Rails 6.2.
|
|
338
|
+
# See https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11
|
|
339
|
+
def type_cast(value)
|
|
340
|
+
::ActiveRecord::Base.connection.type_cast(value)
|
|
341
|
+
end
|
|
342
|
+
|
|
323
343
|
# Check whether the `most_recent` column allows null values. If it doesn't, set old
|
|
324
344
|
# records to `false`, otherwise, set them to `NULL`.
|
|
325
345
|
#
|
|
@@ -104,7 +104,7 @@ module Statesman
|
|
|
104
104
|
|
|
105
105
|
def most_recent_transition_join
|
|
106
106
|
"LEFT OUTER JOIN #{model_table} AS #{most_recent_transition_alias} " \
|
|
107
|
-
"ON #{model.table_name}
|
|
107
|
+
"ON #{model.table_name}.#{model_primary_key} = " \
|
|
108
108
|
"#{most_recent_transition_alias}.#{model_foreign_key} " \
|
|
109
109
|
"AND #{most_recent_transition_alias}.most_recent = #{db_true}"
|
|
110
110
|
end
|
|
@@ -127,6 +127,10 @@ module Statesman
|
|
|
127
127
|
"and #{transition_class}."
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
+
def model_primary_key
|
|
131
|
+
transition_reflection.active_record_primary_key
|
|
132
|
+
end
|
|
133
|
+
|
|
130
134
|
def model_foreign_key
|
|
131
135
|
transition_reflection.foreign_key
|
|
132
136
|
end
|
data/lib/statesman/exceptions.rb
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
module Statesman
|
|
4
4
|
class InvalidStateError < StandardError; end
|
|
5
|
+
|
|
5
6
|
class InvalidTransitionError < StandardError; end
|
|
7
|
+
|
|
6
8
|
class InvalidCallbackError < StandardError; end
|
|
9
|
+
|
|
7
10
|
class TransitionConflictError < StandardError; end
|
|
11
|
+
|
|
8
12
|
class MissingTransitionAssociation < StandardError; end
|
|
9
13
|
|
|
10
14
|
class TransitionFailedError < StandardError
|
data/lib/statesman/machine.rb
CHANGED
|
@@ -209,6 +209,10 @@ module Statesman
|
|
|
209
209
|
@storage_adapter.last(force_reload: force_reload)
|
|
210
210
|
end
|
|
211
211
|
|
|
212
|
+
def last_transition_to(state)
|
|
213
|
+
history.reverse.find { |transition| transition.to_state.to_sym == state.to_sym }
|
|
214
|
+
end
|
|
215
|
+
|
|
212
216
|
def can_transition_to?(new_state, metadata = {})
|
|
213
217
|
validate_transition(from: current_state,
|
|
214
218
|
to: new_state,
|
|
@@ -257,6 +261,10 @@ module Statesman
|
|
|
257
261
|
false
|
|
258
262
|
end
|
|
259
263
|
|
|
264
|
+
def reset
|
|
265
|
+
@storage_adapter.reset
|
|
266
|
+
end
|
|
267
|
+
|
|
260
268
|
private
|
|
261
269
|
|
|
262
270
|
def adapter_class(transition_class)
|
data/lib/statesman/version.rb
CHANGED
|
@@ -50,10 +50,11 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
|
50
50
|
|
|
51
51
|
shared_examples "testing methods" do
|
|
52
52
|
before do
|
|
53
|
-
|
|
53
|
+
case config_type
|
|
54
|
+
when :old
|
|
54
55
|
configure_old(MyActiveRecordModel, MyActiveRecordModelTransition)
|
|
55
56
|
configure_old(OtherActiveRecordModel, OtherActiveRecordModelTransition)
|
|
56
|
-
|
|
57
|
+
when :new
|
|
57
58
|
configure_new(MyActiveRecordModel, MyActiveRecordModelTransition)
|
|
58
59
|
configure_new(OtherActiveRecordModel, OtherActiveRecordModelTransition)
|
|
59
60
|
else
|
|
@@ -154,6 +155,31 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
|
154
155
|
end
|
|
155
156
|
end
|
|
156
157
|
|
|
158
|
+
context "with a custom primary key for the model" do
|
|
159
|
+
before do
|
|
160
|
+
# Switch to using OtherActiveRecordModelTransition, so the existing
|
|
161
|
+
# relation with MyActiveRecordModelTransition doesn't interfere with
|
|
162
|
+
# this spec.
|
|
163
|
+
# Configure the relationship to use a different primary key,
|
|
164
|
+
MyActiveRecordModel.send(:has_many,
|
|
165
|
+
:custom_name,
|
|
166
|
+
class_name: "OtherActiveRecordModelTransition",
|
|
167
|
+
primary_key: :external_id)
|
|
168
|
+
|
|
169
|
+
MyActiveRecordModel.class_eval do
|
|
170
|
+
def self.transition_class
|
|
171
|
+
OtherActiveRecordModelTransition
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe ".in_state" do
|
|
177
|
+
subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
|
|
178
|
+
|
|
179
|
+
specify { expect { query }.to_not raise_error }
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
157
183
|
context "after_commit transactional integrity" do
|
|
158
184
|
before do
|
|
159
185
|
MyStateMachine.class_eval do
|
|
@@ -176,7 +202,6 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
|
176
202
|
MyActiveRecordModel.create
|
|
177
203
|
end
|
|
178
204
|
|
|
179
|
-
# rubocop:disable RSpec/ExampleLength
|
|
180
205
|
it do
|
|
181
206
|
expect do
|
|
182
207
|
ActiveRecord::Base.transaction do
|
|
@@ -185,7 +210,6 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
|
185
210
|
end
|
|
186
211
|
end.to_not change(MyStateMachine, :after_commit_callback_executed)
|
|
187
212
|
end
|
|
188
|
-
# rubocop:enable RSpec/ExampleLength
|
|
189
213
|
end
|
|
190
214
|
end
|
|
191
215
|
|
|
@@ -130,6 +130,31 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
130
130
|
expect { adapter.create(:y, :z) }.
|
|
131
131
|
to raise_exception(Statesman::TransitionConflictError)
|
|
132
132
|
end
|
|
133
|
+
|
|
134
|
+
it "does not pollute the state when the transition fails" do
|
|
135
|
+
# this increments the sort_key in the database
|
|
136
|
+
adapter.create(:x, :y)
|
|
137
|
+
|
|
138
|
+
# we then pre-load the transitions for efficiency
|
|
139
|
+
preloaded_model = MyActiveRecordModel.
|
|
140
|
+
includes(:my_active_record_model_transitions).
|
|
141
|
+
find(model.id)
|
|
142
|
+
|
|
143
|
+
adapter2 = described_class.
|
|
144
|
+
new(MyActiveRecordModelTransition, preloaded_model, observer)
|
|
145
|
+
|
|
146
|
+
# Now we generate a race
|
|
147
|
+
adapter.create(:y, :z)
|
|
148
|
+
expect { adapter2.create(:y, :a) }.
|
|
149
|
+
to raise_error(Statesman::TransitionConflictError)
|
|
150
|
+
|
|
151
|
+
# The preloaded adapter should discard the preloaded info
|
|
152
|
+
expect(adapter2.last).to have_attributes(to_state: "z")
|
|
153
|
+
expect(adapter2.history).to contain_exactly(
|
|
154
|
+
have_attributes(to_state: "y"),
|
|
155
|
+
have_attributes(to_state: "z"),
|
|
156
|
+
)
|
|
157
|
+
end
|
|
133
158
|
end
|
|
134
159
|
|
|
135
160
|
context "when other exceptions occur" do
|
|
@@ -355,6 +380,19 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
355
380
|
end
|
|
356
381
|
end
|
|
357
382
|
|
|
383
|
+
it "resets last with #reload" do
|
|
384
|
+
model.save!
|
|
385
|
+
ActiveRecord::Base.transaction do
|
|
386
|
+
model.state_machine.transition_to!(:succeeded)
|
|
387
|
+
# force to cache value in last_transition instance variable
|
|
388
|
+
expect(model.state_machine.current_state).to eq("succeeded")
|
|
389
|
+
raise ActiveRecord::Rollback
|
|
390
|
+
end
|
|
391
|
+
expect(model.state_machine.current_state).to eq("succeeded")
|
|
392
|
+
model.reload
|
|
393
|
+
expect(model.state_machine.current_state).to eq("initial")
|
|
394
|
+
end
|
|
395
|
+
|
|
358
396
|
context "with a namespaced model" do
|
|
359
397
|
before do
|
|
360
398
|
CreateNamespacedARModelMigration.migrate(:up)
|
|
@@ -128,6 +128,7 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
|
|
|
128
128
|
|
|
129
129
|
it { is_expected.to be_a(transition_class) }
|
|
130
130
|
specify { expect(adapter.last.to_state.to_sym).to eq(:z) }
|
|
131
|
+
|
|
131
132
|
specify do
|
|
132
133
|
expect(adapter.last(force_reload: true).to_state.to_sym).to eq(:z)
|
|
133
134
|
end
|
|
@@ -7,6 +7,7 @@ describe Statesman do
|
|
|
7
7
|
subject(:error) { Statesman::InvalidStateError.new }
|
|
8
8
|
|
|
9
9
|
its(:message) { is_expected.to eq("Statesman::InvalidStateError") }
|
|
10
|
+
|
|
10
11
|
its "string matches its message" do
|
|
11
12
|
expect(error.to_s).to eq(error.message)
|
|
12
13
|
end
|
|
@@ -16,6 +17,7 @@ describe Statesman do
|
|
|
16
17
|
subject(:error) { Statesman::InvalidTransitionError.new }
|
|
17
18
|
|
|
18
19
|
its(:message) { is_expected.to eq("Statesman::InvalidTransitionError") }
|
|
20
|
+
|
|
19
21
|
its "string matches its message" do
|
|
20
22
|
expect(error.to_s).to eq(error.message)
|
|
21
23
|
end
|
|
@@ -25,6 +27,7 @@ describe Statesman do
|
|
|
25
27
|
subject(:error) { Statesman::InvalidTransitionError.new }
|
|
26
28
|
|
|
27
29
|
its(:message) { is_expected.to eq("Statesman::InvalidTransitionError") }
|
|
30
|
+
|
|
28
31
|
its "string matches its message" do
|
|
29
32
|
expect(error.to_s).to eq(error.message)
|
|
30
33
|
end
|
|
@@ -34,6 +37,7 @@ describe Statesman do
|
|
|
34
37
|
subject(:error) { Statesman::TransitionConflictError.new }
|
|
35
38
|
|
|
36
39
|
its(:message) { is_expected.to eq("Statesman::TransitionConflictError") }
|
|
40
|
+
|
|
37
41
|
its "string matches its message" do
|
|
38
42
|
expect(error.to_s).to eq(error.message)
|
|
39
43
|
end
|
|
@@ -43,6 +47,7 @@ describe Statesman do
|
|
|
43
47
|
subject(:error) { Statesman::MissingTransitionAssociation.new }
|
|
44
48
|
|
|
45
49
|
its(:message) { is_expected.to eq("Statesman::MissingTransitionAssociation") }
|
|
50
|
+
|
|
46
51
|
its "string matches its message" do
|
|
47
52
|
expect(error.to_s).to eq(error.message)
|
|
48
53
|
end
|
|
@@ -52,6 +57,7 @@ describe Statesman do
|
|
|
52
57
|
subject(:error) { Statesman::TransitionFailedError.new("from", "to") }
|
|
53
58
|
|
|
54
59
|
its(:message) { is_expected.to eq("Cannot transition from 'from' to 'to'") }
|
|
60
|
+
|
|
55
61
|
its "string matches its message" do
|
|
56
62
|
expect(error.to_s).to eq(error.message)
|
|
57
63
|
end
|
|
@@ -63,6 +69,7 @@ describe Statesman do
|
|
|
63
69
|
its(:message) do
|
|
64
70
|
is_expected.to eq("Guard on transition from: 'from' to 'to' returned false")
|
|
65
71
|
end
|
|
72
|
+
|
|
66
73
|
its "string matches its message" do
|
|
67
74
|
expect(error.to_s).to eq(error.message)
|
|
68
75
|
end
|
|
@@ -72,6 +79,7 @@ describe Statesman do
|
|
|
72
79
|
subject(:error) { Statesman::UnserializedMetadataError.new("foo") }
|
|
73
80
|
|
|
74
81
|
its(:message) { is_expected.to match(/foo#metadata is not serialized/) }
|
|
82
|
+
|
|
75
83
|
its "string matches its message" do
|
|
76
84
|
expect(error.to_s).to eq(error.message)
|
|
77
85
|
end
|
|
@@ -81,6 +89,7 @@ describe Statesman do
|
|
|
81
89
|
subject(:error) { Statesman::IncompatibleSerializationError.new("foo") }
|
|
82
90
|
|
|
83
91
|
its(:message) { is_expected.to match(/foo#metadata column type cannot be json/) }
|
|
92
|
+
|
|
84
93
|
its "string matches its message" do
|
|
85
94
|
expect(error.to_s).to eq(error.message)
|
|
86
95
|
end
|
|
@@ -234,11 +234,9 @@ describe Statesman::Machine do
|
|
|
234
234
|
|
|
235
235
|
it "does not add a callback" do
|
|
236
236
|
expect do
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
nil
|
|
241
|
-
end
|
|
237
|
+
set_callback
|
|
238
|
+
rescue error_type
|
|
239
|
+
nil
|
|
242
240
|
end.to_not change(machine.callbacks[callback_store], :count)
|
|
243
241
|
end
|
|
244
242
|
end
|
|
@@ -537,6 +535,34 @@ describe Statesman::Machine do
|
|
|
537
535
|
end
|
|
538
536
|
end
|
|
539
537
|
|
|
538
|
+
describe "#last_transition_to" do
|
|
539
|
+
subject { instance.last_transition_to(:y) }
|
|
540
|
+
|
|
541
|
+
before do
|
|
542
|
+
machine.class_eval do
|
|
543
|
+
state :x, initial: true
|
|
544
|
+
state :y
|
|
545
|
+
state :z
|
|
546
|
+
transition from: :x, to: :y
|
|
547
|
+
transition from: :y, to: :z
|
|
548
|
+
transition from: :z, to: :y
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
instance.transition_to!(:y)
|
|
552
|
+
instance.transition_to!(:z)
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
let(:instance) { machine.new(my_model) }
|
|
556
|
+
|
|
557
|
+
it { is_expected.to have_attributes(to_state: "y") }
|
|
558
|
+
|
|
559
|
+
context "when there are 2 transitions to the state" do
|
|
560
|
+
before { instance.transition_to!(:y) }
|
|
561
|
+
|
|
562
|
+
it { is_expected.to eq(instance.last_transition) }
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
540
566
|
describe "#can_transition_to?" do
|
|
541
567
|
subject(:can_transition_to?) { instance.can_transition_to?(new_state, metadata) }
|
|
542
568
|
|
|
@@ -33,6 +33,11 @@ class MyActiveRecordModel < ActiveRecord::Base
|
|
|
33
33
|
def metadata
|
|
34
34
|
super || {}
|
|
35
35
|
end
|
|
36
|
+
|
|
37
|
+
def reload(*)
|
|
38
|
+
state_machine.reset
|
|
39
|
+
super
|
|
40
|
+
end
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
class MyActiveRecordModelTransition < ActiveRecord::Base
|
|
@@ -59,7 +64,7 @@ class CreateMyActiveRecordModelMigration < MIGRATION_CLASS
|
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
# TODO: make this a module we can extend from the app? Or a generator?
|
|
62
|
-
# rubocop:disable
|
|
67
|
+
# rubocop:disable Metrics/MethodLength
|
|
63
68
|
class CreateMyActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
64
69
|
def change
|
|
65
70
|
create_table :my_active_record_model_transitions do |t|
|
|
@@ -105,7 +110,7 @@ class CreateMyActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
|
105
110
|
end
|
|
106
111
|
end
|
|
107
112
|
end
|
|
108
|
-
# rubocop:enable
|
|
113
|
+
# rubocop:enable Metrics/MethodLength
|
|
109
114
|
|
|
110
115
|
class OtherActiveRecordModel < ActiveRecord::Base
|
|
111
116
|
has_many :other_active_record_model_transitions, autosave: false
|
|
@@ -139,7 +144,7 @@ class CreateOtherActiveRecordModelMigration < MIGRATION_CLASS
|
|
|
139
144
|
end
|
|
140
145
|
end
|
|
141
146
|
|
|
142
|
-
# rubocop:disable MethodLength
|
|
147
|
+
# rubocop:disable Metrics/MethodLength
|
|
143
148
|
class CreateOtherActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
144
149
|
def change
|
|
145
150
|
create_table :other_active_record_model_transitions do |t|
|
|
@@ -183,7 +188,7 @@ class CreateOtherActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
|
183
188
|
end
|
|
184
189
|
end
|
|
185
190
|
end
|
|
186
|
-
# rubocop:enable MethodLength
|
|
191
|
+
# rubocop:enable Metrics/MethodLength
|
|
187
192
|
|
|
188
193
|
class DropMostRecentColumn < MIGRATION_CLASS
|
|
189
194
|
def change
|
|
@@ -237,7 +242,7 @@ class CreateNamespacedARModelMigration < MIGRATION_CLASS
|
|
|
237
242
|
end
|
|
238
243
|
end
|
|
239
244
|
|
|
240
|
-
# rubocop:disable MethodLength
|
|
245
|
+
# rubocop:disable Metrics/MethodLength
|
|
241
246
|
class CreateNamespacedARModelTransitionMigration < MIGRATION_CLASS
|
|
242
247
|
def change
|
|
243
248
|
create_table :my_namespace_my_active_record_model_transitions do |t|
|
|
@@ -277,5 +282,5 @@ class CreateNamespacedARModelTransitionMigration < MIGRATION_CLASS
|
|
|
277
282
|
name: "index_namespace_model_transitions_parent_latest"
|
|
278
283
|
end
|
|
279
284
|
end
|
|
280
|
-
# rubocop:enable MethodLength
|
|
285
|
+
# rubocop:enable Metrics/MethodLength
|
|
281
286
|
end
|
data/statesman.gemspec
CHANGED
|
@@ -21,11 +21,11 @@ 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.5"
|
|
25
25
|
|
|
26
26
|
spec.add_development_dependency "ammeter", "~> 1.1"
|
|
27
|
-
spec.add_development_dependency "bundler", "~> 2
|
|
28
|
-
spec.add_development_dependency "gc_ruboconfig", "~> 2.
|
|
27
|
+
spec.add_development_dependency "bundler", "~> 2"
|
|
28
|
+
spec.add_development_dependency "gc_ruboconfig", "~> 2.26.0"
|
|
29
29
|
spec.add_development_dependency "mysql2", ">= 0.4", "< 0.6"
|
|
30
30
|
spec.add_development_dependency "pg", ">= 0.18", "<= 1.3"
|
|
31
31
|
spec.add_development_dependency "pry"
|
|
@@ -33,8 +33,8 @@ 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-rails", "~> 3.1"
|
|
37
36
|
spec.add_development_dependency "rspec_junit_formatter", "~> 0.4.0"
|
|
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"
|
|
40
40
|
|
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: 9.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- GoCardless
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-02-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ammeter
|
|
@@ -30,28 +30,28 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 2
|
|
33
|
+
version: '2'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 2
|
|
40
|
+
version: '2'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: gc_ruboconfig
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: 2.
|
|
47
|
+
version: 2.26.0
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: 2.
|
|
54
|
+
version: 2.26.0
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: mysql2
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -163,33 +163,33 @@ dependencies:
|
|
|
163
163
|
- !ruby/object:Gem::Version
|
|
164
164
|
version: '1.1'
|
|
165
165
|
- !ruby/object:Gem::Dependency
|
|
166
|
-
name:
|
|
166
|
+
name: rspec_junit_formatter
|
|
167
167
|
requirement: !ruby/object:Gem::Requirement
|
|
168
168
|
requirements:
|
|
169
169
|
- - "~>"
|
|
170
170
|
- !ruby/object:Gem::Version
|
|
171
|
-
version:
|
|
171
|
+
version: 0.4.0
|
|
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:
|
|
178
|
+
version: 0.4.0
|
|
179
179
|
- !ruby/object:Gem::Dependency
|
|
180
|
-
name:
|
|
180
|
+
name: rspec-rails
|
|
181
181
|
requirement: !ruby/object:Gem::Requirement
|
|
182
182
|
requirements:
|
|
183
183
|
- - "~>"
|
|
184
184
|
- !ruby/object:Gem::Version
|
|
185
|
-
version:
|
|
185
|
+
version: '3.1'
|
|
186
186
|
type: :development
|
|
187
187
|
prerelease: false
|
|
188
188
|
version_requirements: !ruby/object:Gem::Requirement
|
|
189
189
|
requirements:
|
|
190
190
|
- - "~>"
|
|
191
191
|
- !ruby/object:Gem::Version
|
|
192
|
-
version:
|
|
192
|
+
version: '3.1'
|
|
193
193
|
- !ruby/object:Gem::Dependency
|
|
194
194
|
name: sqlite3
|
|
195
195
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -226,9 +226,11 @@ extensions: []
|
|
|
226
226
|
extra_rdoc_files: []
|
|
227
227
|
files:
|
|
228
228
|
- ".circleci/config.yml"
|
|
229
|
+
- ".github/dependabot.yml"
|
|
229
230
|
- ".gitignore"
|
|
230
231
|
- ".rubocop.yml"
|
|
231
232
|
- ".rubocop_todo.yml"
|
|
233
|
+
- ".ruby-version"
|
|
232
234
|
- CHANGELOG.md
|
|
233
235
|
- CONTRIBUTING.md
|
|
234
236
|
- Gemfile
|
|
@@ -296,14 +298,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
296
298
|
requirements:
|
|
297
299
|
- - ">="
|
|
298
300
|
- !ruby/object:Gem::Version
|
|
299
|
-
version: '2.
|
|
301
|
+
version: '2.5'
|
|
300
302
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
301
303
|
requirements:
|
|
302
304
|
- - ">="
|
|
303
305
|
- !ruby/object:Gem::Version
|
|
304
306
|
version: '0'
|
|
305
307
|
requirements: []
|
|
306
|
-
rubygems_version: 3.
|
|
308
|
+
rubygems_version: 3.2.22
|
|
307
309
|
signing_key:
|
|
308
310
|
specification_version: 4
|
|
309
311
|
summary: A statesman-like state machine library
|