statesman 9.0.0 → 13.0.0
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/.devcontainer/devcontainer.json +31 -0
- data/.devcontainer/docker-compose.yml +39 -0
- data/.github/workflows/tests.yml +130 -0
- data/.gitignore +65 -15
- data/.rspec +2 -0
- data/.rubocop.yml +11 -1
- data/.rubocop_todo.yml +23 -38
- data/.ruby-version +1 -1
- data/CHANGELOG.md +229 -43
- data/CONTRIBUTING.md +14 -13
- data/Gemfile +18 -3
- data/README.md +203 -74
- data/docs/COMPATIBILITY.md +3 -3
- data/lib/generators/statesman/active_record_transition_generator.rb +1 -1
- data/lib/generators/statesman/generator_helpers.rb +2 -2
- data/lib/statesman/adapters/active_record.rb +69 -52
- data/lib/statesman/adapters/active_record_queries.rb +15 -7
- data/lib/statesman/adapters/active_record_transition.rb +5 -1
- data/lib/statesman/adapters/memory.rb +1 -1
- data/lib/statesman/adapters/type_safe_active_record_queries.rb +21 -0
- data/lib/statesman/callback.rb +2 -2
- data/lib/statesman/config.rb +3 -10
- data/lib/statesman/exceptions.rb +9 -7
- data/lib/statesman/guard.rb +1 -1
- data/lib/statesman/machine.rb +60 -0
- data/lib/statesman/version.rb +1 -1
- data/lib/statesman.rb +5 -5
- data/lib/tasks/statesman.rake +5 -5
- data/spec/generators/statesman/active_record_transition_generator_spec.rb +7 -1
- data/spec/generators/statesman/migration_generator_spec.rb +5 -1
- data/spec/spec_helper.rb +44 -7
- data/spec/statesman/adapters/active_record_queries_spec.rb +8 -10
- data/spec/statesman/adapters/active_record_spec.rb +144 -55
- data/spec/statesman/adapters/active_record_transition_spec.rb +5 -2
- data/spec/statesman/adapters/memory_spec.rb +0 -1
- data/spec/statesman/adapters/memory_transition_spec.rb +0 -1
- data/spec/statesman/adapters/shared_examples.rb +6 -7
- data/spec/statesman/adapters/type_safe_active_record_queries_spec.rb +206 -0
- data/spec/statesman/callback_spec.rb +0 -2
- data/spec/statesman/config_spec.rb +0 -2
- data/spec/statesman/exceptions_spec.rb +8 -4
- data/spec/statesman/guard_spec.rb +0 -2
- data/spec/statesman/machine_spec.rb +231 -19
- data/spec/statesman/utils_spec.rb +0 -2
- data/spec/support/active_record.rb +156 -29
- data/spec/support/exactly_query_databases.rb +35 -0
- data/statesman.gemspec +2 -17
- metadata +14 -238
- data/.circleci/config.yml +0 -127
data/spec/spec_helper.rb
CHANGED
|
@@ -5,13 +5,14 @@ require "sqlite3"
|
|
|
5
5
|
require "mysql2"
|
|
6
6
|
require "pg"
|
|
7
7
|
require "active_record"
|
|
8
|
+
require "active_record/database_configurations"
|
|
8
9
|
# We have to include all of Rails to make rspec-rails work
|
|
9
10
|
require "rails"
|
|
10
11
|
require "action_view"
|
|
11
12
|
require "action_dispatch"
|
|
12
13
|
require "action_controller"
|
|
13
14
|
require "rspec/rails"
|
|
14
|
-
require "support/
|
|
15
|
+
require "support/exactly_query_databases"
|
|
15
16
|
require "rspec/its"
|
|
16
17
|
require "pry"
|
|
17
18
|
|
|
@@ -28,10 +29,31 @@ RSpec.configure do |config|
|
|
|
28
29
|
if config.exclusion_filter[:active_record]
|
|
29
30
|
puts "Skipping ActiveRecord tests"
|
|
30
31
|
else
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
|
|
33
|
+
|
|
34
|
+
# We have to parse this to a hash since ActiveRecord::Base.configurations
|
|
35
|
+
# will only consider a single URL config.
|
|
36
|
+
url_config = if ENV["DATABASE_URL"]
|
|
37
|
+
ActiveRecord::DatabaseConfigurations::ConnectionUrlResolver.
|
|
38
|
+
new(ENV["DATABASE_URL"]).to_hash.merge({ sslmode: "disable" })
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
db_config = {
|
|
42
|
+
current_env => {
|
|
43
|
+
primary: url_config || {
|
|
44
|
+
adapter: "sqlite3",
|
|
45
|
+
database: "/tmp/statesman.db",
|
|
46
|
+
},
|
|
47
|
+
secondary: url_config || {
|
|
48
|
+
adapter: "sqlite3",
|
|
49
|
+
database: "/tmp/statesman.db",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Connect to the primary database for activerecord tests.
|
|
55
|
+
ActiveRecord::Base.configurations = db_config
|
|
56
|
+
ActiveRecord::Base.establish_connection(:primary)
|
|
35
57
|
|
|
36
58
|
db_adapter = ActiveRecord::Base.connection.adapter_name
|
|
37
59
|
puts "Running with database adapter '#{db_adapter}'"
|
|
@@ -40,7 +62,9 @@ RSpec.configure do |config|
|
|
|
40
62
|
ActiveRecord::Migration.verbose = false
|
|
41
63
|
end
|
|
42
64
|
|
|
43
|
-
|
|
65
|
+
# Since our primary and secondary connections point to the same database, we don't
|
|
66
|
+
# need to worry about applying these actions to both.
|
|
67
|
+
config.before(:each, :active_record) do
|
|
44
68
|
tables = %w[
|
|
45
69
|
my_active_record_models
|
|
46
70
|
my_active_record_model_transitions
|
|
@@ -48,9 +72,12 @@ RSpec.configure do |config|
|
|
|
48
72
|
my_namespace_my_active_record_model_transitions
|
|
49
73
|
other_active_record_models
|
|
50
74
|
other_active_record_model_transitions
|
|
75
|
+
sti_active_record_models
|
|
76
|
+
sti_active_record_model_transitions
|
|
51
77
|
]
|
|
52
78
|
tables.each do |table_name|
|
|
53
79
|
sql = "DROP TABLE IF EXISTS #{table_name};"
|
|
80
|
+
|
|
54
81
|
ActiveRecord::Base.connection.execute(sql)
|
|
55
82
|
end
|
|
56
83
|
|
|
@@ -72,6 +99,16 @@ RSpec.configure do |config|
|
|
|
72
99
|
OtherActiveRecordModelTransition.reset_column_information
|
|
73
100
|
end
|
|
74
101
|
|
|
75
|
-
|
|
102
|
+
def prepare_sti_model_table
|
|
103
|
+
CreateStiActiveRecordModelMigration.migrate(:up)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def prepare_sti_transitions_table
|
|
107
|
+
CreateStiActiveRecordModelTransitionMigration.migrate(:up)
|
|
108
|
+
StiActiveRecordModelTransition.reset_column_information
|
|
109
|
+
end
|
|
76
110
|
end
|
|
77
111
|
end
|
|
112
|
+
|
|
113
|
+
# We have to require this after the databases are configured.
|
|
114
|
+
require "support/active_record"
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
3
|
+
describe Statesman::Adapters::ActiveRecordQueries, :active_record do
|
|
6
4
|
def configure_old(klass, transition_class)
|
|
7
5
|
klass.define_singleton_method(:transition_class) { transition_class }
|
|
8
6
|
klass.define_singleton_method(:initial_state) { :initial }
|
|
@@ -117,8 +115,8 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
|
117
115
|
subject(:not_in_state) { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
|
|
118
116
|
|
|
119
117
|
it do
|
|
120
|
-
expect(not_in_state).to
|
|
121
|
-
|
|
118
|
+
expect(not_in_state).to contain_exactly(initial_state_model,
|
|
119
|
+
returned_to_initial_model)
|
|
122
120
|
end
|
|
123
121
|
end
|
|
124
122
|
|
|
@@ -126,8 +124,8 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
|
126
124
|
subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
|
|
127
125
|
|
|
128
126
|
it do
|
|
129
|
-
expect(not_in_state).to
|
|
130
|
-
|
|
127
|
+
expect(not_in_state).to contain_exactly(initial_state_model,
|
|
128
|
+
returned_to_initial_model)
|
|
131
129
|
end
|
|
132
130
|
end
|
|
133
131
|
end
|
|
@@ -216,13 +214,13 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
|
216
214
|
context "using old configuration method" do
|
|
217
215
|
let(:config_type) { :old }
|
|
218
216
|
|
|
219
|
-
|
|
217
|
+
it_behaves_like "testing methods"
|
|
220
218
|
end
|
|
221
219
|
|
|
222
220
|
context "using new configuration method" do
|
|
223
221
|
let(:config_type) { :new }
|
|
224
222
|
|
|
225
|
-
|
|
223
|
+
it_behaves_like "testing methods"
|
|
226
224
|
end
|
|
227
225
|
|
|
228
226
|
context "with no association with the transition class" do
|
|
@@ -254,7 +252,7 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
|
254
252
|
end
|
|
255
253
|
|
|
256
254
|
it "does not raise an error" do
|
|
257
|
-
expect { check_missing_methods! }.to_not raise_exception
|
|
255
|
+
expect { check_missing_methods! }.to_not raise_exception
|
|
258
256
|
end
|
|
259
257
|
end
|
|
260
258
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "spec_helper"
|
|
4
3
|
require "timecop"
|
|
5
4
|
require "statesman/adapters/shared_examples"
|
|
6
5
|
require "statesman/exceptions"
|
|
7
6
|
|
|
8
|
-
describe Statesman::Adapters::ActiveRecord, active_record
|
|
7
|
+
describe Statesman::Adapters::ActiveRecord, :active_record do
|
|
9
8
|
before do
|
|
10
9
|
prepare_model_table
|
|
11
10
|
prepare_transitions_table
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
prepare_sti_model_table
|
|
13
|
+
prepare_sti_transitions_table
|
|
14
14
|
|
|
15
15
|
Statesman.configure do
|
|
16
16
|
# Rubocop requires described_class to be used, but this block
|
|
@@ -23,8 +23,10 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
23
23
|
|
|
24
24
|
after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
|
|
25
25
|
|
|
26
|
+
let(:model_class) { MyActiveRecordModel }
|
|
27
|
+
let(:transition_class) { MyActiveRecordModelTransition }
|
|
26
28
|
let(:observer) { double(Statesman::Machine, execute: nil) }
|
|
27
|
-
let(:model) {
|
|
29
|
+
let(:model) { model_class.create(current_state: :pending) }
|
|
28
30
|
|
|
29
31
|
it_behaves_like "an adapter", described_class, MyActiveRecordModelTransition
|
|
30
32
|
|
|
@@ -33,17 +35,11 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
33
35
|
before do
|
|
34
36
|
metadata_column = double
|
|
35
37
|
allow(metadata_column).to receive_messages(sql_type: "")
|
|
36
|
-
allow(MyActiveRecordModelTransition).
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
to receive(:type_for_attribute).with("metadata").
|
|
42
|
-
and_return(ActiveRecord::Type::Value.new)
|
|
43
|
-
else
|
|
44
|
-
expect(MyActiveRecordModelTransition).
|
|
45
|
-
to receive_messages(serialized_attributes: {})
|
|
46
|
-
end
|
|
38
|
+
allow(MyActiveRecordModelTransition).
|
|
39
|
+
to receive_messages(columns_hash: { "metadata" => metadata_column })
|
|
40
|
+
expect(MyActiveRecordModelTransition).
|
|
41
|
+
to receive(:type_for_attribute).with("metadata").
|
|
42
|
+
and_return(ActiveRecord::Type::Value.new)
|
|
47
43
|
end
|
|
48
44
|
|
|
49
45
|
it "raises an exception" do
|
|
@@ -60,10 +56,10 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
60
56
|
allow(metadata_column).to receive_messages(sql_type: "json")
|
|
61
57
|
allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
|
|
62
58
|
{ "metadata" => metadata_column })
|
|
63
|
-
if
|
|
64
|
-
|
|
65
|
-
serialized_type =
|
|
66
|
-
"",
|
|
59
|
+
if ActiveRecord.respond_to?(:gem_version) &&
|
|
60
|
+
ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
|
|
61
|
+
serialized_type = ActiveRecord::Type::Serialized.new(
|
|
62
|
+
"", ActiveRecord::Coders::JSON
|
|
67
63
|
)
|
|
68
64
|
expect(MyActiveRecordModelTransition).
|
|
69
65
|
to receive(:type_for_attribute).with("metadata").
|
|
@@ -88,18 +84,12 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
88
84
|
allow(metadata_column).to receive_messages(sql_type: "jsonb")
|
|
89
85
|
allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
|
|
90
86
|
{ "metadata" => metadata_column })
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
to receive(:type_for_attribute).with("metadata").
|
|
98
|
-
and_return(serialized_type)
|
|
99
|
-
else
|
|
100
|
-
expect(MyActiveRecordModelTransition).
|
|
101
|
-
to receive_messages(serialized_attributes: { "metadata" => "" })
|
|
102
|
-
end
|
|
87
|
+
serialized_type = ActiveRecord::Type::Serialized.new(
|
|
88
|
+
"", ActiveRecord::Coders::JSON
|
|
89
|
+
)
|
|
90
|
+
expect(MyActiveRecordModelTransition).
|
|
91
|
+
to receive(:type_for_attribute).with("metadata").
|
|
92
|
+
and_return(serialized_type)
|
|
103
93
|
end
|
|
104
94
|
|
|
105
95
|
it "raises an exception" do
|
|
@@ -112,15 +102,17 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
112
102
|
end
|
|
113
103
|
|
|
114
104
|
describe "#create" do
|
|
115
|
-
subject {
|
|
105
|
+
subject(:transition) { create }
|
|
116
106
|
|
|
117
|
-
let!(:adapter)
|
|
118
|
-
described_class.new(MyActiveRecordModelTransition, model, observer)
|
|
119
|
-
end
|
|
107
|
+
let!(:adapter) { described_class.new(transition_class, model, observer) }
|
|
120
108
|
let(:from) { :x }
|
|
121
109
|
let(:to) { :y }
|
|
122
110
|
let(:create) { adapter.create(from, to) }
|
|
123
111
|
|
|
112
|
+
it "only connects to the primary database" do
|
|
113
|
+
expect { create }.to exactly_query_databases({ primary: [:writing] })
|
|
114
|
+
end
|
|
115
|
+
|
|
124
116
|
context "when there is a race" do
|
|
125
117
|
it "raises a TransitionConflictError" do
|
|
126
118
|
adapter2 = adapter.dup
|
|
@@ -128,7 +120,8 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
128
120
|
adapter.last
|
|
129
121
|
adapter2.create(:y, :z)
|
|
130
122
|
expect { adapter.create(:y, :z) }.
|
|
131
|
-
to raise_exception(Statesman::TransitionConflictError)
|
|
123
|
+
to raise_exception(Statesman::TransitionConflictError).
|
|
124
|
+
and exactly_query_databases({ primary: [:writing] })
|
|
132
125
|
end
|
|
133
126
|
|
|
134
127
|
it "does not pollute the state when the transition fails" do
|
|
@@ -165,27 +158,25 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
165
158
|
|
|
166
159
|
context "ActiveRecord::RecordNotUnique unrelated to this transition" do
|
|
167
160
|
let(:error) do
|
|
168
|
-
if
|
|
169
|
-
|
|
161
|
+
if ActiveRecord.respond_to?(:gem_version) &&
|
|
162
|
+
ActiveRecord.gem_version >= Gem::Version.new("4.0.0")
|
|
170
163
|
ActiveRecord::RecordNotUnique.new("unrelated")
|
|
171
164
|
else
|
|
172
165
|
ActiveRecord::RecordNotUnique.new("unrelated", nil)
|
|
173
166
|
end
|
|
174
167
|
end
|
|
175
168
|
|
|
176
|
-
it {
|
|
169
|
+
it { expect { transition }.to raise_exception(ActiveRecord::RecordNotUnique) }
|
|
177
170
|
end
|
|
178
171
|
|
|
179
172
|
context "other errors" do
|
|
180
173
|
let(:error) { StandardError }
|
|
181
174
|
|
|
182
|
-
it {
|
|
175
|
+
it { expect { transition }.to raise_exception(StandardError) }
|
|
183
176
|
end
|
|
184
177
|
end
|
|
185
178
|
|
|
186
179
|
describe "updating the most_recent column" do
|
|
187
|
-
subject { create }
|
|
188
|
-
|
|
189
180
|
context "with no previous transition" do
|
|
190
181
|
its(:most_recent) { is_expected.to eq(true) }
|
|
191
182
|
end
|
|
@@ -302,18 +293,92 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
302
293
|
from(true).to be_falsey
|
|
303
294
|
end
|
|
304
295
|
end
|
|
296
|
+
|
|
297
|
+
context "when transition uses STI" do
|
|
298
|
+
let(:sti_model) { StiActiveRecordModel.create }
|
|
299
|
+
|
|
300
|
+
let(:adapter_a) do
|
|
301
|
+
described_class.new(
|
|
302
|
+
StiAActiveRecordModelTransition,
|
|
303
|
+
sti_model,
|
|
304
|
+
observer,
|
|
305
|
+
{ association_name: :sti_a_active_record_model_transitions },
|
|
306
|
+
)
|
|
307
|
+
end
|
|
308
|
+
let(:adapter_b) do
|
|
309
|
+
described_class.new(
|
|
310
|
+
StiBActiveRecordModelTransition,
|
|
311
|
+
sti_model,
|
|
312
|
+
observer,
|
|
313
|
+
{ association_name: :sti_b_active_record_model_transitions },
|
|
314
|
+
)
|
|
315
|
+
end
|
|
316
|
+
let(:create) { adapter_a.create(from, to) }
|
|
317
|
+
|
|
318
|
+
context "with a previous unrelated transition" do
|
|
319
|
+
let!(:transition_b) { adapter_b.create(from, to) }
|
|
320
|
+
|
|
321
|
+
its(:most_recent) { is_expected.to eq(true) }
|
|
322
|
+
|
|
323
|
+
it "doesn't update the previous transition's most_recent flag" do
|
|
324
|
+
expect { create }.
|
|
325
|
+
to_not(change { transition_b.reload.most_recent })
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
context "with previous related and unrelated transitions" do
|
|
330
|
+
let!(:transition_a) { adapter_a.create(from, to) }
|
|
331
|
+
let!(:transition_b) { adapter_b.create(from, to) }
|
|
332
|
+
|
|
333
|
+
its(:most_recent) { is_expected.to eq(true) }
|
|
334
|
+
|
|
335
|
+
it "updates the previous transition's most_recent flag" do
|
|
336
|
+
expect { create }.
|
|
337
|
+
to change { transition_a.reload.most_recent }.
|
|
338
|
+
from(true).to be_falsey
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
it "doesn't update the previous unrelated transition's most_recent flag" do
|
|
342
|
+
expect { create }.
|
|
343
|
+
to_not(change { transition_b.reload.most_recent })
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
305
347
|
end
|
|
306
|
-
end
|
|
307
348
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
349
|
+
context "when using the secondary database" do
|
|
350
|
+
let(:model_class) { SecondaryActiveRecordModel }
|
|
351
|
+
let(:transition_class) { SecondaryActiveRecordModelTransition }
|
|
352
|
+
|
|
353
|
+
it "doesn't connect to the primary database" do
|
|
354
|
+
expect { create }.to exactly_query_databases({ secondary: [:writing] })
|
|
355
|
+
expect(adapter.last.to_state).to eq("y")
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
context "when there is a race" do
|
|
359
|
+
it "raises a TransitionConflictError and uses the correct database" do
|
|
360
|
+
adapter2 = adapter.dup
|
|
361
|
+
adapter2.create(:x, :y)
|
|
362
|
+
adapter.last
|
|
363
|
+
adapter2.create(:y, :z)
|
|
364
|
+
|
|
365
|
+
expect { adapter.create(:y, :z) }.
|
|
366
|
+
to raise_exception(Statesman::TransitionConflictError).
|
|
367
|
+
and exactly_query_databases({ secondary: [:writing] })
|
|
368
|
+
end
|
|
369
|
+
end
|
|
311
370
|
end
|
|
371
|
+
end
|
|
312
372
|
|
|
313
|
-
|
|
373
|
+
describe "#last" do
|
|
374
|
+
let(:transition_class) { MyActiveRecordModelTransition }
|
|
375
|
+
let(:adapter) { described_class.new(transition_class, model, observer) }
|
|
314
376
|
|
|
315
377
|
context "with a previously looked up transition" do
|
|
316
|
-
before
|
|
378
|
+
before do
|
|
379
|
+
adapter.create(:x, :y)
|
|
380
|
+
adapter.last
|
|
381
|
+
end
|
|
317
382
|
|
|
318
383
|
it "caches the transition" do
|
|
319
384
|
expect_any_instance_of(MyActiveRecordModel).
|
|
@@ -325,8 +390,19 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
325
390
|
before { adapter.create(:y, :z, []) }
|
|
326
391
|
|
|
327
392
|
it "retrieves the new transition from the database" do
|
|
393
|
+
expect { adapter.last.to_state }.to exactly_query_databases({ primary: [:writing] })
|
|
328
394
|
expect(adapter.last.to_state).to eq("z")
|
|
329
395
|
end
|
|
396
|
+
|
|
397
|
+
context "when using the secondary database" do
|
|
398
|
+
let(:model_class) { SecondaryActiveRecordModel }
|
|
399
|
+
let(:transition_class) { SecondaryActiveRecordModelTransition }
|
|
400
|
+
|
|
401
|
+
it "retrieves the new transition from the database" do
|
|
402
|
+
expect { adapter.last.to_state }.to exactly_query_databases({ secondary: [:writing] })
|
|
403
|
+
expect(adapter.last.to_state).to eq("z")
|
|
404
|
+
end
|
|
405
|
+
end
|
|
330
406
|
end
|
|
331
407
|
|
|
332
408
|
context "when a new transition has been created elsewhere" do
|
|
@@ -369,15 +445,32 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
369
445
|
end
|
|
370
446
|
|
|
371
447
|
context "with a pre-fetched transition history" do
|
|
372
|
-
before
|
|
373
|
-
|
|
374
|
-
|
|
448
|
+
before do
|
|
449
|
+
adapter.create(:x, :y)
|
|
450
|
+
model.my_active_record_model_transitions.load_target
|
|
451
|
+
end
|
|
375
452
|
|
|
376
453
|
it "doesn't query the database" do
|
|
377
454
|
expect(MyActiveRecordModelTransition).to_not receive(:connection)
|
|
378
455
|
expect(adapter.last.to_state).to eq("y")
|
|
379
456
|
end
|
|
380
457
|
end
|
|
458
|
+
|
|
459
|
+
context "without previous transitions" do
|
|
460
|
+
it "does query the database only once" do
|
|
461
|
+
expect(model.my_active_record_model_transitions).
|
|
462
|
+
to receive(:order).once.and_call_original
|
|
463
|
+
|
|
464
|
+
expect(adapter.last).to eq(nil)
|
|
465
|
+
expect(adapter.last).to eq(nil)
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
describe "#reset" do
|
|
471
|
+
it "works with empty cache" do
|
|
472
|
+
expect { model.state_machine.reset }.to_not raise_error
|
|
473
|
+
end
|
|
381
474
|
end
|
|
382
475
|
|
|
383
476
|
it "resets last with #reload" do
|
|
@@ -399,10 +492,6 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
|
399
492
|
CreateNamespacedARModelTransitionMigration.migrate(:up)
|
|
400
493
|
end
|
|
401
494
|
|
|
402
|
-
before do
|
|
403
|
-
MyNamespace::MyActiveRecordModelTransition.serialize(:metadata, JSON)
|
|
404
|
-
end
|
|
405
|
-
|
|
406
495
|
let(:observer) { double(Statesman::Machine, execute: nil) }
|
|
407
496
|
let(:model) do
|
|
408
497
|
MyNamespace::MyActiveRecordModel.create(current_state: :pending)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "spec_helper"
|
|
4
3
|
require "json"
|
|
5
4
|
|
|
6
5
|
describe Statesman::Adapters::ActiveRecordTransition do
|
|
@@ -8,7 +7,11 @@ describe Statesman::Adapters::ActiveRecordTransition do
|
|
|
8
7
|
|
|
9
8
|
describe "including behaviour" do
|
|
10
9
|
it "calls Class.serialize" do
|
|
11
|
-
|
|
10
|
+
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("7.1")
|
|
11
|
+
expect(transition_class).to receive(:serialize).with(:metadata, coder: JSON).once
|
|
12
|
+
else
|
|
13
|
+
expect(transition_class).to receive(:serialize).with(:metadata, JSON).once
|
|
14
|
+
end
|
|
12
15
|
transition_class.send(:include, described_class)
|
|
13
16
|
end
|
|
14
17
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
|
|
5
3
|
# All adpators must define seven methods:
|
|
6
4
|
# initialize: Accepts a transition class, parent model and state_attr.
|
|
7
5
|
# transition_class: Returns the transition class object passed to initialize.
|
|
@@ -30,14 +28,14 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
|
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
describe "#create" do
|
|
33
|
-
subject {
|
|
31
|
+
subject(:transition) { create }
|
|
34
32
|
|
|
35
33
|
let(:from) { :x }
|
|
36
34
|
let(:to) { :y }
|
|
37
35
|
let(:there) { :z }
|
|
38
36
|
let(:create) { adapter.create(from, to) }
|
|
39
37
|
|
|
40
|
-
it {
|
|
38
|
+
it { expect { transition }.to change(adapter.history, :count).by(1) }
|
|
41
39
|
|
|
42
40
|
context "the new transition" do
|
|
43
41
|
subject(:instance) { create }
|
|
@@ -122,9 +120,10 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
|
|
|
122
120
|
describe "#last" do
|
|
123
121
|
subject { adapter.last }
|
|
124
122
|
|
|
125
|
-
before
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
before do
|
|
124
|
+
adapter.create(:x, :y)
|
|
125
|
+
adapter.create(:y, :z)
|
|
126
|
+
end
|
|
128
127
|
|
|
129
128
|
it { is_expected.to be_a(transition_class) }
|
|
130
129
|
specify { expect(adapter.last.to_state.to_sym).to eq(:z) }
|