statesman 10.2.1 → 10.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84f52a986a8c32bd6f42d6e450da1b26b8608cf4dc7434a163ee602d84425a36
4
- data.tar.gz: 8aaf748af7917715006dd2bd8b7946656ecbaa3cfd9dd7df9bd776da06f5ce41
3
+ metadata.gz: '07548e72198d3efbe3164700acee48b30c0fae61606b8e56304a078f8f2b7d37'
4
+ data.tar.gz: 750c6c7f3fa4f9099d64afffe2ed3faa55c6536e33da9b4dd14283e3a3fc39ab
5
5
  SHA512:
6
- metadata.gz: dc43e626d95b225a002c423092dc0906aa93365b028f8d9fef20a19c4657bef8761c430321db3046dece32d5b9ed3f8fac667a502ba13bfb9f5df8460d9cfd5d
7
- data.tar.gz: 7aa34ddc4e22e611d942db5b6c9751496a203bcb678b5a89ff8772c251515edfa27a4bca92f2c999d499b63105624464dd0e7d42dba5dcb8670d6bafcc1bd517
6
+ metadata.gz: 15a2d47316506e5e2345242a94e6aef09110f0944c2f8b2a4bb726027e45f500c7b51661093df8028db21b60f1afbf1bfc7792f37fb315edf7fad4291ef64712
7
+ data.tar.gz: 25bf0708566b48822316977e446fc543823ce702219c5584eb439b4f10e5bac51f08636e1fc207fc840fbe73c7d9dbe3b0a1c041923184bd5da6d5693033965b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## v10.2.2 21st April 2023
2
+
3
+ ### Changed
4
+ - Calling `active_record.reload` resets the adapater's internal cache
5
+
1
6
  ## v10.2.1 3rd April 2023
2
7
 
3
8
  ### Changed
@@ -76,8 +76,9 @@ module Statesman
76
76
  end
77
77
 
78
78
  def reset
79
- remove_instance_variable(:@last_transition) \
80
- if instance_variable_defined?(:@last_transition)
79
+ if instance_variable_defined?(:@last_transition)
80
+ remove_instance_variable(:@last_transition)
81
+ end
81
82
  end
82
83
 
83
84
  private
@@ -158,13 +159,24 @@ module Statesman
158
159
 
159
160
  def most_recent_transitions(most_recent_id = nil)
160
161
  if most_recent_id
161
- transitions_of_parent.and(
162
+ concrete_transitions_of_parent.and(
162
163
  transition_table[:id].eq(most_recent_id).or(
163
164
  transition_table[:most_recent].eq(true),
164
165
  ),
165
166
  )
166
167
  else
167
- transitions_of_parent.and(transition_table[:most_recent].eq(true))
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
168
180
  end
169
181
  end
170
182
 
@@ -263,13 +275,18 @@ module Statesman
263
275
  end
264
276
  end
265
277
 
266
- def parent_join_foreign_key
267
- association =
268
- parent_model.class.
269
- reflect_on_all_associations(:has_many).
270
- 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
271
281
 
272
- association_join_primary_key(association)
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)
273
290
  end
274
291
 
275
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Statesman
4
- VERSION = "10.2.1"
4
+ VERSION = "10.2.2"
5
5
  end
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
 
@@ -976,20 +976,20 @@ describe Statesman::Machine do
976
976
  end
977
977
 
978
978
  context "with defined callbacks" do
979
- let(:callback_1) { -> { "Hi" } }
980
- let(:callback_2) { -> { "Bye" } }
979
+ let(:callback_one) { -> { "Hi" } }
980
+ let(:callback_two) { -> { "Bye" } }
981
981
 
982
982
  before do
983
- machine.send(definer, from: :x, to: :y, &callback_1)
984
- machine.send(definer, from: :y, to: :z, &callback_2)
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(callback_1)
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(callback_2)
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.1
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-03 00:00:00.000000000 Z
11
+ date: 2023-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ammeter