statesman 10.2.0 → 10.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/statesman/adapters/active_record.rb +28 -10
- data/lib/statesman/adapters/active_record_queries.rb +8 -0
- data/lib/statesman/version.rb +1 -1
- data/spec/spec_helper.rb +11 -0
- data/spec/statesman/adapters/active_record_spec.rb +60 -0
- data/spec/statesman/machine_spec.rb +6 -6
- data/spec/support/active_record.rb +103 -12
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '07548e72198d3efbe3164700acee48b30c0fae61606b8e56304a078f8f2b7d37'
|
4
|
+
data.tar.gz: 750c6c7f3fa4f9099d64afffe2ed3faa55c6536e33da9b4dd14283e3a3fc39ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15a2d47316506e5e2345242a94e6aef09110f0944c2f8b2a4bb726027e45f500c7b51661093df8028db21b60f1afbf1bfc7792f37fb315edf7fad4291ef64712
|
7
|
+
data.tar.gz: 25bf0708566b48822316977e446fc543823ce702219c5584eb439b4f10e5bac51f08636e1fc207fc840fbe73c7d9dbe3b0a1c041923184bd5da6d5693033965b
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## v10.2.2 21st April 2023
|
2
|
+
|
3
|
+
### Changed
|
4
|
+
- Calling `active_record.reload` resets the adapater's internal cache
|
5
|
+
|
6
|
+
## v10.2.1 3rd April 2023
|
7
|
+
|
8
|
+
### Changed
|
9
|
+
- Fixed an edge case where `adapter.reset` were failing if the cache is empty
|
10
|
+
|
1
11
|
## v10.2.0 3rd April 2023
|
2
12
|
|
3
13
|
### Changed
|
@@ -52,7 +52,7 @@ module Statesman
|
|
52
52
|
|
53
53
|
raise
|
54
54
|
ensure
|
55
|
-
|
55
|
+
reset
|
56
56
|
end
|
57
57
|
|
58
58
|
def history(force_reload: false)
|
@@ -76,7 +76,9 @@ module Statesman
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def reset
|
79
|
-
|
79
|
+
if instance_variable_defined?(:@last_transition)
|
80
|
+
remove_instance_variable(:@last_transition)
|
81
|
+
end
|
80
82
|
end
|
81
83
|
|
82
84
|
private
|
@@ -157,13 +159,24 @@ module Statesman
|
|
157
159
|
|
158
160
|
def most_recent_transitions(most_recent_id = nil)
|
159
161
|
if most_recent_id
|
160
|
-
|
162
|
+
concrete_transitions_of_parent.and(
|
161
163
|
transition_table[:id].eq(most_recent_id).or(
|
162
164
|
transition_table[:most_recent].eq(true),
|
163
165
|
),
|
164
166
|
)
|
165
167
|
else
|
166
|
-
|
168
|
+
concrete_transitions_of_parent.and(transition_table[:most_recent].eq(true))
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def concrete_transitions_of_parent
|
173
|
+
if transition_sti?
|
174
|
+
transitions_of_parent.and(
|
175
|
+
transition_table[transition_class.inheritance_column].
|
176
|
+
eq(transition_class.name),
|
177
|
+
)
|
178
|
+
else
|
179
|
+
transitions_of_parent
|
167
180
|
end
|
168
181
|
end
|
169
182
|
|
@@ -262,13 +275,18 @@ module Statesman
|
|
262
275
|
end
|
263
276
|
end
|
264
277
|
|
265
|
-
def
|
266
|
-
|
267
|
-
|
268
|
-
reflect_on_all_associations(:has_many).
|
269
|
-
find { |r| r.name.to_s == @association_name.to_s }
|
278
|
+
def transition_sti?
|
279
|
+
transition_class.column_names.include?(transition_class.inheritance_column)
|
280
|
+
end
|
270
281
|
|
271
|
-
|
282
|
+
def parent_association
|
283
|
+
parent_model.class.
|
284
|
+
reflect_on_all_associations(:has_many).
|
285
|
+
find { |r| r.name.to_s == @association_name.to_s }
|
286
|
+
end
|
287
|
+
|
288
|
+
def parent_join_foreign_key
|
289
|
+
association_join_primary_key(parent_association)
|
272
290
|
end
|
273
291
|
|
274
292
|
def association_join_primary_key(association)
|
@@ -49,6 +49,14 @@ module Statesman
|
|
49
49
|
|
50
50
|
define_in_state(base, query_builder)
|
51
51
|
define_not_in_state(base, query_builder)
|
52
|
+
|
53
|
+
define_method(:reload) do |*a|
|
54
|
+
instance = super(*a)
|
55
|
+
if instance.respond_to?(:state_machine, true)
|
56
|
+
instance.state_machine.reset
|
57
|
+
end
|
58
|
+
instance
|
59
|
+
end
|
52
60
|
end
|
53
61
|
|
54
62
|
private
|
data/lib/statesman/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -48,6 +48,8 @@ RSpec.configure do |config|
|
|
48
48
|
my_namespace_my_active_record_model_transitions
|
49
49
|
other_active_record_models
|
50
50
|
other_active_record_model_transitions
|
51
|
+
sti_active_record_models
|
52
|
+
sti_active_record_model_transitions
|
51
53
|
]
|
52
54
|
tables.each do |table_name|
|
53
55
|
sql = "DROP TABLE IF EXISTS #{table_name};"
|
@@ -72,6 +74,15 @@ RSpec.configure do |config|
|
|
72
74
|
OtherActiveRecordModelTransition.reset_column_information
|
73
75
|
end
|
74
76
|
|
77
|
+
def prepare_sti_model_table
|
78
|
+
CreateStiActiveRecordModelMigration.migrate(:up)
|
79
|
+
end
|
80
|
+
|
81
|
+
def prepare_sti_transitions_table
|
82
|
+
CreateStiActiveRecordModelTransitionMigration.migrate(:up)
|
83
|
+
StiActiveRecordModelTransition.reset_column_information
|
84
|
+
end
|
85
|
+
|
75
86
|
MyNamespace::MyActiveRecordModelTransition.serialize(:metadata, JSON)
|
76
87
|
end
|
77
88
|
end
|
@@ -12,6 +12,9 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
12
12
|
|
13
13
|
MyActiveRecordModelTransition.serialize(:metadata, JSON)
|
14
14
|
|
15
|
+
prepare_sti_model_table
|
16
|
+
prepare_sti_transitions_table
|
17
|
+
|
15
18
|
Statesman.configure do
|
16
19
|
# Rubocop requires described_class to be used, but this block
|
17
20
|
# is instance_eval'd and described_class won't be defined
|
@@ -300,6 +303,57 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
300
303
|
from(true).to be_falsey
|
301
304
|
end
|
302
305
|
end
|
306
|
+
|
307
|
+
context "when transition uses STI" do
|
308
|
+
let(:sti_model) { StiActiveRecordModel.create }
|
309
|
+
|
310
|
+
let(:adapter_a) do
|
311
|
+
described_class.new(
|
312
|
+
StiAActiveRecordModelTransition,
|
313
|
+
sti_model,
|
314
|
+
observer,
|
315
|
+
{ association_name: :sti_a_active_record_model_transitions },
|
316
|
+
)
|
317
|
+
end
|
318
|
+
let(:adapter_b) do
|
319
|
+
described_class.new(
|
320
|
+
StiBActiveRecordModelTransition,
|
321
|
+
sti_model,
|
322
|
+
observer,
|
323
|
+
{ association_name: :sti_b_active_record_model_transitions },
|
324
|
+
)
|
325
|
+
end
|
326
|
+
let(:create) { adapter_a.create(from, to) }
|
327
|
+
|
328
|
+
context "with a previous unrelated transition" do
|
329
|
+
let!(:transition_b) { adapter_b.create(from, to) }
|
330
|
+
|
331
|
+
its(:most_recent) { is_expected.to eq(true) }
|
332
|
+
|
333
|
+
it "doesn't update the previous transition's most_recent flag" do
|
334
|
+
expect { create }.
|
335
|
+
to_not(change { transition_b.reload.most_recent })
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
context "with previous related and unrelated transitions" do
|
340
|
+
let!(:transition_a) { adapter_a.create(from, to) }
|
341
|
+
let!(:transition_b) { adapter_b.create(from, to) }
|
342
|
+
|
343
|
+
its(:most_recent) { is_expected.to eq(true) }
|
344
|
+
|
345
|
+
it "updates the previous transition's most_recent flag" do
|
346
|
+
expect { create }.
|
347
|
+
to change { transition_a.reload.most_recent }.
|
348
|
+
from(true).to be_falsey
|
349
|
+
end
|
350
|
+
|
351
|
+
it "doesn't update the previous unrelated transition's most_recent flag" do
|
352
|
+
expect { create }.
|
353
|
+
to_not(change { transition_b.reload.most_recent })
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
303
357
|
end
|
304
358
|
end
|
305
359
|
|
@@ -388,6 +442,12 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
388
442
|
end
|
389
443
|
end
|
390
444
|
|
445
|
+
describe "#reset" do
|
446
|
+
it "works with empty cache" do
|
447
|
+
expect { model.state_machine.reset }.to_not raise_error
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
391
451
|
it "resets last with #reload" do
|
392
452
|
model.save!
|
393
453
|
ActiveRecord::Base.transaction do
|
@@ -976,20 +976,20 @@ describe Statesman::Machine do
|
|
976
976
|
end
|
977
977
|
|
978
978
|
context "with defined callbacks" do
|
979
|
-
let(:
|
980
|
-
let(:
|
979
|
+
let(:callback_one) { -> { "Hi" } }
|
980
|
+
let(:callback_two) { -> { "Bye" } }
|
981
981
|
|
982
982
|
before do
|
983
|
-
machine.send(definer, from: :x, to: :y, &
|
984
|
-
machine.send(definer, from: :y, to: :z, &
|
983
|
+
machine.send(definer, from: :x, to: :y, &callback_one)
|
984
|
+
machine.send(definer, from: :y, to: :z, &callback_two)
|
985
985
|
end
|
986
986
|
|
987
987
|
it "contains the relevant callback" do
|
988
|
-
expect(callbacks.map(&:callback)).to include(
|
988
|
+
expect(callbacks.map(&:callback)).to include(callback_one)
|
989
989
|
end
|
990
990
|
|
991
991
|
it "does not contain the irrelevant callback" do
|
992
|
-
expect(callbacks.map(&:callback)).to_not include(
|
992
|
+
expect(callbacks.map(&:callback)).to_not include(callback_two)
|
993
993
|
end
|
994
994
|
end
|
995
995
|
end
|
@@ -20,10 +20,22 @@ class MyStateMachine
|
|
20
20
|
transition from: :failed, to: :initial
|
21
21
|
end
|
22
22
|
|
23
|
+
class MyActiveRecordModelTransition < ActiveRecord::Base
|
24
|
+
include Statesman::Adapters::ActiveRecordTransition
|
25
|
+
|
26
|
+
belongs_to :my_active_record_model
|
27
|
+
serialize :metadata, JSON
|
28
|
+
end
|
29
|
+
|
23
30
|
class MyActiveRecordModel < ActiveRecord::Base
|
24
31
|
has_many :my_active_record_model_transitions, autosave: false
|
25
32
|
alias_method :transitions, :my_active_record_model_transitions
|
26
33
|
|
34
|
+
include Statesman::Adapters::ActiveRecordQueries[
|
35
|
+
transition_class: MyActiveRecordModelTransition,
|
36
|
+
initial_state: :initial
|
37
|
+
]
|
38
|
+
|
27
39
|
def state_machine
|
28
40
|
@state_machine ||= MyStateMachine.new(
|
29
41
|
self, transition_class: MyActiveRecordModelTransition
|
@@ -33,18 +45,6 @@ class MyActiveRecordModel < ActiveRecord::Base
|
|
33
45
|
def metadata
|
34
46
|
super || {}
|
35
47
|
end
|
36
|
-
|
37
|
-
def reload(*)
|
38
|
-
state_machine.reset
|
39
|
-
super
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
class MyActiveRecordModelTransition < ActiveRecord::Base
|
44
|
-
include Statesman::Adapters::ActiveRecordTransition
|
45
|
-
|
46
|
-
belongs_to :my_active_record_model
|
47
|
-
serialize :metadata, JSON
|
48
48
|
end
|
49
49
|
|
50
50
|
class MyActiveRecordModelTransitionWithoutInclude < ActiveRecord::Base
|
@@ -278,3 +278,94 @@ class CreateNamespacedARModelTransitionMigration < MIGRATION_CLASS
|
|
278
278
|
end
|
279
279
|
end
|
280
280
|
end
|
281
|
+
|
282
|
+
class StiActiveRecordModel < ActiveRecord::Base
|
283
|
+
has_many :sti_a_active_record_model_transitions, autosave: false
|
284
|
+
has_many :sti_b_active_record_model_transitions, autosave: false
|
285
|
+
|
286
|
+
def state_machine_a
|
287
|
+
@state_machine_a ||= MyStateMachine.new(
|
288
|
+
self, transition_class: StiAActiveRecordModelTransition
|
289
|
+
)
|
290
|
+
end
|
291
|
+
|
292
|
+
def state_machine_b
|
293
|
+
@state_machine_b ||= MyStateMachine.new(
|
294
|
+
self, transition_class: StiBActiveRecordModelTransition
|
295
|
+
)
|
296
|
+
end
|
297
|
+
|
298
|
+
def metadata
|
299
|
+
super || {}
|
300
|
+
end
|
301
|
+
|
302
|
+
def reload(*)
|
303
|
+
state_machine_a.reset
|
304
|
+
state_machine_b.reset
|
305
|
+
super
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
class StiActiveRecordModelTransition < ActiveRecord::Base
|
310
|
+
include Statesman::Adapters::ActiveRecordTransition
|
311
|
+
|
312
|
+
belongs_to :sti_active_record_model
|
313
|
+
serialize :metadata, JSON
|
314
|
+
end
|
315
|
+
|
316
|
+
class StiAActiveRecordModelTransition < StiActiveRecordModelTransition
|
317
|
+
end
|
318
|
+
|
319
|
+
class StiBActiveRecordModelTransition < StiActiveRecordModelTransition
|
320
|
+
end
|
321
|
+
|
322
|
+
class CreateStiActiveRecordModelMigration < MIGRATION_CLASS
|
323
|
+
def change
|
324
|
+
create_table :sti_active_record_models do |t|
|
325
|
+
t.timestamps null: false
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
class CreateStiActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
331
|
+
def change
|
332
|
+
create_table :sti_active_record_model_transitions do |t|
|
333
|
+
t.string :to_state
|
334
|
+
t.integer :sti_active_record_model_id
|
335
|
+
t.integer :sort_key
|
336
|
+
t.string :type
|
337
|
+
|
338
|
+
# MySQL doesn't allow default values on text fields
|
339
|
+
if ActiveRecord::Base.connection.adapter_name == "Mysql2"
|
340
|
+
t.text :metadata
|
341
|
+
else
|
342
|
+
t.text :metadata, default: "{}"
|
343
|
+
end
|
344
|
+
|
345
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
346
|
+
t.boolean :most_recent, default: true, null: false
|
347
|
+
else
|
348
|
+
t.boolean :most_recent, default: true
|
349
|
+
end
|
350
|
+
|
351
|
+
t.timestamps null: false
|
352
|
+
end
|
353
|
+
|
354
|
+
add_index :sti_active_record_model_transitions,
|
355
|
+
%i[type sti_active_record_model_id sort_key],
|
356
|
+
unique: true, name: "sti_sort_key_index"
|
357
|
+
|
358
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
359
|
+
add_index :sti_active_record_model_transitions,
|
360
|
+
%i[type sti_active_record_model_id most_recent],
|
361
|
+
unique: true,
|
362
|
+
where: "most_recent",
|
363
|
+
name: "index_sti_active_record_model_transitions_parent_latest"
|
364
|
+
else
|
365
|
+
add_index :sti_active_record_model_transitions,
|
366
|
+
%i[type sti_active_record_model_id most_recent],
|
367
|
+
unique: true,
|
368
|
+
name: "index_sti_active_record_model_transitions_parent_latest"
|
369
|
+
end
|
370
|
+
end
|
371
|
+
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: 10.2.
|
4
|
+
version: 10.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GoCardless
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ammeter
|