statesman 4.1.3 → 4.1.4
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 +5 -5
- data/CHANGELOG.md +4 -0
- data/README.md +38 -23
- data/lib/statesman/adapters/active_record_queries.rb +34 -96
- data/lib/statesman/version.rb +1 -1
- data/spec/statesman/adapters/active_record_queries_spec.rb +125 -135
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7445e509f7b9bafc381522bfbdbedac811430da81375bb767472f92386218140
|
4
|
+
data.tar.gz: 176143a22965d91c30957bfa7744089947e4269046fac188b89550d737b7cbb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afec82cb24e537055105b34d1468f55b4fc220d7dffe49627910fdc7be7e61fbeb044b2e36f53bb111a9c7aeab23cc77b7828c0ac8b3bba167ac461246becc40
|
7
|
+
data.tar.gz: d98cf49966c4bb635b98798b44ecab0e5ad77714c794580eeb13939cbff7ab7dfd308de8041e6b95a0a1b56e5e364813896ade88b50422a87ecb00b7294745c0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## v4.1.4, 11th November 2019
|
2
|
+
|
3
|
+
- Reverts the breaking changes from [PR#358](https://github.com/gocardless/statesman/pull/358) & `v4.1.3` that where included in the last minor release. If you have changed your code to work with these changes `v5.0.0` will be a copy of `v4.1.3` with a bugfix applied.
|
4
|
+
|
1
5
|
## v4.1.3, 6th November 2019
|
2
6
|
|
3
7
|
- Add accessible from / to state attributes on the `TransitionFailedError` to avoid parsing strings [@ahjmorton](https://github.com/gocardless/statesman/pull/367)
|
data/README.md
CHANGED
@@ -30,7 +30,7 @@ protection.
|
|
30
30
|
To get started, just add Statesman to your `Gemfile`, and then run `bundle`:
|
31
31
|
|
32
32
|
```ruby
|
33
|
-
gem 'statesman', '~>
|
33
|
+
gem 'statesman', '~> 4.1.4'
|
34
34
|
```
|
35
35
|
|
36
36
|
## Usage
|
@@ -76,16 +76,22 @@ Then, link it to your model:
|
|
76
76
|
|
77
77
|
```ruby
|
78
78
|
class Order < ActiveRecord::Base
|
79
|
-
|
79
|
+
include Statesman::Adapters::ActiveRecordQueries
|
80
80
|
|
81
|
-
|
82
|
-
transition_class: OrderTransition,
|
83
|
-
initial_state: :pending
|
84
|
-
]
|
81
|
+
has_many :order_transitions, autosave: false
|
85
82
|
|
86
83
|
def state_machine
|
87
84
|
@state_machine ||= OrderStateMachine.new(self, transition_class: OrderTransition)
|
88
85
|
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
|
89
95
|
end
|
90
96
|
```
|
91
97
|
|
@@ -351,34 +357,43 @@ callback code throws an exception, it will not be caught.)
|
|
351
357
|
|
352
358
|
A mixin is provided for the ActiveRecord adapter which adds scopes to easily
|
353
359
|
find all models currently in (or not in) a given state. Include it into your
|
354
|
-
model and
|
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.
|
360
|
+
model and define `transition_class` and `initial_state` class methods:
|
360
361
|
|
361
362
|
```ruby
|
362
363
|
class Order < ActiveRecord::Base
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
364
|
+
include Statesman::Adapters::ActiveRecordQueries
|
365
|
+
|
366
|
+
def self.transition_class
|
367
|
+
OrderTransition
|
368
|
+
end
|
369
|
+
private_class_method :transition_class
|
370
|
+
|
371
|
+
def self.initial_state
|
372
|
+
OrderStateMachine.initial_state
|
373
|
+
end
|
374
|
+
private_class_method :initial_state
|
368
375
|
end
|
369
376
|
```
|
370
377
|
|
371
378
|
If the transition class-name differs from the association name, you will also
|
372
|
-
need to
|
379
|
+
need to define a corresponding `transition_name` class method:
|
373
380
|
|
374
381
|
```ruby
|
375
382
|
class Order < ActiveRecord::Base
|
376
383
|
has_many :transitions, class_name: "OrderTransition", autosave: false
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
384
|
+
|
385
|
+
def self.transition_name
|
386
|
+
:transitions
|
387
|
+
end
|
388
|
+
|
389
|
+
def self.transition_class
|
390
|
+
OrderTransition
|
391
|
+
end
|
392
|
+
|
393
|
+
def self.initial_state
|
394
|
+
OrderStateMachine.initial_state
|
395
|
+
end
|
396
|
+
private_class_method :initial_state
|
382
397
|
end
|
383
398
|
```
|
384
399
|
|
@@ -1,122 +1,51 @@
|
|
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
|
-
|
17
4
|
def self.included(base)
|
18
|
-
|
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
|
-
)
|
5
|
+
base.extend(ClassMethods)
|
28
6
|
end
|
29
7
|
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
8
|
+
module ClassMethods
|
9
|
+
def in_state(*states)
|
10
|
+
states = states.flatten.map(&:to_s)
|
51
11
|
|
52
|
-
|
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
|
12
|
+
joins(most_recent_transition_join).
|
13
|
+
where(states_where(most_recent_transition_alias, states), states)
|
61
14
|
end
|
62
15
|
|
63
|
-
def
|
64
|
-
|
65
|
-
states = states.flatten.map(&:to_s)
|
16
|
+
def not_in_state(*states)
|
17
|
+
states = states.flatten.map(&:to_s)
|
66
18
|
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
19
|
+
joins(most_recent_transition_join).
|
20
|
+
where("NOT (#{states_where(most_recent_transition_alias, states)})",
|
21
|
+
states)
|
101
22
|
end
|
102
23
|
|
103
24
|
def most_recent_transition_join
|
104
25
|
"LEFT OUTER JOIN #{model_table} AS #{most_recent_transition_alias}
|
105
|
-
ON #{
|
26
|
+
ON #{table_name}.id =
|
106
27
|
#{most_recent_transition_alias}.#{model_foreign_key}
|
107
28
|
AND #{most_recent_transition_alias}.most_recent = #{db_true}"
|
108
29
|
end
|
109
30
|
|
110
31
|
private
|
111
32
|
|
112
|
-
|
33
|
+
def transition_class
|
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
|
113
42
|
|
114
43
|
def transition_name
|
115
|
-
|
44
|
+
transition_class.table_name.to_sym
|
116
45
|
end
|
117
46
|
|
118
47
|
def transition_reflection
|
119
|
-
|
48
|
+
reflect_on_all_associations(:has_many).each do |value|
|
120
49
|
return value if value.klass == transition_class
|
121
50
|
end
|
122
51
|
|
@@ -133,9 +62,18 @@ module Statesman
|
|
133
62
|
transition_reflection.table_name
|
134
63
|
end
|
135
64
|
|
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
|
+
|
136
75
|
def most_recent_transition_alias
|
137
|
-
|
138
|
-
"most_recent_#{transition_name.to_s.singularize}"
|
76
|
+
"most_recent_#{transition_name.to_s.singularize}"
|
139
77
|
end
|
140
78
|
|
141
79
|
def db_true
|
data/lib/statesman/version.rb
CHANGED
@@ -1,17 +1,6 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
4
|
-
def configure_old(klass, transition_class)
|
5
|
-
klass.define_singleton_method(:transition_class) { transition_class }
|
6
|
-
klass.define_singleton_method(:initial_state) { :initial }
|
7
|
-
klass.send(:include, described_class)
|
8
|
-
end
|
9
|
-
|
10
|
-
def configure_new(klass, transition_class)
|
11
|
-
klass.send(:include, described_class[transition_class: transition_class,
|
12
|
-
initial_state: :initial])
|
13
|
-
end
|
14
|
-
|
15
4
|
before do
|
16
5
|
prepare_model_table
|
17
6
|
prepare_transitions_table
|
@@ -19,6 +8,32 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
19
8
|
prepare_other_transitions_table
|
20
9
|
|
21
10
|
Statesman.configure { storage_adapter(Statesman::Adapters::ActiveRecord) }
|
11
|
+
|
12
|
+
MyActiveRecordModel.send(:include, Statesman::Adapters::ActiveRecordQueries)
|
13
|
+
MyActiveRecordModel.class_eval do
|
14
|
+
def self.transition_class
|
15
|
+
MyActiveRecordModelTransition
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.initial_state
|
19
|
+
:initial
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
OtherActiveRecordModel.send(:include,
|
24
|
+
Statesman::Adapters::ActiveRecordQueries)
|
25
|
+
OtherActiveRecordModel.class_eval do
|
26
|
+
def self.transition_class
|
27
|
+
OtherActiveRecordModelTransition
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.initial_state
|
31
|
+
:initial
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
MyActiveRecordModel.send(:has_one, :other_active_record_model)
|
36
|
+
OtherActiveRecordModel.send(:belongs_to, :my_active_record_model)
|
22
37
|
end
|
23
38
|
|
24
39
|
after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
|
@@ -44,164 +59,105 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
44
59
|
model
|
45
60
|
end
|
46
61
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
configure_old(MyActiveRecordModel, MyActiveRecordModelTransition)
|
51
|
-
configure_old(OtherActiveRecordModel, OtherActiveRecordModelTransition)
|
52
|
-
elsif config_type == :new
|
53
|
-
configure_new(MyActiveRecordModel, MyActiveRecordModelTransition)
|
54
|
-
configure_new(OtherActiveRecordModel, OtherActiveRecordModelTransition)
|
55
|
-
else
|
56
|
-
raise "Unknown config type #{config_type}"
|
57
|
-
end
|
62
|
+
describe ".in_state" do
|
63
|
+
context "given a single state" do
|
64
|
+
subject { MyActiveRecordModel.in_state(:succeeded) }
|
58
65
|
|
59
|
-
|
60
|
-
|
66
|
+
it { is_expected.to include model }
|
67
|
+
it { is_expected.to_not include other_model }
|
61
68
|
end
|
62
69
|
|
63
|
-
|
64
|
-
|
65
|
-
subject { MyActiveRecordModel.in_state(:succeeded) }
|
66
|
-
|
67
|
-
it { is_expected.to include model }
|
68
|
-
it { is_expected.to_not include other_model }
|
69
|
-
end
|
70
|
+
context "given multiple states" do
|
71
|
+
subject { MyActiveRecordModel.in_state(:succeeded, :failed) }
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
it { is_expected.to include model }
|
75
|
-
it { is_expected.to include other_model }
|
76
|
-
end
|
77
|
-
|
78
|
-
context "given the initial state" do
|
79
|
-
subject { MyActiveRecordModel.in_state(:initial) }
|
80
|
-
|
81
|
-
it { is_expected.to include initial_state_model }
|
82
|
-
it { is_expected.to include returned_to_initial_model }
|
83
|
-
end
|
84
|
-
|
85
|
-
context "given an array of states" do
|
86
|
-
subject { MyActiveRecordModel.in_state(%i[succeeded failed]) }
|
87
|
-
|
88
|
-
it { is_expected.to include model }
|
89
|
-
it { is_expected.to include other_model }
|
90
|
-
end
|
73
|
+
it { is_expected.to include model }
|
74
|
+
it { is_expected.to include other_model }
|
75
|
+
end
|
91
76
|
|
92
|
-
|
93
|
-
|
94
|
-
MyActiveRecordModel.in_state(:succeeded).
|
95
|
-
joins(:other_active_record_model).
|
96
|
-
merge(OtherActiveRecordModel.in_state(:initial))
|
97
|
-
end
|
77
|
+
context "given the initial state" do
|
78
|
+
subject { MyActiveRecordModel.in_state(:initial) }
|
98
79
|
|
99
|
-
|
100
|
-
|
80
|
+
it { is_expected.to include initial_state_model }
|
81
|
+
it { is_expected.to include returned_to_initial_model }
|
101
82
|
end
|
102
83
|
|
103
|
-
|
104
|
-
|
105
|
-
subject { MyActiveRecordModel.not_in_state(:failed) }
|
106
|
-
|
107
|
-
it { is_expected.to include model }
|
108
|
-
it { is_expected.to_not include other_model }
|
109
|
-
end
|
84
|
+
context "given an array of states" do
|
85
|
+
subject { MyActiveRecordModel.in_state(%i[succeeded failed]) }
|
110
86
|
|
111
|
-
|
112
|
-
|
87
|
+
it { is_expected.to include model }
|
88
|
+
it { is_expected.to include other_model }
|
89
|
+
end
|
113
90
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
91
|
+
context "merging two queries" do
|
92
|
+
subject do
|
93
|
+
MyActiveRecordModel.in_state(:succeeded).
|
94
|
+
joins(:other_active_record_model).
|
95
|
+
merge(OtherActiveRecordModel.in_state(:initial))
|
118
96
|
end
|
119
97
|
|
120
|
-
|
121
|
-
subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
|
122
|
-
|
123
|
-
it do
|
124
|
-
expect(not_in_state).to match_array([initial_state_model,
|
125
|
-
returned_to_initial_model])
|
126
|
-
end
|
127
|
-
end
|
98
|
+
it { is_expected.to be_empty }
|
128
99
|
end
|
100
|
+
end
|
129
101
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
# relation with MyActiveRecordModelTransition doesn't interfere with
|
134
|
-
# this spec.
|
135
|
-
MyActiveRecordModel.send(:has_many,
|
136
|
-
:custom_name,
|
137
|
-
class_name: "OtherActiveRecordModelTransition")
|
138
|
-
|
139
|
-
MyActiveRecordModel.class_eval do
|
140
|
-
def self.transition_class
|
141
|
-
OtherActiveRecordModelTransition
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
describe ".in_state" do
|
147
|
-
subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
|
102
|
+
describe ".not_in_state" do
|
103
|
+
context "given a single state" do
|
104
|
+
subject { MyActiveRecordModel.not_in_state(:failed) }
|
148
105
|
|
149
|
-
|
150
|
-
|
106
|
+
it { is_expected.to include model }
|
107
|
+
it { is_expected.to_not include other_model }
|
151
108
|
end
|
152
109
|
|
153
|
-
context "
|
154
|
-
|
155
|
-
MyStateMachine.class_eval do
|
156
|
-
cattr_accessor(:after_commit_callback_executed) { false }
|
110
|
+
context "given multiple states" do
|
111
|
+
subject(:not_in_state) { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
|
157
112
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
after do
|
166
|
-
MyStateMachine.class_eval do
|
167
|
-
callbacks[:after_commit] = []
|
168
|
-
end
|
113
|
+
it do
|
114
|
+
expect(not_in_state).to match_array([initial_state_model,
|
115
|
+
returned_to_initial_model])
|
169
116
|
end
|
117
|
+
end
|
170
118
|
|
171
|
-
|
172
|
-
|
173
|
-
end
|
119
|
+
context "given an array of states" do
|
120
|
+
subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
|
174
121
|
|
175
|
-
# rubocop:disable RSpec/ExampleLength
|
176
122
|
it do
|
177
|
-
expect
|
178
|
-
|
179
|
-
model.state_machine.transition_to!(:succeeded)
|
180
|
-
raise ActiveRecord::Rollback
|
181
|
-
end
|
182
|
-
end.to_not change(MyStateMachine, :after_commit_callback_executed)
|
123
|
+
expect(not_in_state).to match_array([initial_state_model,
|
124
|
+
returned_to_initial_model])
|
183
125
|
end
|
184
|
-
# rubocop:enable RSpec/ExampleLength
|
185
126
|
end
|
186
127
|
end
|
187
128
|
|
188
|
-
context "
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
129
|
+
context "with a custom name for the transition association" do
|
130
|
+
before do
|
131
|
+
# Switch to using OtherActiveRecordModelTransition, so the existing
|
132
|
+
# relation with MyActiveRecordModelTransition doesn't interfere with
|
133
|
+
# this spec.
|
134
|
+
MyActiveRecordModel.send(:has_many,
|
135
|
+
:custom_name,
|
136
|
+
class_name: "OtherActiveRecordModelTransition")
|
137
|
+
|
138
|
+
MyActiveRecordModel.class_eval do
|
139
|
+
def self.transition_class
|
140
|
+
OtherActiveRecordModelTransition
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
193
144
|
|
194
|
-
|
195
|
-
|
145
|
+
describe ".in_state" do
|
146
|
+
subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
|
196
147
|
|
197
|
-
|
148
|
+
specify { expect { query }.to_not raise_error }
|
149
|
+
end
|
198
150
|
end
|
199
151
|
|
200
152
|
context "with no association with the transition class" do
|
201
153
|
before do
|
202
154
|
class UnknownModelTransition < OtherActiveRecordModelTransition; end
|
203
155
|
|
204
|
-
|
156
|
+
MyActiveRecordModel.class_eval do
|
157
|
+
def self.transition_class
|
158
|
+
UnknownModelTransition
|
159
|
+
end
|
160
|
+
end
|
205
161
|
end
|
206
162
|
|
207
163
|
describe ".in_state" do
|
@@ -212,4 +168,38 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
212
168
|
end
|
213
169
|
end
|
214
170
|
end
|
171
|
+
|
172
|
+
context "after_commit transactional integrity" do
|
173
|
+
before do
|
174
|
+
MyStateMachine.class_eval do
|
175
|
+
cattr_accessor(:after_commit_callback_executed) { false }
|
176
|
+
|
177
|
+
after_transition(from: :initial, to: :succeeded, after_commit: true) do
|
178
|
+
# This leaks state in a testable way if transactional integrity is broken.
|
179
|
+
MyStateMachine.after_commit_callback_executed = true
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
after do
|
185
|
+
MyStateMachine.class_eval do
|
186
|
+
callbacks[:after_commit] = []
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
let!(:model) do
|
191
|
+
MyActiveRecordModel.create
|
192
|
+
end
|
193
|
+
|
194
|
+
# rubocop:disable RSpec/ExampleLength
|
195
|
+
it do
|
196
|
+
expect do
|
197
|
+
ActiveRecord::Base.transaction do
|
198
|
+
model.state_machine.transition_to!(:succeeded)
|
199
|
+
raise ActiveRecord::Rollback
|
200
|
+
end
|
201
|
+
end.to_not change(MyStateMachine, :after_commit_callback_executed)
|
202
|
+
end
|
203
|
+
# rubocop:enable RSpec/ExampleLength
|
204
|
+
end
|
215
205
|
end
|
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.1.
|
4
|
+
version: 4.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GoCardless
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-11-
|
11
|
+
date: 2019-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ammeter
|
@@ -292,7 +292,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
292
292
|
version: '0'
|
293
293
|
requirements: []
|
294
294
|
rubyforge_project:
|
295
|
-
rubygems_version: 2.
|
295
|
+
rubygems_version: 2.7.6.2
|
296
296
|
signing_key:
|
297
297
|
specification_version: 4
|
298
298
|
summary: A statesman-like state machine library
|