statesman 4.1.2 → 4.1.3
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 +0 -23
- data/.rubocop_todo.yml +0 -2
- data/CHANGELOG.md +5 -0
- data/Gemfile +0 -2
- data/README.md +74 -44
- data/Rakefile +0 -4
- data/lib/statesman.rb +0 -3
- data/lib/statesman/adapters/active_record_queries.rb +96 -34
- data/lib/statesman/exceptions.rb +12 -1
- data/lib/statesman/machine.rb +20 -5
- data/lib/statesman/version.rb +1 -1
- data/spec/spec_helper.rb +1 -30
- data/spec/statesman/adapters/active_record_queries_spec.rb +135 -125
- data/spec/statesman/machine_spec.rb +57 -3
- data/statesman.gemspec +1 -1
- metadata +5 -15
- data/lib/generators/statesman/mongoid_transition_generator.rb +0 -25
- data/lib/generators/statesman/templates/mongoid_transition_model.rb.erb +0 -14
- data/lib/statesman/adapters/mongoid.rb +0 -66
- data/lib/statesman/adapters/mongoid_transition.rb +0 -10
- data/spec/generators/statesman/mongoid_transition_generator_spec.rb +0 -23
- data/spec/statesman/adapters/mongoid_spec.rb +0 -86
- data/spec/support/mongoid.rb +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ad6e72cd43e5b54c25a618524a51aa2029d50a60
|
|
4
|
+
data.tar.gz: dd974b992be7e1a764f1b0b58613fe1dbf2951f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ac91aa0a9d34410eda08fab8eded5fba9efc280617dad18eb93c6ed91c8ca46f8a34c3e13f5c7d5ca3797053551e4a514b9bcb1d6433cf7fb9ca2d29bf3da423
|
|
7
|
+
data.tar.gz: 6223d5ca89126a28905943f0b9e91bef5d5c1c2090e3effb6e78b3849a2c7c01c24069f1171307ae0fb4d3c2757dede80f6a04239f21c0bba7f50af1e6c80fe3
|
data/.circleci/config.yml
CHANGED
|
@@ -37,7 +37,6 @@ jobs:
|
|
|
37
37
|
environment:
|
|
38
38
|
- RAILS_VERSION=4.2.9
|
|
39
39
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
40
|
-
- EXCLUDE_MONGOID=true
|
|
41
40
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
42
41
|
- image: circleci/mysql:5.7.18
|
|
43
42
|
environment:
|
|
@@ -52,7 +51,6 @@ jobs:
|
|
|
52
51
|
environment:
|
|
53
52
|
- RAILS_VERSION=4.2.9
|
|
54
53
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
55
|
-
- EXCLUDE_MONGOID=true
|
|
56
54
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
57
55
|
- image: circleci/postgres:9.6
|
|
58
56
|
environment:
|
|
@@ -65,7 +63,6 @@ jobs:
|
|
|
65
63
|
environment:
|
|
66
64
|
- RAILS_VERSION=5.0.5
|
|
67
65
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
68
|
-
- EXCLUDE_MONGOID=true
|
|
69
66
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
70
67
|
- image: circleci/mysql:5.7.18
|
|
71
68
|
environment:
|
|
@@ -80,7 +77,6 @@ jobs:
|
|
|
80
77
|
environment:
|
|
81
78
|
- RAILS_VERSION=5.0.5
|
|
82
79
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
83
|
-
- EXCLUDE_MONGOID=true
|
|
84
80
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
85
81
|
- image: circleci/postgres:9.6
|
|
86
82
|
environment:
|
|
@@ -93,7 +89,6 @@ jobs:
|
|
|
93
89
|
environment:
|
|
94
90
|
- RAILS_VERSION=5.1.3
|
|
95
91
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
96
|
-
- EXCLUDE_MONGOID=true
|
|
97
92
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
98
93
|
- image: circleci/mysql:5.7.18
|
|
99
94
|
environment:
|
|
@@ -108,7 +103,6 @@ jobs:
|
|
|
108
103
|
environment:
|
|
109
104
|
- RAILS_VERSION=5.1.3
|
|
110
105
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
111
|
-
- EXCLUDE_MONGOID=true
|
|
112
106
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
113
107
|
- image: circleci/postgres:9.6
|
|
114
108
|
environment:
|
|
@@ -121,7 +115,6 @@ jobs:
|
|
|
121
115
|
environment:
|
|
122
116
|
- RAILS_VERSION=6.0.0
|
|
123
117
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
124
|
-
- EXCLUDE_MONGOID=true
|
|
125
118
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
126
119
|
- image: circleci/mysql:5.7.18
|
|
127
120
|
environment:
|
|
@@ -136,7 +129,6 @@ jobs:
|
|
|
136
129
|
environment:
|
|
137
130
|
- RAILS_VERSION=6.0.0
|
|
138
131
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
139
|
-
- EXCLUDE_MONGOID=true
|
|
140
132
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
141
133
|
- image: circleci/postgres:9.6
|
|
142
134
|
environment:
|
|
@@ -149,7 +141,6 @@ jobs:
|
|
|
149
141
|
environment:
|
|
150
142
|
- RAILS_VERSION=master
|
|
151
143
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
152
|
-
- EXCLUDE_MONGOID=true
|
|
153
144
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
154
145
|
- image: circleci/mysql:5.7.18
|
|
155
146
|
environment:
|
|
@@ -177,7 +168,6 @@ jobs:
|
|
|
177
168
|
environment:
|
|
178
169
|
- RAILS_VERSION=5.2.3
|
|
179
170
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
180
|
-
- EXCLUDE_MONGOID=true
|
|
181
171
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
182
172
|
- image: circleci/mysql:5.7.18
|
|
183
173
|
environment:
|
|
@@ -192,7 +182,6 @@ jobs:
|
|
|
192
182
|
environment:
|
|
193
183
|
- RAILS_VERSION=5.2.3
|
|
194
184
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
195
|
-
- EXCLUDE_MONGOID=true
|
|
196
185
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
197
186
|
- image: circleci/postgres:9.6
|
|
198
187
|
environment:
|
|
@@ -205,7 +194,6 @@ jobs:
|
|
|
205
194
|
environment:
|
|
206
195
|
- RAILS_VERSION=4.2.9
|
|
207
196
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
208
|
-
- EXCLUDE_MONGOID=true
|
|
209
197
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
210
198
|
- image: circleci/mysql:5.7.18
|
|
211
199
|
environment:
|
|
@@ -220,7 +208,6 @@ jobs:
|
|
|
220
208
|
environment:
|
|
221
209
|
- RAILS_VERSION=4.2.9
|
|
222
210
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
223
|
-
- EXCLUDE_MONGOID=true
|
|
224
211
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
225
212
|
- image: circleci/postgres:9.6
|
|
226
213
|
environment:
|
|
@@ -233,7 +220,6 @@ jobs:
|
|
|
233
220
|
environment:
|
|
234
221
|
- RAILS_VERSION=5.0.5
|
|
235
222
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
236
|
-
- EXCLUDE_MONGOID=true
|
|
237
223
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
238
224
|
- image: circleci/mysql:5.7.18
|
|
239
225
|
environment:
|
|
@@ -248,7 +234,6 @@ jobs:
|
|
|
248
234
|
environment:
|
|
249
235
|
- RAILS_VERSION=5.0.5
|
|
250
236
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
251
|
-
- EXCLUDE_MONGOID=true
|
|
252
237
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
253
238
|
- image: circleci/postgres:9.6
|
|
254
239
|
environment:
|
|
@@ -261,7 +246,6 @@ jobs:
|
|
|
261
246
|
environment:
|
|
262
247
|
- RAILS_VERSION=5.1.3
|
|
263
248
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
264
|
-
- EXCLUDE_MONGOID=true
|
|
265
249
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
266
250
|
- image: circleci/mysql:5.7.18
|
|
267
251
|
environment:
|
|
@@ -276,7 +260,6 @@ jobs:
|
|
|
276
260
|
environment:
|
|
277
261
|
- RAILS_VERSION=5.1.3
|
|
278
262
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
279
|
-
- EXCLUDE_MONGOID=true
|
|
280
263
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
281
264
|
- image: circleci/postgres:9.6
|
|
282
265
|
environment:
|
|
@@ -289,7 +272,6 @@ jobs:
|
|
|
289
272
|
environment:
|
|
290
273
|
- RAILS_VERSION=4.2.9
|
|
291
274
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
292
|
-
- EXCLUDE_MONGOID=true
|
|
293
275
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
294
276
|
- image: circleci/mysql:5.7.18
|
|
295
277
|
environment:
|
|
@@ -304,7 +286,6 @@ jobs:
|
|
|
304
286
|
environment:
|
|
305
287
|
- RAILS_VERSION=4.2.9
|
|
306
288
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
307
|
-
- EXCLUDE_MONGOID=true
|
|
308
289
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
309
290
|
- image: circleci/postgres:9.6
|
|
310
291
|
environment:
|
|
@@ -317,7 +298,6 @@ jobs:
|
|
|
317
298
|
environment:
|
|
318
299
|
- RAILS_VERSION=5.0.5
|
|
319
300
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
320
|
-
- EXCLUDE_MONGOID=true
|
|
321
301
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
322
302
|
- image: circleci/mysql:5.7.18
|
|
323
303
|
environment:
|
|
@@ -332,7 +312,6 @@ jobs:
|
|
|
332
312
|
environment:
|
|
333
313
|
- RAILS_VERSION=5.0.5
|
|
334
314
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
335
|
-
- EXCLUDE_MONGOID=true
|
|
336
315
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
337
316
|
- image: circleci/postgres:9.6
|
|
338
317
|
environment:
|
|
@@ -345,7 +324,6 @@ jobs:
|
|
|
345
324
|
environment:
|
|
346
325
|
- RAILS_VERSION=5.1.3
|
|
347
326
|
- DATABASE_URL=mysql2://root@127.0.0.1/statesman_test
|
|
348
|
-
- EXCLUDE_MONGOID=true
|
|
349
327
|
- DATABASE_DEPENDENCY_PORT=3306
|
|
350
328
|
- image: circleci/mysql:5.7.18
|
|
351
329
|
environment:
|
|
@@ -360,7 +338,6 @@ jobs:
|
|
|
360
338
|
environment:
|
|
361
339
|
- RAILS_VERSION=5.1.3
|
|
362
340
|
- DATABASE_URL=postgres://postgres@localhost/statesman_test
|
|
363
|
-
- EXCLUDE_MONGOID=true
|
|
364
341
|
- DATABASE_DEPENDENCY_PORT=5432
|
|
365
342
|
- image: circleci/postgres:9.6
|
|
366
343
|
environment:
|
data/.rubocop_todo.yml
CHANGED
|
@@ -85,7 +85,6 @@ RSpec/NestedGroups:
|
|
|
85
85
|
RSpec/ScatteredSetup:
|
|
86
86
|
Exclude:
|
|
87
87
|
- 'spec/statesman/adapters/active_record_spec.rb'
|
|
88
|
-
- 'spec/statesman/adapters/mongoid_spec.rb'
|
|
89
88
|
- 'spec/statesman/adapters/shared_examples.rb'
|
|
90
89
|
- 'spec/statesman/machine_spec.rb'
|
|
91
90
|
|
|
@@ -96,5 +95,4 @@ RSpec/VerifiedDoubles:
|
|
|
96
95
|
- 'spec/generators/statesman/active_record_transition_generator_spec.rb'
|
|
97
96
|
- 'spec/generators/statesman/migration_generator_spec.rb'
|
|
98
97
|
- 'spec/statesman/adapters/active_record_spec.rb'
|
|
99
|
-
- 'spec/statesman/adapters/mongoid_spec.rb'
|
|
100
98
|
- 'spec/statesman/adapters/shared_examples.rb'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## v4.1.3, 6th November 2019
|
|
2
|
+
|
|
3
|
+
- Add accessible from / to state attributes on the `TransitionFailedError` to avoid parsing strings [@ahjmorton](https://github.com/gocardless/statesman/pull/367)
|
|
4
|
+
- Add `after_transition_failure` mechanism [@credric-cordenier](https://github.com/gocardless/statesman/pull/366)
|
|
5
|
+
|
|
1
6
|
## v4.1.2, 17th August 2019
|
|
2
7
|
|
|
3
8
|
- Add support for Rails 6 [@greysteil](https://github.com/gocardless/statesman/pull/360)
|
data/Gemfile
CHANGED
|
@@ -11,8 +11,6 @@ end
|
|
|
11
11
|
# rubocop:enable Bundler/DuplicatedGem
|
|
12
12
|
|
|
13
13
|
group :development do
|
|
14
|
-
gem "mongoid", ">= 3.1" unless ENV["EXCLUDE_MONGOID"]
|
|
15
|
-
|
|
16
14
|
# test/unit is no longer bundled with Ruby 2.2, but required by Rails
|
|
17
15
|
gem "test-unit", "~> 3.0" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.0")
|
|
18
16
|
end
|
data/README.md
CHANGED
|
@@ -76,22 +76,16 @@ Then, link it to your model:
|
|
|
76
76
|
|
|
77
77
|
```ruby
|
|
78
78
|
class Order < ActiveRecord::Base
|
|
79
|
-
include Statesman::Adapters::ActiveRecordQueries
|
|
80
|
-
|
|
81
79
|
has_many :order_transitions, autosave: false
|
|
82
80
|
|
|
81
|
+
include Statesman::Adapters::ActiveRecordQueries[
|
|
82
|
+
transition_class: OrderTransition,
|
|
83
|
+
initial_state: :pending
|
|
84
|
+
]
|
|
85
|
+
|
|
83
86
|
def state_machine
|
|
84
87
|
@state_machine ||= OrderStateMachine.new(self, transition_class: OrderTransition)
|
|
85
88
|
end
|
|
86
|
-
|
|
87
|
-
def self.transition_class
|
|
88
|
-
OrderTransition
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def self.initial_state
|
|
92
|
-
:pending
|
|
93
|
-
end
|
|
94
|
-
private_class_method :initial_state
|
|
95
89
|
end
|
|
96
90
|
```
|
|
97
91
|
|
|
@@ -164,8 +158,9 @@ class Order < ActiveRecord::Base
|
|
|
164
158
|
end
|
|
165
159
|
|
|
166
160
|
# Optionally delegate some methods
|
|
167
|
-
|
|
168
|
-
|
|
161
|
+
|
|
162
|
+
delegate :can_transition_to?, :current_state, :history, :last_transition,
|
|
163
|
+
:transition_to!, :transition_to, :in_state?, to: :state_machine
|
|
169
164
|
end
|
|
170
165
|
```
|
|
171
166
|
#### Using PostgreSQL JSON column
|
|
@@ -199,13 +194,11 @@ or 5. To do that
|
|
|
199
194
|
```ruby
|
|
200
195
|
Statesman.configure do
|
|
201
196
|
storage_adapter(Statesman::Adapters::ActiveRecord)
|
|
202
|
-
# ...or
|
|
203
|
-
storage_adapter(Statesman::Adapters::Mongoid)
|
|
204
197
|
end
|
|
205
198
|
```
|
|
206
199
|
Statesman defaults to storing transitions in memory. If you're using rails, you
|
|
207
200
|
can instead configure it to persist transitions to the database by using the
|
|
208
|
-
ActiveRecord
|
|
201
|
+
ActiveRecord adapter.
|
|
209
202
|
|
|
210
203
|
Statesman will fallback to memory unless you specify a transition_class when instantiating your state machine. This allows you to only persist transitions on certain state machines in your app.
|
|
211
204
|
|
|
@@ -234,7 +227,8 @@ end
|
|
|
234
227
|
```
|
|
235
228
|
Define a guard. `to` and `from` parameters are optional, a nil parameter means
|
|
236
229
|
guard all transitions. The passed block should evaluate to a boolean and must
|
|
237
|
-
be idempotent as it could be called many times.
|
|
230
|
+
be idempotent as it could be called many times. The guard will pass when it
|
|
231
|
+
evaluates to a truthy value and fail when it evaluates to a falsey value (`nil` or `false`).
|
|
238
232
|
|
|
239
233
|
#### `Machine.before_transition`
|
|
240
234
|
```ruby
|
|
@@ -261,6 +255,35 @@ after the transition.
|
|
|
261
255
|
If you specify `after_commit: true`, the callback will be executed once the
|
|
262
256
|
transition has been committed to the database.
|
|
263
257
|
|
|
258
|
+
#### `Machine.after_transition_failure`
|
|
259
|
+
```ruby
|
|
260
|
+
Machine.after_transition_failure(from: :some_state, to: :another_state) do |object|
|
|
261
|
+
Logger.info("transition failed for #{object.id}")
|
|
262
|
+
end
|
|
263
|
+
```
|
|
264
|
+
Define a callback to run if `Statesman::TransitionFailedError` is raised
|
|
265
|
+
during the execution of transition callbacks. `to` and `from`
|
|
266
|
+
parameters are optional, a nil parameter means run after all transitions.
|
|
267
|
+
The model object is passed as an argument to the callback.
|
|
268
|
+
This is executed outside of the transaction wrapping other callbacks.
|
|
269
|
+
If using `transition!` the exception is re-raised after these callbacks are
|
|
270
|
+
executed.
|
|
271
|
+
|
|
272
|
+
#### `Machine.after_guard_failure`
|
|
273
|
+
```ruby
|
|
274
|
+
Machine.after_guard_failure(from: :some_state, to: :another_state) do |object|
|
|
275
|
+
Logger.info("guard failed for #{object.id}")
|
|
276
|
+
end
|
|
277
|
+
```
|
|
278
|
+
Define a callback to run if `Statesman::GuardFailedError` is raised
|
|
279
|
+
during the execution of guard callbacks. `to` and `from`
|
|
280
|
+
parameters are optional, a nil parameter means run after all transitions.
|
|
281
|
+
The model object is passed as an argument to the callback.
|
|
282
|
+
This is executed outside of the transaction wrapping other callbacks.
|
|
283
|
+
If using `transition!` the exception is re-raised after these callbacks are
|
|
284
|
+
executed.
|
|
285
|
+
|
|
286
|
+
|
|
264
287
|
#### `Machine.new`
|
|
265
288
|
```ruby
|
|
266
289
|
my_machine = Machine.new(my_model, transition_class: MyTransitionModel)
|
|
@@ -328,43 +351,34 @@ callback code throws an exception, it will not be caught.)
|
|
|
328
351
|
|
|
329
352
|
A mixin is provided for the ActiveRecord adapter which adds scopes to easily
|
|
330
353
|
find all models currently in (or not in) a given state. Include it into your
|
|
331
|
-
model and
|
|
354
|
+
model and passing in `transition_class` and `initial_state` as options.
|
|
355
|
+
|
|
356
|
+
In 4.1.1 and below, these two options had to be defined as methods on the model,
|
|
357
|
+
but 4.2.0 and above allow this style of configuration as well. The old method
|
|
358
|
+
pollutes the model with extra class methods, and is deprecated, to be removed
|
|
359
|
+
in 5.0.0.
|
|
332
360
|
|
|
333
361
|
```ruby
|
|
334
362
|
class Order < ActiveRecord::Base
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
private_class_method :transition_class
|
|
341
|
-
|
|
342
|
-
def self.initial_state
|
|
343
|
-
OrderStateMachine.initial_state
|
|
344
|
-
end
|
|
345
|
-
private_class_method :initial_state
|
|
363
|
+
has_many :order_transitions, autosave: false
|
|
364
|
+
include Statesman::Adapters::ActiveRecordQueries[
|
|
365
|
+
transition_class: OrderTransition,
|
|
366
|
+
initial_state: OrderStateMachine.initial_state
|
|
367
|
+
]
|
|
346
368
|
end
|
|
347
369
|
```
|
|
348
370
|
|
|
349
371
|
If the transition class-name differs from the association name, you will also
|
|
350
|
-
need to
|
|
372
|
+
need to pass `transition_name` as an option:
|
|
351
373
|
|
|
352
374
|
```ruby
|
|
353
375
|
class Order < ActiveRecord::Base
|
|
354
376
|
has_many :transitions, class_name: "OrderTransition", autosave: false
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
:
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def self.transition_class
|
|
361
|
-
OrderTransition
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
def self.initial_state
|
|
365
|
-
OrderStateMachine.initial_state
|
|
366
|
-
end
|
|
367
|
-
private_class_method :initial_state
|
|
377
|
+
include Statesman::Adapters::ActiveRecordQueries[
|
|
378
|
+
transition_class: OrderTransition,
|
|
379
|
+
initial_state: OrderStateMachine.initial_state,
|
|
380
|
+
transition_name: :transitions
|
|
381
|
+
]
|
|
368
382
|
end
|
|
369
383
|
```
|
|
370
384
|
|
|
@@ -375,7 +389,7 @@ Returns all models currently in any of the supplied states.
|
|
|
375
389
|
Returns all models not currently in any of the supplied states.
|
|
376
390
|
|
|
377
391
|
|
|
378
|
-
|
|
392
|
+
#### `Model.most_recent_transition_join`
|
|
379
393
|
This joins the model to its most recent transition whatever that may be.
|
|
380
394
|
We expose this method to ease use of ActiveRecord's `or` e.g
|
|
381
395
|
|
|
@@ -425,6 +439,22 @@ class OrderStateMachine
|
|
|
425
439
|
end
|
|
426
440
|
```
|
|
427
441
|
|
|
442
|
+
#### Deleting records.
|
|
443
|
+
|
|
444
|
+
If you need to delete the Parent model regularly you will need to change
|
|
445
|
+
either the association deletion behaviour or add a `DELETE CASCADE` condition
|
|
446
|
+
to foreign key in your database.
|
|
447
|
+
|
|
448
|
+
E.g
|
|
449
|
+
```
|
|
450
|
+
has_many :order_transitions, autosave: false, dependent: :destroy
|
|
451
|
+
```
|
|
452
|
+
or when migrating the transition model
|
|
453
|
+
```
|
|
454
|
+
add_foreign_key :order_transitions, :orders, on_delete: :cascade
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
|
|
428
458
|
## Testing Statesman Implementations
|
|
429
459
|
|
|
430
460
|
This answer was abstracted from [this issue](https://github.com/gocardless/statesman/issues/77).
|
data/Rakefile
CHANGED
|
@@ -4,10 +4,6 @@ require "rspec/core/rake_task"
|
|
|
4
4
|
RSpec::Core::RakeTask.new(:spec) do |task|
|
|
5
5
|
task.rspec_opts = []
|
|
6
6
|
|
|
7
|
-
if ENV["EXCLUDE_MONGOID"]
|
|
8
|
-
task.rspec_opts += ["--tag ~mongo", "--exclude-pattern **/*mongo*"]
|
|
9
|
-
end
|
|
10
|
-
|
|
11
7
|
if ENV["CIRCLECI"]
|
|
12
8
|
task.rspec_opts += ["--format RspecJunitFormatter",
|
|
13
9
|
"--out /tmp/test-results/rspec.xml",
|
data/lib/statesman.rb
CHANGED
|
@@ -12,9 +12,6 @@ module Statesman
|
|
|
12
12
|
"statesman/adapters/active_record_transition"
|
|
13
13
|
autoload :ActiveRecordQueries,
|
|
14
14
|
"statesman/adapters/active_record_queries"
|
|
15
|
-
autoload :Mongoid, "statesman/adapters/mongoid"
|
|
16
|
-
autoload :MongoidTransition,
|
|
17
|
-
"statesman/adapters/mongoid_transition"
|
|
18
15
|
end
|
|
19
16
|
require "statesman/railtie" if defined?(::Rails::Railtie)
|
|
20
17
|
|
|
@@ -1,51 +1,122 @@
|
|
|
1
1
|
module Statesman
|
|
2
2
|
module Adapters
|
|
3
3
|
module ActiveRecordQueries
|
|
4
|
+
def self.check_missing_methods!(base)
|
|
5
|
+
missing_methods = %i[transition_class initial_state].
|
|
6
|
+
reject { |_method| base.respond_to?(:method) }
|
|
7
|
+
return if missing_methods.none?
|
|
8
|
+
|
|
9
|
+
raise NotImplementedError,
|
|
10
|
+
"#{missing_methods.join(', ')} method(s) should be defined on " \
|
|
11
|
+
"the model. Alternatively, use the new form of `extend " \
|
|
12
|
+
"Statesman::Adapters::ActiveRecordQueries[" \
|
|
13
|
+
"transition_class: MyTransition, " \
|
|
14
|
+
"initial_state: :some_state]`"
|
|
15
|
+
end
|
|
16
|
+
|
|
4
17
|
def self.included(base)
|
|
5
|
-
base
|
|
18
|
+
check_missing_methods!(base)
|
|
19
|
+
|
|
20
|
+
base.include(
|
|
21
|
+
ClassMethods.new(
|
|
22
|
+
transition_class: base.transition_class,
|
|
23
|
+
initial_state: base.initial_state,
|
|
24
|
+
most_recent_transition_alias: base.try(:most_recent_transition_alias),
|
|
25
|
+
transition_name: base.try(:transition_name),
|
|
26
|
+
),
|
|
27
|
+
)
|
|
6
28
|
end
|
|
7
29
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
30
|
+
def self.[](**args)
|
|
31
|
+
ClassMethods.new(**args)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class ClassMethods < Module
|
|
35
|
+
def initialize(**args)
|
|
36
|
+
@args = args
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def included(base)
|
|
40
|
+
ensure_inheritance(base)
|
|
41
|
+
|
|
42
|
+
query_builder = QueryBuilder.new(base, **@args)
|
|
43
|
+
|
|
44
|
+
base.define_singleton_method(:most_recent_transition_join) do
|
|
45
|
+
query_builder.most_recent_transition_join
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
define_in_state(base, query_builder)
|
|
49
|
+
define_not_in_state(base, query_builder)
|
|
50
|
+
end
|
|
11
51
|
|
|
12
|
-
|
|
13
|
-
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def ensure_inheritance(base)
|
|
55
|
+
klass = self
|
|
56
|
+
existing_inherited = base.method(:inherited)
|
|
57
|
+
base.define_singleton_method(:inherited) do |subclass|
|
|
58
|
+
existing_inherited.call(subclass)
|
|
59
|
+
subclass.send(:include, klass)
|
|
60
|
+
end
|
|
14
61
|
end
|
|
15
62
|
|
|
16
|
-
def
|
|
17
|
-
|
|
63
|
+
def define_in_state(base, query_builder)
|
|
64
|
+
base.define_singleton_method(:in_state) do |*states|
|
|
65
|
+
states = states.flatten.map(&:to_s)
|
|
18
66
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
67
|
+
joins(most_recent_transition_join).
|
|
68
|
+
where(query_builder.states_where(states), states)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def define_not_in_state(base, query_builder)
|
|
73
|
+
base.define_singleton_method(:not_in_state) do |*states|
|
|
74
|
+
states = states.flatten.map(&:to_s)
|
|
75
|
+
|
|
76
|
+
joins(most_recent_transition_join).
|
|
77
|
+
where("NOT (#{query_builder.states_where(states)})", states)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class QueryBuilder
|
|
83
|
+
def initialize(model, transition_class:, initial_state:,
|
|
84
|
+
most_recent_transition_alias: nil,
|
|
85
|
+
transition_name: nil)
|
|
86
|
+
@model = model
|
|
87
|
+
@transition_class = transition_class
|
|
88
|
+
@initial_state = initial_state
|
|
89
|
+
@most_recent_transition_alias = most_recent_transition_alias
|
|
90
|
+
@transition_name = transition_name
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def states_where(states)
|
|
94
|
+
if initial_state.to_s.in?(states.map(&:to_s))
|
|
95
|
+
"#{most_recent_transition_alias}.to_state IN (?) OR " \
|
|
96
|
+
"#{most_recent_transition_alias}.to_state IS NULL"
|
|
97
|
+
else
|
|
98
|
+
"#{most_recent_transition_alias}.to_state IN (?) AND " \
|
|
99
|
+
"#{most_recent_transition_alias}.to_state IS NOT NULL"
|
|
100
|
+
end
|
|
22
101
|
end
|
|
23
102
|
|
|
24
103
|
def most_recent_transition_join
|
|
25
104
|
"LEFT OUTER JOIN #{model_table} AS #{most_recent_transition_alias}
|
|
26
|
-
ON #{table_name}.id =
|
|
105
|
+
ON #{model.table_name}.id =
|
|
27
106
|
#{most_recent_transition_alias}.#{model_foreign_key}
|
|
28
107
|
AND #{most_recent_transition_alias}.most_recent = #{db_true}"
|
|
29
108
|
end
|
|
30
109
|
|
|
31
110
|
private
|
|
32
111
|
|
|
33
|
-
|
|
34
|
-
raise NotImplementedError, "A transition_class method should be " \
|
|
35
|
-
"defined on the model"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def initial_state
|
|
39
|
-
raise NotImplementedError, "An initial_state method should be " \
|
|
40
|
-
"defined on the model"
|
|
41
|
-
end
|
|
112
|
+
attr_reader :model, :transition_class, :initial_state
|
|
42
113
|
|
|
43
114
|
def transition_name
|
|
44
|
-
transition_class.table_name.to_sym
|
|
115
|
+
@transition_name || transition_class.table_name.to_sym
|
|
45
116
|
end
|
|
46
117
|
|
|
47
118
|
def transition_reflection
|
|
48
|
-
reflect_on_all_associations(:has_many).each do |value|
|
|
119
|
+
model.reflect_on_all_associations(:has_many).each do |value|
|
|
49
120
|
return value if value.klass == transition_class
|
|
50
121
|
end
|
|
51
122
|
|
|
@@ -62,18 +133,9 @@ module Statesman
|
|
|
62
133
|
transition_reflection.table_name
|
|
63
134
|
end
|
|
64
135
|
|
|
65
|
-
def states_where(temporary_table_name, states)
|
|
66
|
-
if initial_state.to_s.in?(states.map(&:to_s))
|
|
67
|
-
"#{temporary_table_name}.to_state IN (?) OR " \
|
|
68
|
-
"#{temporary_table_name}.to_state IS NULL"
|
|
69
|
-
else
|
|
70
|
-
"#{temporary_table_name}.to_state IN (?) AND " \
|
|
71
|
-
"#{temporary_table_name}.to_state IS NOT NULL"
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
136
|
def most_recent_transition_alias
|
|
76
|
-
|
|
137
|
+
@most_recent_transition_alias ||
|
|
138
|
+
"most_recent_#{transition_name.to_s.singularize}"
|
|
77
139
|
end
|
|
78
140
|
|
|
79
141
|
def db_true
|