statesman 7.4.0 → 8.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +6 -36
- data/CHANGELOG.md +41 -6
- data/Gemfile +2 -2
- data/README.md +77 -5
- data/lib/generators/statesman/generator_helpers.rb +10 -2
- data/lib/statesman/adapters/active_record.rb +19 -11
- data/lib/statesman/adapters/active_record_queries.rb +5 -1
- data/lib/statesman/adapters/memory.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 +25 -0
- data/spec/statesman/adapters/active_record_spec.rb +38 -0
- data/spec/statesman/machine_spec.rb +28 -0
- data/spec/support/active_record.rb +5 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d099ca7f60641163125a158ca189d41b571a46e4da8d5277a9391ff12df69e4
|
4
|
+
data.tar.gz: 5a33e698ded14cbc080f6d3e385d4d00d8191b5d1887e0b4fb062d592a35214f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b7e63cd44278ca20c9aac95b6ff1372b83131d5df988b72d6fabfbbc98057917f33bc6cdbc447c2d1d04f075ac215e58eb5e0beb09d0fd7d2d16e672cebacd2
|
7
|
+
data.tar.gz: 63588d5869538bab0f4a7900f2761ed3bcfb124895485d2fc1667ec159763e4fad69327af25d7221f113f6c43396aabb3d8548085d6bb25229487c5d19ba11c0
|
data/.circleci/config.yml
CHANGED
@@ -86,34 +86,6 @@ jobs:
|
|
86
86
|
- POSTGRES_DB=statesman_test
|
87
87
|
- POSTGRES_PASSWORD=statesman
|
88
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
|
117
89
|
|
118
90
|
build-ruby270-rails-602-mysql:
|
119
91
|
docker:
|
@@ -142,11 +114,11 @@ jobs:
|
|
142
114
|
- POSTGRES_DB=statesman_test
|
143
115
|
- POSTGRES_PASSWORD=statesman
|
144
116
|
steps: *steps
|
145
|
-
build-ruby270-rails-
|
117
|
+
build-ruby270-rails-main-mysql:
|
146
118
|
docker:
|
147
119
|
- image: circleci/ruby:2.7.0-node
|
148
120
|
environment:
|
149
|
-
- RAILS_VERSION=
|
121
|
+
- RAILS_VERSION=main
|
150
122
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
151
123
|
- DATABASE_DEPENDENCY_PORT=3306
|
152
124
|
- image: circleci/mysql:5.7.18
|
@@ -156,11 +128,11 @@ jobs:
|
|
156
128
|
- MYSQL_PASSWORD=
|
157
129
|
- MYSQL_DATABASE=statesman_test
|
158
130
|
steps: *steps
|
159
|
-
build-ruby270-rails-
|
131
|
+
build-ruby270-rails-main-postgres:
|
160
132
|
docker:
|
161
133
|
- image: circleci/ruby:2.7.0-node
|
162
134
|
environment:
|
163
|
-
- RAILS_VERSION=
|
135
|
+
- RAILS_VERSION=main
|
164
136
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
165
137
|
- EXCLUDE_MONGOID=true
|
166
138
|
- DATABASE_DEPENDENCY_PORT=5432
|
@@ -179,9 +151,7 @@ workflows:
|
|
179
151
|
- build-ruby249-rails-524-postgres
|
180
152
|
- build-ruby265-rails-602-mysql
|
181
153
|
- build-ruby265-rails-602-postgres
|
182
|
-
- build-ruby265-rails-master-mysql
|
183
|
-
- build-ruby265-rails-master-postgres
|
184
154
|
- build-ruby270-rails-602-mysql
|
185
155
|
- build-ruby270-rails-602-postgres
|
186
|
-
- build-ruby270-rails-
|
187
|
-
- build-ruby270-rails-
|
156
|
+
- build-ruby270-rails-main-mysql
|
157
|
+
- build-ruby270-rails-main-postgres
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,38 @@
|
|
1
|
+
## UNRELEASED
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- Implement `Machine#last_transition_to`, to find the last transition to a given state
|
5
|
+
[#xxx](https://github.com/gocardless/statesman/pull/xxx)
|
6
|
+
|
7
|
+
## v8.0.2 30th March 2021
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
|
11
|
+
- Fixed a bug where the `history` of a model was left in an incorrect state after a transition
|
12
|
+
conflict [#433](https://github.com/gocardless/statesman/pull/433)
|
13
|
+
|
14
|
+
## v8.0.1 20th January 2021
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
- Fixed `no implicit conversion of nil into String` error when quoting null values
|
19
|
+
[#427](https://github.com/gocardless/statesman/pull/427)
|
20
|
+
|
21
|
+
## v8.0.0 6th January 2021
|
22
|
+
|
23
|
+
### Added
|
24
|
+
|
25
|
+
- Use AR Arel table to type cast booleans in order to avoid deprecation warning [#421](https://github.com/gocardless/statesman/pull/421)
|
26
|
+
- Support relationships that doesn't use `id` as a Primary Key
|
27
|
+
[#422](https://github.com/gocardless/statesman/pull/422)
|
28
|
+
|
29
|
+
## v7.4.1 11th November 2020
|
30
|
+
|
31
|
+
### Added
|
32
|
+
|
33
|
+
- Add #reset method to state machine and adapter interfaces
|
34
|
+
[#417](https://github.com/gocardless/statesman/pull/417)
|
35
|
+
|
1
36
|
## v7.4.0 26th August 2020
|
2
37
|
|
3
38
|
### Added
|
@@ -9,16 +44,16 @@
|
|
9
44
|
|
10
45
|
### Changed
|
11
46
|
|
12
|
-
- Use correct Arel for null [#409](https://github.com/gocardless/statesman/pull
|
47
|
+
- Use correct Arel for null [#409](https://github.com/gocardless/statesman/pull/409)
|
13
48
|
|
14
49
|
## v7.2.0, 19th May 2020
|
15
50
|
|
16
51
|
### Changed
|
17
52
|
|
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
|
53
|
+
- Set non-empty password for postgres tests [#398](https://github.com/gocardless/statesman/pull/398)
|
54
|
+
- Handle transitions differently for MySQL [#399](https://github.com/gocardless/statesman/pull/399)
|
55
|
+
- pg requirement from >= 0.18, <= 1.1 to >= 0.18, <= 1.3 [#400](https://github.com/gocardless/statesman/pull/400)
|
56
|
+
- Lazily enable mysql gaplock protection [#402](https://github.com/gocardless/statesman/pull/402)
|
22
57
|
|
23
58
|
## v7.1.0, 10th Feb 2020
|
24
59
|
|
@@ -71,7 +106,7 @@
|
|
71
106
|
to
|
72
107
|
```ruby
|
73
108
|
include Statesman::Adapters::ActiveRecordQueries[
|
74
|
-
initial_state: :
|
109
|
+
initial_state: :initial,
|
75
110
|
transition_class: MyTransition
|
76
111
|
]
|
77
112
|
```
|
data/Gemfile
CHANGED
@@ -5,8 +5,8 @@ source 'https://rubygems.org'
|
|
5
5
|
gemspec
|
6
6
|
|
7
7
|
# rubocop:disable Bundler/DuplicatedGem
|
8
|
-
if ENV['RAILS_VERSION'] == '
|
9
|
-
gem "rails", git: "https://github.com/rails/rails"
|
8
|
+
if ENV['RAILS_VERSION'] == 'main'
|
9
|
+
gem "rails", git: "https://github.com/rails/rails", branch: "main"
|
10
10
|
elsif ENV['RAILS_VERSION']
|
11
11
|
gem "rails", "~> #{ENV['RAILS_VERSION']}"
|
12
12
|
end
|
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
|
@@ -67,6 +73,10 @@ module Statesman
|
|
67
73
|
end
|
68
74
|
end
|
69
75
|
|
76
|
+
def reset
|
77
|
+
@last_transition = nil
|
78
|
+
end
|
79
|
+
|
70
80
|
private
|
71
81
|
|
72
82
|
# rubocop:disable Metrics/MethodLength
|
@@ -301,25 +311,23 @@ module Statesman
|
|
301
311
|
end
|
302
312
|
|
303
313
|
def db_true
|
304
|
-
|
305
|
-
true,
|
306
|
-
transition_class.columns_hash["most_recent"],
|
307
|
-
)
|
308
|
-
::ActiveRecord::Base.connection.quote(value)
|
314
|
+
::ActiveRecord::Base.connection.quote(type_cast(true))
|
309
315
|
end
|
310
316
|
|
311
317
|
def db_false
|
312
|
-
|
313
|
-
false,
|
314
|
-
transition_class.columns_hash["most_recent"],
|
315
|
-
)
|
316
|
-
::ActiveRecord::Base.connection.quote(value)
|
318
|
+
::ActiveRecord::Base.connection.quote(type_cast(false))
|
317
319
|
end
|
318
320
|
|
319
321
|
def db_null
|
320
322
|
Arel::Nodes::SqlLiteral.new("NULL")
|
321
323
|
end
|
322
324
|
|
325
|
+
# Type casting against a column is deprecated and will be removed in Rails 6.2.
|
326
|
+
# See https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11
|
327
|
+
def type_cast(value)
|
328
|
+
::ActiveRecord::Base.connection.type_cast(value)
|
329
|
+
end
|
330
|
+
|
323
331
|
# Check whether the `most_recent` column allows null values. If it doesn't, set old
|
324
332
|
# records to `false`, otherwise, set them to `NULL`.
|
325
333
|
#
|
@@ -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/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
@@ -154,6 +154,31 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|
157
|
+
context "with a custom primary key for the model" do
|
158
|
+
before do
|
159
|
+
# Switch to using OtherActiveRecordModelTransition, so the existing
|
160
|
+
# relation with MyActiveRecordModelTransition doesn't interfere with
|
161
|
+
# this spec.
|
162
|
+
# Configure the relationship to use a different primary key,
|
163
|
+
MyActiveRecordModel.send(:has_many,
|
164
|
+
:custom_name,
|
165
|
+
class_name: "OtherActiveRecordModelTransition",
|
166
|
+
primary_key: :external_id)
|
167
|
+
|
168
|
+
MyActiveRecordModel.class_eval do
|
169
|
+
def self.transition_class
|
170
|
+
OtherActiveRecordModelTransition
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe ".in_state" do
|
176
|
+
subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
|
177
|
+
|
178
|
+
specify { expect { query }.to_not raise_error }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
157
182
|
context "after_commit transactional integrity" do
|
158
183
|
before do
|
159
184
|
MyStateMachine.class_eval do
|
@@ -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)
|
@@ -537,6 +537,34 @@ describe Statesman::Machine do
|
|
537
537
|
end
|
538
538
|
end
|
539
539
|
|
540
|
+
describe "#last_transition_to" do
|
541
|
+
subject { instance.last_transition_to(:y) }
|
542
|
+
|
543
|
+
before do
|
544
|
+
machine.class_eval do
|
545
|
+
state :x, initial: true
|
546
|
+
state :y
|
547
|
+
state :z
|
548
|
+
transition from: :x, to: :y
|
549
|
+
transition from: :y, to: :z
|
550
|
+
transition from: :z, to: :y
|
551
|
+
end
|
552
|
+
|
553
|
+
instance.transition_to!(:y)
|
554
|
+
instance.transition_to!(:z)
|
555
|
+
end
|
556
|
+
|
557
|
+
let(:instance) { machine.new(my_model) }
|
558
|
+
|
559
|
+
it { is_expected.to have_attributes(to_state: "y") }
|
560
|
+
|
561
|
+
context "when there are 2 transitions to the state" do
|
562
|
+
before { instance.transition_to!(:y) }
|
563
|
+
|
564
|
+
it { is_expected.to eq(instance.last_transition) }
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
540
568
|
describe "#can_transition_to?" do
|
541
569
|
subject(:can_transition_to?) { instance.can_transition_to?(new_state, metadata) }
|
542
570
|
|
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: 8.0.3
|
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: 2021-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ammeter
|
@@ -303,7 +303,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
303
303
|
- !ruby/object:Gem::Version
|
304
304
|
version: '0'
|
305
305
|
requirements: []
|
306
|
-
rubygems_version: 3.1.
|
306
|
+
rubygems_version: 3.1.2
|
307
307
|
signing_key:
|
308
308
|
specification_version: 4
|
309
309
|
summary: A statesman-like state machine library
|