statesman 3.4.1 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +9 -9
- data/.rubocop_todo.yml +67 -2
- data/CHANGELOG.md +7 -0
- data/Gemfile +0 -4
- data/README.md +15 -1
- data/lib/generators/statesman/active_record_transition_generator.rb +1 -1
- data/lib/generators/statesman/migration_generator.rb +1 -1
- data/lib/generators/statesman/mongoid_transition_generator.rb +1 -1
- data/lib/generators/statesman/templates/create_migration.rb.erb +3 -3
- data/lib/generators/statesman/templates/update_migration.rb.erb +2 -2
- data/lib/statesman/adapters/active_record.rb +3 -3
- data/lib/statesman/adapters/active_record_queries.rb +7 -7
- data/lib/statesman/adapters/memory.rb +2 -2
- data/lib/statesman/adapters/mongoid.rb +1 -1
- data/lib/statesman/config.rb +0 -1
- data/lib/statesman/machine.rb +1 -1
- data/lib/statesman/version.rb +1 -1
- data/spec/generators/statesman/active_record_transition_generator_spec.rb +14 -9
- data/spec/generators/statesman/migration_generator_spec.rb +9 -9
- data/spec/generators/statesman/mongoid_transition_generator_spec.rb +7 -5
- data/spec/statesman/adapters/active_record_queries_spec.rb +17 -15
- data/spec/statesman/adapters/active_record_spec.rb +14 -4
- data/spec/statesman/adapters/memory_spec.rb +1 -0
- data/spec/statesman/{transition_spec.rb → adapters/memory_transition_spec.rb} +0 -0
- data/spec/statesman/adapters/mongoid_spec.rb +5 -1
- data/spec/statesman/adapters/shared_examples.rb +18 -9
- data/spec/statesman/callback_spec.rb +18 -6
- data/spec/statesman/config_spec.rb +6 -3
- data/spec/statesman/guard_spec.rb +3 -1
- data/spec/statesman/machine_spec.rb +52 -14
- data/spec/support/generators_shared_examples.rb +3 -1
- data/statesman.gemspec +12 -12
- metadata +23 -23
@@ -4,18 +4,20 @@ require "generators/statesman/mongoid_transition_generator"
|
|
4
4
|
|
5
5
|
describe Statesman::MongoidTransitionGenerator, type: :generator do
|
6
6
|
describe "the model contains the correct words" do
|
7
|
-
before { run_generator %w[Yummy::Bacon Yummy::BaconTransition] }
|
8
7
|
subject { file("app/models/yummy/bacon_transition.rb") }
|
9
8
|
|
10
|
-
|
9
|
+
before { run_generator %w[Yummy::Bacon Yummy::BaconTransition] }
|
10
|
+
|
11
|
+
it { is_expected.to_not contain(%r{:yummy/bacon}) }
|
11
12
|
it { is_expected.to contain(/class_name: 'Yummy::Bacon'/) }
|
12
13
|
end
|
13
14
|
|
14
15
|
describe "the model contains the correct words" do
|
15
|
-
before { run_generator %w[Bacon BaconTransition] }
|
16
16
|
subject { file("app/models/bacon_transition.rb") }
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
before { run_generator %w[Bacon BaconTransition] }
|
19
|
+
|
20
|
+
it { is_expected.to_not contain(/class_name:/) }
|
21
|
+
it { is_expected.to_not contain(/CreateYummy::Bacon/) }
|
20
22
|
end
|
21
23
|
end
|
@@ -6,14 +6,9 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
6
6
|
prepare_transitions_table
|
7
7
|
prepare_other_model_table
|
8
8
|
prepare_other_transitions_table
|
9
|
-
end
|
10
9
|
|
11
|
-
before do
|
12
10
|
Statesman.configure { storage_adapter(Statesman::Adapters::ActiveRecord) }
|
13
|
-
end
|
14
|
-
after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
|
15
11
|
|
16
|
-
before do
|
17
12
|
MyActiveRecordModel.send(:include, Statesman::Adapters::ActiveRecordQueries)
|
18
13
|
MyActiveRecordModel.class_eval do
|
19
14
|
def self.transition_class
|
@@ -36,9 +31,12 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
36
31
|
:initial
|
37
32
|
end
|
38
33
|
end
|
34
|
+
|
35
|
+
MyActiveRecordModel.send(:has_one, :other_active_record_model)
|
36
|
+
OtherActiveRecordModel.send(:belongs_to, :my_active_record_model)
|
39
37
|
end
|
40
|
-
|
41
|
-
|
38
|
+
|
39
|
+
after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
|
42
40
|
|
43
41
|
let!(:model) do
|
44
42
|
model = MyActiveRecordModel.create
|
@@ -66,7 +64,7 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
66
64
|
subject { MyActiveRecordModel.in_state(:succeeded) }
|
67
65
|
|
68
66
|
it { is_expected.to include model }
|
69
|
-
it { is_expected.
|
67
|
+
it { is_expected.to_not include other_model }
|
70
68
|
end
|
71
69
|
|
72
70
|
context "given multiple states" do
|
@@ -104,23 +102,26 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
104
102
|
describe ".not_in_state" do
|
105
103
|
context "given a single state" do
|
106
104
|
subject { MyActiveRecordModel.not_in_state(:failed) }
|
105
|
+
|
107
106
|
it { is_expected.to include model }
|
108
|
-
it { is_expected.
|
107
|
+
it { is_expected.to_not include other_model }
|
109
108
|
end
|
110
109
|
|
111
110
|
context "given multiple states" do
|
112
|
-
subject { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
|
111
|
+
subject(:not_in_state) { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
|
112
|
+
|
113
113
|
it do
|
114
|
-
|
115
|
-
|
114
|
+
expect(not_in_state).to match_array([initial_state_model,
|
115
|
+
returned_to_initial_model])
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
119
119
|
context "given an array of states" do
|
120
|
-
subject { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
|
120
|
+
subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
|
121
|
+
|
121
122
|
it do
|
122
|
-
|
123
|
-
|
123
|
+
expect(not_in_state).to match_array([initial_state_model,
|
124
|
+
returned_to_initial_model])
|
124
125
|
end
|
125
126
|
end
|
126
127
|
end
|
@@ -143,6 +144,7 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
143
144
|
|
144
145
|
describe ".in_state" do
|
145
146
|
subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
|
147
|
+
|
146
148
|
specify { expect { query }.to_not raise_error }
|
147
149
|
end
|
148
150
|
end
|
@@ -10,8 +10,10 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
before { MyActiveRecordModelTransition.serialize(:metadata, JSON) }
|
13
|
+
|
13
14
|
let(:observer) { double(Statesman::Machine, execute: nil) }
|
14
15
|
let(:model) { MyActiveRecordModel.create(current_state: :pending) }
|
16
|
+
|
15
17
|
it_behaves_like "an adapter", described_class, MyActiveRecordModelTransition
|
16
18
|
|
17
19
|
describe "#initialize" do
|
@@ -98,13 +100,14 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
98
100
|
end
|
99
101
|
|
100
102
|
describe "#create" do
|
103
|
+
subject { -> { create } }
|
104
|
+
|
101
105
|
let!(:adapter) do
|
102
106
|
described_class.new(MyActiveRecordModelTransition, model, observer)
|
103
107
|
end
|
104
108
|
let(:from) { :x }
|
105
109
|
let(:to) { :y }
|
106
110
|
let(:create) { adapter.create(from, to) }
|
107
|
-
subject { -> { create } }
|
108
111
|
|
109
112
|
context "when there is a race" do
|
110
113
|
it "raises a TransitionConflictError" do
|
@@ -132,11 +135,13 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
132
135
|
ActiveRecord::RecordNotUnique.new("unrelated", nil)
|
133
136
|
end
|
134
137
|
end
|
138
|
+
|
135
139
|
it { is_expected.to raise_exception(ActiveRecord::RecordNotUnique) }
|
136
140
|
end
|
137
141
|
|
138
142
|
context "other errors" do
|
139
143
|
let(:error) { StandardError }
|
144
|
+
|
140
145
|
it { is_expected.to raise_exception(StandardError) }
|
141
146
|
end
|
142
147
|
end
|
@@ -150,6 +155,7 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
150
155
|
|
151
156
|
context "with a previous transition" do
|
152
157
|
let!(:previous_transition) { adapter.create(from, to) }
|
158
|
+
|
153
159
|
its(:most_recent) { is_expected.to eq(true) }
|
154
160
|
|
155
161
|
it "updates the previous transition's most_recent flag" do
|
@@ -248,6 +254,7 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
248
254
|
context "with two previous transitions" do
|
249
255
|
let!(:previous_transition) { adapter.create(from, to) }
|
250
256
|
let!(:another_previous_transition) { adapter.create(from, to) }
|
257
|
+
|
251
258
|
its(:most_recent) { is_expected.to eq(true) }
|
252
259
|
|
253
260
|
it "updates the previous transition's most_recent flag" do
|
@@ -271,12 +278,13 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
271
278
|
|
272
279
|
it "caches the transition" do
|
273
280
|
expect_any_instance_of(MyActiveRecordModel).
|
274
|
-
|
281
|
+
to_not receive(:my_active_record_model_transitions)
|
275
282
|
adapter.last
|
276
283
|
end
|
277
284
|
|
278
285
|
context "after then creating a new transition" do
|
279
286
|
before { adapter.create(:y, :z, []) }
|
287
|
+
|
280
288
|
it "retrieves the new transition from the database" do
|
281
289
|
expect(adapter.last.to_state).to eq("z")
|
282
290
|
end
|
@@ -291,7 +299,7 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
291
299
|
alternate_adapter.create(:y, :z, [])
|
292
300
|
|
293
301
|
expect_any_instance_of(MyActiveRecordModel).
|
294
|
-
|
302
|
+
to_not receive(:my_active_record_model_transitions)
|
295
303
|
expect(adapter.last.to_state).to eq("y")
|
296
304
|
end
|
297
305
|
|
@@ -323,10 +331,11 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
323
331
|
|
324
332
|
context "with a pre-fetched transition history" do
|
325
333
|
before { adapter.create(:x, :y) }
|
334
|
+
|
326
335
|
before { model.my_active_record_model_transitions.load_target }
|
327
336
|
|
328
337
|
it "doesn't query the database" do
|
329
|
-
expect(MyActiveRecordModelTransition).
|
338
|
+
expect(MyActiveRecordModelTransition).to_not receive(:connection)
|
330
339
|
expect(adapter.last.to_state).to eq("y")
|
331
340
|
end
|
332
341
|
end
|
@@ -341,6 +350,7 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
341
350
|
before do
|
342
351
|
MyNamespace::MyActiveRecordModelTransition.serialize(:metadata, JSON)
|
343
352
|
end
|
353
|
+
|
344
354
|
let(:observer) { double(Statesman::Machine, execute: nil) }
|
345
355
|
let(:model) do
|
346
356
|
MyNamespace::MyActiveRecordModel.create(current_state: :pending)
|
File without changes
|
@@ -6,8 +6,10 @@ require "mongoid"
|
|
6
6
|
|
7
7
|
describe Statesman::Adapters::Mongoid, mongo: true do
|
8
8
|
after { Mongoid.purge! }
|
9
|
+
|
9
10
|
let(:observer) { double(Statesman::Machine, execute: nil) }
|
10
11
|
let(:model) { MyMongoidModel.create(current_state: :pending) }
|
12
|
+
|
11
13
|
it_behaves_like "an adapter", described_class, MyMongoidModelTransition
|
12
14
|
|
13
15
|
describe "#initialize" do
|
@@ -33,16 +35,18 @@ describe Statesman::Adapters::Mongoid, mongo: true do
|
|
33
35
|
|
34
36
|
context "with a previously looked up transition" do
|
35
37
|
before { adapter.create(:x, :y) }
|
38
|
+
|
36
39
|
before { adapter.last }
|
37
40
|
|
38
41
|
it "caches the transition" do
|
39
42
|
expect_any_instance_of(MyMongoidModel).
|
40
|
-
|
43
|
+
to_not receive(:my_mongoid_model_transitions)
|
41
44
|
adapter.last
|
42
45
|
end
|
43
46
|
|
44
47
|
context "and a new transition" do
|
45
48
|
before { adapter.create(:y, :z) }
|
49
|
+
|
46
50
|
it "retrieves the new transition from the database" do
|
47
51
|
expect(adapter.last.to_state).to eq("z")
|
48
52
|
end
|
@@ -13,8 +13,6 @@ require "spec_helper"
|
|
13
13
|
#
|
14
14
|
# NOTE This line cannot reasonably be shortened.
|
15
15
|
shared_examples_for "an adapter" do |adapter_class, transition_class, options = {}|
|
16
|
-
# rubocop:enable Metrics/LineLength
|
17
|
-
|
18
16
|
let(:observer) { double(Statesman::Machine, execute: nil) }
|
19
17
|
let(:adapter) do
|
20
18
|
adapter_class.new(transition_class,
|
@@ -23,26 +21,29 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
|
|
23
21
|
|
24
22
|
describe "#initialize" do
|
25
23
|
subject { adapter }
|
24
|
+
|
26
25
|
its(:transition_class) { is_expected.to be(transition_class) }
|
27
26
|
its(:parent_model) { is_expected.to be(model) }
|
28
27
|
its(:history) { is_expected.to eq([]) }
|
29
28
|
end
|
30
29
|
|
31
30
|
describe "#create" do
|
31
|
+
subject { -> { create } }
|
32
|
+
|
32
33
|
let(:from) { :x }
|
33
34
|
let(:to) { :y }
|
34
35
|
let(:there) { :z }
|
35
36
|
let(:create) { adapter.create(from, to) }
|
36
|
-
subject { -> { create } }
|
37
37
|
|
38
38
|
it { is_expected.to change(adapter.history, :count).by(1) }
|
39
39
|
|
40
40
|
context "the new transition" do
|
41
|
-
subject { create }
|
41
|
+
subject(:instance) { create }
|
42
|
+
|
42
43
|
it { is_expected.to be_a(transition_class) }
|
43
44
|
|
44
|
-
it "
|
45
|
-
expect(
|
45
|
+
it "has the initial state" do
|
46
|
+
expect(instance.to_state.to_sym).to eq(to)
|
46
47
|
end
|
47
48
|
|
48
49
|
context "with no previous transition" do
|
@@ -51,6 +52,7 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
|
|
51
52
|
|
52
53
|
context "with a previous transition" do
|
53
54
|
before { adapter.create(from, to) }
|
55
|
+
|
54
56
|
its(:sort_key) { is_expected.to be(20) }
|
55
57
|
end
|
56
58
|
end
|
@@ -87,33 +89,40 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
|
|
87
89
|
end
|
88
90
|
|
89
91
|
context "with metadata" do
|
90
|
-
let(:metadata) { { "some" => "hash" } }
|
91
92
|
subject { adapter.create(from, to, metadata) }
|
93
|
+
|
94
|
+
let(:metadata) { { "some" => "hash" } }
|
95
|
+
|
92
96
|
its(:metadata) { is_expected.to eq(metadata) }
|
93
97
|
end
|
94
98
|
end
|
95
99
|
|
96
100
|
describe "#history" do
|
97
101
|
subject { adapter.history }
|
102
|
+
|
98
103
|
it { is_expected.to eq([]) }
|
99
104
|
|
100
105
|
context "with transitions" do
|
101
106
|
let!(:transition) { adapter.create(:x, :y) }
|
107
|
+
|
102
108
|
it { is_expected.to eq([transition]) }
|
103
109
|
|
104
110
|
context "sorting" do
|
105
|
-
let!(:transition2) { adapter.create(:x, :y) }
|
106
111
|
subject { adapter.history }
|
107
112
|
|
113
|
+
let!(:transition2) { adapter.create(:x, :y) }
|
114
|
+
|
108
115
|
it { is_expected.to eq(adapter.history.sort_by(&:sort_key)) }
|
109
116
|
end
|
110
117
|
end
|
111
118
|
end
|
112
119
|
|
113
120
|
describe "#last" do
|
121
|
+
subject { adapter.last }
|
122
|
+
|
114
123
|
before { adapter.create(:x, :y) }
|
124
|
+
|
115
125
|
before { adapter.create(:y, :z) }
|
116
|
-
subject { adapter.last }
|
117
126
|
|
118
127
|
it { is_expected.to be_a(transition_class) }
|
119
128
|
specify { expect(adapter.last.to_state.to_sym).to eq(:z) }
|
@@ -3,7 +3,7 @@ require "spec_helper"
|
|
3
3
|
describe Statesman::Callback do
|
4
4
|
let(:cb_lambda) { -> {} }
|
5
5
|
let(:callback) do
|
6
|
-
|
6
|
+
described_class.new(from: nil, to: nil, callback: cb_lambda)
|
7
7
|
end
|
8
8
|
|
9
9
|
describe "#initialize" do
|
@@ -27,21 +27,24 @@ describe Statesman::Callback do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
describe "#applies_to" do
|
30
|
+
subject { callback.applies_to?(from: from, to: to) }
|
31
|
+
|
30
32
|
let(:callback) do
|
31
|
-
|
33
|
+
described_class.new(from: :x, to: :y, callback: cb_lambda)
|
32
34
|
end
|
33
|
-
subject { callback.applies_to?(from: from, to: to) }
|
34
35
|
|
35
36
|
context "with any from value" do
|
36
37
|
let(:from) { nil }
|
37
38
|
|
38
39
|
context "and an allowed to value" do
|
39
40
|
let(:to) { :y }
|
41
|
+
|
40
42
|
it { is_expected.to be_truthy }
|
41
43
|
end
|
42
44
|
|
43
45
|
context "and a disallowed to value" do
|
44
46
|
let(:to) { :a }
|
47
|
+
|
45
48
|
it { is_expected.to be_falsey }
|
46
49
|
end
|
47
50
|
end
|
@@ -51,17 +54,19 @@ describe Statesman::Callback do
|
|
51
54
|
|
52
55
|
context "and an allowed 'from' value" do
|
53
56
|
let(:from) { :x }
|
57
|
+
|
54
58
|
it { is_expected.to be_truthy }
|
55
59
|
end
|
56
60
|
|
57
61
|
context "and a disallowed 'from' value" do
|
58
62
|
let(:from) { :a }
|
63
|
+
|
59
64
|
it { is_expected.to be_falsey }
|
60
65
|
end
|
61
66
|
end
|
62
67
|
|
63
68
|
context "with any to and any from value on the callback" do
|
64
|
-
let(:callback) {
|
69
|
+
let(:callback) { described_class.new(callback: cb_lambda) }
|
65
70
|
let(:from) { :x }
|
66
71
|
let(:to) { :y }
|
67
72
|
|
@@ -70,37 +75,42 @@ describe Statesman::Callback do
|
|
70
75
|
|
71
76
|
context "with any from value on the callback" do
|
72
77
|
let(:callback) do
|
73
|
-
|
78
|
+
described_class.new(to: %i[y z], callback: cb_lambda)
|
74
79
|
end
|
75
80
|
let(:from) { :x }
|
76
81
|
|
77
82
|
context "and an allowed to value" do
|
78
83
|
let(:to) { :y }
|
84
|
+
|
79
85
|
it { is_expected.to be_truthy }
|
80
86
|
end
|
81
87
|
|
82
88
|
context "and another allowed to value" do
|
83
89
|
let(:to) { :z }
|
90
|
+
|
84
91
|
it { is_expected.to be_truthy }
|
85
92
|
end
|
86
93
|
|
87
94
|
context "and a disallowed to value" do
|
88
95
|
let(:to) { :a }
|
96
|
+
|
89
97
|
it { is_expected.to be_falsey }
|
90
98
|
end
|
91
99
|
end
|
92
100
|
|
93
101
|
context "with any to value on the callback" do
|
94
|
-
let(:callback) {
|
102
|
+
let(:callback) { described_class.new(from: :x, callback: cb_lambda) }
|
95
103
|
let(:to) { :y }
|
96
104
|
|
97
105
|
context "and an allowed to value" do
|
98
106
|
let(:from) { :x }
|
107
|
+
|
99
108
|
it { is_expected.to be_truthy }
|
100
109
|
end
|
101
110
|
|
102
111
|
context "and a disallowed to value" do
|
103
112
|
let(:from) { :a }
|
113
|
+
|
104
114
|
it { is_expected.to be_falsey }
|
105
115
|
end
|
106
116
|
end
|
@@ -108,12 +118,14 @@ describe Statesman::Callback do
|
|
108
118
|
context "with allowed 'from' and 'to' values" do
|
109
119
|
let(:from) { :x }
|
110
120
|
let(:to) { :y }
|
121
|
+
|
111
122
|
it { is_expected.to be_truthy }
|
112
123
|
end
|
113
124
|
|
114
125
|
context "with disallowed 'from' and 'to' values" do
|
115
126
|
let(:from) { :a }
|
116
127
|
let(:to) { :b }
|
128
|
+
|
117
129
|
it { is_expected.to be_falsey }
|
118
130
|
end
|
119
131
|
end
|