statesman 10.2.3 → 12.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +12 -10
- data/.gitignore +0 -3
- data/.rspec +1 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +15 -0
- data/Gemfile +2 -2
- data/lib/generators/statesman/generator_helpers.rb +1 -1
- data/lib/statesman/adapters/active_record.rb +26 -33
- data/lib/statesman/adapters/active_record_queries.rb +1 -1
- data/lib/statesman/adapters/active_record_transition.rb +5 -1
- data/lib/statesman/config.rb +3 -10
- data/lib/statesman/version.rb +1 -1
- data/lib/statesman.rb +2 -4
- data/lib/tasks/statesman.rake +2 -2
- 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 +34 -8
- data/spec/statesman/adapters/active_record_queries_spec.rb +1 -3
- data/spec/statesman/adapters/active_record_spec.rb +58 -39
- 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 +0 -2
- data/spec/statesman/adapters/type_safe_active_record_queries_spec.rb +1 -3
- data/spec/statesman/callback_spec.rb +0 -2
- data/spec/statesman/config_spec.rb +0 -2
- data/spec/statesman/exceptions_spec.rb +1 -3
- data/spec/statesman/guard_spec.rb +0 -2
- data/spec/statesman/machine_spec.rb +0 -2
- data/spec/statesman/utils_spec.rb +0 -2
- data/spec/support/active_record.rb +55 -13
- data/spec/support/exactly_query_databases.rb +35 -0
- data/statesman.gemspec +2 -2
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a799d928f8e674203dde325b7653ed8fe2b719f5b9108ded9353afaa5eefe028
|
4
|
+
data.tar.gz: a92e8f0a4a49a22e66c4d3a83ccdec62e042cf8cd2a734368090f9144373f02a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 781f2603034ba02007a26678f06738ff5b0691cf2bd45c9d56406c3d4bffe15f375301b52f42f4a5e5bf8fd7fcb2b10b61e61d623cfab5d21c901025cb6f70a6
|
7
|
+
data.tar.gz: df7bfefdb22eaf086e25dfc91b233bd3eb02b3c7e658315e05b4d1e92a30e534f626622b7025a8eff244f6b29c1637bb3ed0678a1c04cab0c5ffd180b18df105
|
data/.github/workflows/tests.yml
CHANGED
@@ -24,15 +24,16 @@ jobs:
|
|
24
24
|
strategy:
|
25
25
|
fail-fast: false
|
26
26
|
matrix:
|
27
|
-
ruby-version: ["
|
27
|
+
ruby-version: ["3.0", "3.1", "3.2"]
|
28
28
|
rails-version:
|
29
|
-
- "6.1.
|
30
|
-
- "7.0.
|
29
|
+
- "6.1.7.6"
|
30
|
+
- "7.0.8"
|
31
|
+
- "7.1.1"
|
31
32
|
- "main"
|
32
|
-
postgres-version: ["
|
33
|
+
postgres-version: ["12", "13", "14", "15", "16"]
|
33
34
|
exclude:
|
34
35
|
- ruby-version: "3.2"
|
35
|
-
rails-version: "6.1.
|
36
|
+
rails-version: "6.1.7.6"
|
36
37
|
runs-on: ubuntu-latest
|
37
38
|
services:
|
38
39
|
postgres:
|
@@ -66,15 +67,16 @@ jobs:
|
|
66
67
|
strategy:
|
67
68
|
fail-fast: false
|
68
69
|
matrix:
|
69
|
-
ruby-version: ["
|
70
|
+
ruby-version: ["3.0", "3.1", "3.2"]
|
70
71
|
rails-version:
|
71
|
-
- "6.1.
|
72
|
-
- "7.0.
|
72
|
+
- "6.1.7.6"
|
73
|
+
- "7.0.8"
|
74
|
+
- "7.1.1"
|
73
75
|
- "main"
|
74
|
-
mysql-version: ["
|
76
|
+
mysql-version: ["8.0", "8.2"]
|
75
77
|
exclude:
|
76
78
|
- ruby-version: 3.2
|
77
|
-
rails-version: "6.1.
|
79
|
+
rails-version: "6.1.7.6"
|
78
80
|
runs-on: ubuntu-latest
|
79
81
|
services:
|
80
82
|
mysql:
|
data/.gitignore
CHANGED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.2.
|
1
|
+
3.2.2
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## v12.0.0 30th November 2023
|
2
|
+
|
3
|
+
### Changed
|
4
|
+
- Added multi-database support [#522](https://github.com/gocardless/statesman/pull/522)
|
5
|
+
- This now uses the correct ActiveRecord connection for the model or transition in a multi-database environment
|
6
|
+
|
7
|
+
## v11.0.0 3rd November 2023
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
- Updated to support ActiveRecord > 7.2
|
11
|
+
- Remove support for:
|
12
|
+
- Ruby; 2.7
|
13
|
+
- Postgres; 9.6, 10, 11
|
14
|
+
- MySQL; 5.7
|
15
|
+
|
1
16
|
## v10.2.3 2nd Aug 2023
|
2
17
|
|
3
18
|
### Changed
|
data/Gemfile
CHANGED
@@ -9,8 +9,8 @@ if ENV['RAILS_VERSION'] == 'main'
|
|
9
9
|
elsif ENV['RAILS_VERSION']
|
10
10
|
gem "rails", "~> #{ENV['RAILS_VERSION']}"
|
11
11
|
end
|
12
|
+
|
12
13
|
group :development do
|
13
|
-
# test/unit is no longer bundled with Ruby 2.2, but required by Rails
|
14
14
|
gem "pry"
|
15
|
-
gem "test-unit", "~> 3.3"
|
15
|
+
gem "test-unit", "~> 3.3"
|
16
16
|
end
|
@@ -52,7 +52,7 @@ module Statesman
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def database_supports_partial_indexes?
|
55
|
-
Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
55
|
+
Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(klass.constantize)
|
56
56
|
end
|
57
57
|
|
58
58
|
def metadata_default_value
|
@@ -7,19 +7,15 @@ module Statesman
|
|
7
7
|
class ActiveRecord
|
8
8
|
JSON_COLUMN_TYPES = %w[json jsonb].freeze
|
9
9
|
|
10
|
-
def self.database_supports_partial_indexes?
|
10
|
+
def self.database_supports_partial_indexes?(model)
|
11
11
|
# Rails 3 doesn't implement `supports_partial_index?`
|
12
|
-
if
|
13
|
-
|
12
|
+
if model.connection.respond_to?(:supports_partial_index?)
|
13
|
+
model.connection.supports_partial_index?
|
14
14
|
else
|
15
|
-
|
15
|
+
model.connection.adapter_name.casecmp("postgresql").zero?
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def self.adapter_name
|
20
|
-
::ActiveRecord::Base.connection.adapter_name.downcase
|
21
|
-
end
|
22
|
-
|
23
19
|
def initialize(transition_class, parent_model, observer, options = {})
|
24
20
|
serialized = serialized?(transition_class)
|
25
21
|
column_type = transition_class.columns_hash["metadata"].sql_type
|
@@ -88,10 +84,10 @@ module Statesman
|
|
88
84
|
default_transition_attributes(to, metadata),
|
89
85
|
)
|
90
86
|
|
91
|
-
|
87
|
+
transition_class.transaction(requires_new: true) do
|
92
88
|
@observer.execute(:before, from, to, transition)
|
93
89
|
|
94
|
-
if mysql_gaplock_protection?
|
90
|
+
if mysql_gaplock_protection?(transition_class.connection)
|
95
91
|
# We save the transition first with most_recent falsy, then mark most_recent
|
96
92
|
# true after to avoid letting MySQL acquire a next-key lock which can cause
|
97
93
|
# deadlocks.
|
@@ -130,8 +126,8 @@ module Statesman
|
|
130
126
|
end
|
131
127
|
|
132
128
|
def add_after_commit_callback(from, to, transition)
|
133
|
-
|
134
|
-
ActiveRecordAfterCommitWrap.new do
|
129
|
+
transition_class.connection.add_transaction_record(
|
130
|
+
ActiveRecordAfterCommitWrap.new(transition_class.connection) do
|
135
131
|
@observer.execute(:after_commit, from, to, transition)
|
136
132
|
end,
|
137
133
|
)
|
@@ -144,7 +140,7 @@ module Statesman
|
|
144
140
|
# Sets the given transition most_recent = t while unsetting the most_recent of any
|
145
141
|
# previous transitions.
|
146
142
|
def update_most_recents(most_recent_id = nil)
|
147
|
-
update = build_arel_manager(::Arel::UpdateManager)
|
143
|
+
update = build_arel_manager(::Arel::UpdateManager, transition_class)
|
148
144
|
update.table(transition_table)
|
149
145
|
update.where(most_recent_transitions(most_recent_id))
|
150
146
|
update.set(build_most_recents_update_all_values(most_recent_id))
|
@@ -152,9 +148,11 @@ module Statesman
|
|
152
148
|
# MySQL will validate index constraints across the intermediate result of an
|
153
149
|
# update. This means we must order our update to deactivate the previous
|
154
150
|
# most_recent before setting the new row to be true.
|
155
|
-
|
151
|
+
if mysql_gaplock_protection?(transition_class.connection)
|
152
|
+
update.order(transition_table[:most_recent].desc)
|
153
|
+
end
|
156
154
|
|
157
|
-
|
155
|
+
transition_class.connection.update(update.to_sql(transition_class))
|
158
156
|
end
|
159
157
|
|
160
158
|
def most_recent_transitions(most_recent_id = nil)
|
@@ -223,7 +221,7 @@ module Statesman
|
|
223
221
|
if most_recent_id
|
224
222
|
Arel::Nodes::Case.new.
|
225
223
|
when(transition_table[:id].eq(most_recent_id)).then(db_true).
|
226
|
-
else(not_most_recent_value).to_sql
|
224
|
+
else(not_most_recent_value).to_sql(transition_class)
|
227
225
|
else
|
228
226
|
Arel::Nodes::SqlLiteral.new(not_most_recent_value)
|
229
227
|
end
|
@@ -233,11 +231,11 @@ module Statesman
|
|
233
231
|
# change in Arel as we move into Rails >6.0.
|
234
232
|
#
|
235
233
|
# https://github.com/rails/rails/commit/7508284800f67b4611c767bff9eae7045674b66f
|
236
|
-
def build_arel_manager(manager)
|
234
|
+
def build_arel_manager(manager, engine)
|
237
235
|
if manager.instance_method(:initialize).arity.zero?
|
238
236
|
manager.new
|
239
237
|
else
|
240
|
-
manager.new(
|
238
|
+
manager.new(engine)
|
241
239
|
end
|
242
240
|
end
|
243
241
|
|
@@ -246,13 +244,8 @@ module Statesman
|
|
246
244
|
end
|
247
245
|
|
248
246
|
def serialized?(transition_class)
|
249
|
-
|
250
|
-
|
251
|
-
transition_class.type_for_attribute("metadata").
|
252
|
-
is_a?(::ActiveRecord::Type::Serialized)
|
253
|
-
else
|
254
|
-
transition_class.serialized_attributes.include?("metadata")
|
255
|
-
end
|
247
|
+
transition_class.type_for_attribute("metadata").
|
248
|
+
is_a?(::ActiveRecord::Type::Serialized)
|
256
249
|
end
|
257
250
|
|
258
251
|
def transition_conflict_error?(err)
|
@@ -263,7 +256,7 @@ module Statesman
|
|
263
256
|
end
|
264
257
|
|
265
258
|
def unique_indexes
|
266
|
-
|
259
|
+
transition_class.connection.
|
267
260
|
indexes(transition_class.table_name).
|
268
261
|
select do |index|
|
269
262
|
next unless index.unique
|
@@ -334,16 +327,16 @@ module Statesman
|
|
334
327
|
::ActiveRecord::Base.default_timezone
|
335
328
|
end
|
336
329
|
|
337
|
-
def mysql_gaplock_protection?
|
338
|
-
Statesman.mysql_gaplock_protection?
|
330
|
+
def mysql_gaplock_protection?(connection)
|
331
|
+
Statesman.mysql_gaplock_protection?(connection)
|
339
332
|
end
|
340
333
|
|
341
334
|
def db_true
|
342
|
-
|
335
|
+
transition_class.connection.quote(type_cast(true))
|
343
336
|
end
|
344
337
|
|
345
338
|
def db_false
|
346
|
-
|
339
|
+
transition_class.connection.quote(type_cast(false))
|
347
340
|
end
|
348
341
|
|
349
342
|
def db_null
|
@@ -353,7 +346,7 @@ module Statesman
|
|
353
346
|
# Type casting against a column is deprecated and will be removed in Rails 6.2.
|
354
347
|
# See https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11
|
355
348
|
def type_cast(value)
|
356
|
-
|
349
|
+
transition_class.connection.type_cast(value)
|
357
350
|
end
|
358
351
|
|
359
352
|
# Check whether the `most_recent` column allows null values. If it doesn't, set old
|
@@ -373,9 +366,9 @@ module Statesman
|
|
373
366
|
end
|
374
367
|
|
375
368
|
class ActiveRecordAfterCommitWrap
|
376
|
-
def initialize(&block)
|
369
|
+
def initialize(connection, &block)
|
377
370
|
@callback = block
|
378
|
-
@connection =
|
371
|
+
@connection = connection
|
379
372
|
end
|
380
373
|
|
381
374
|
def self.trigger_transactional_callbacks?
|
@@ -10,7 +10,11 @@ module Statesman
|
|
10
10
|
extend ActiveSupport::Concern
|
11
11
|
|
12
12
|
included do
|
13
|
-
|
13
|
+
if ::ActiveRecord.gem_version >= Gem::Version.new("7.1")
|
14
|
+
serialize :metadata, coder: JSON
|
15
|
+
else
|
16
|
+
serialize :metadata, JSON
|
17
|
+
end
|
14
18
|
|
15
19
|
class_attribute :updated_timestamp_column
|
16
20
|
self.updated_timestamp_column = DEFAULT_UPDATED_TIMESTAMP_COLUMN
|
data/lib/statesman/config.rb
CHANGED
@@ -15,17 +15,10 @@ module Statesman
|
|
15
15
|
@adapter_class = adapter_class
|
16
16
|
end
|
17
17
|
|
18
|
-
def mysql_gaplock_protection?
|
19
|
-
return @mysql_gaplock_protection unless @mysql_gaplock_protection.nil?
|
20
|
-
|
18
|
+
def mysql_gaplock_protection?(connection)
|
21
19
|
# If our adapter class suggests we're using mysql, enable gaplock protection by
|
22
20
|
# default.
|
23
|
-
|
24
|
-
@mysql_gaplock_protection
|
25
|
-
end
|
26
|
-
|
27
|
-
def enable_mysql_gaplock_protection
|
28
|
-
@mysql_gaplock_protection = true
|
21
|
+
mysql_adapter?(connection)
|
29
22
|
end
|
30
23
|
|
31
24
|
private
|
@@ -34,7 +27,7 @@ module Statesman
|
|
34
27
|
adapter_name = adapter_name(adapter_class)
|
35
28
|
return false unless adapter_name
|
36
29
|
|
37
|
-
adapter_name.start_with?("mysql")
|
30
|
+
adapter_name.downcase.start_with?("mysql")
|
38
31
|
end
|
39
32
|
|
40
33
|
def adapter_name(adapter_class)
|
data/lib/statesman/version.rb
CHANGED
data/lib/statesman.rb
CHANGED
@@ -34,10 +34,8 @@ module Statesman
|
|
34
34
|
@storage_adapter || Adapters::Memory
|
35
35
|
end
|
36
36
|
|
37
|
-
def self.mysql_gaplock_protection?
|
38
|
-
|
39
|
-
|
40
|
-
@mysql_gaplock_protection = config.mysql_gaplock_protection?
|
37
|
+
def self.mysql_gaplock_protection?(connection)
|
38
|
+
config.mysql_gaplock_protection?(connection)
|
41
39
|
end
|
42
40
|
|
43
41
|
def self.config
|
data/lib/tasks/statesman.rake
CHANGED
@@ -21,8 +21,8 @@ namespace :statesman do
|
|
21
21
|
batch_size = 500
|
22
22
|
|
23
23
|
parent_class.find_in_batches(batch_size: batch_size) do |models|
|
24
|
-
|
25
|
-
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
24
|
+
transition_class.transaction(requires_new: true) do
|
25
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(transition_class)
|
26
26
|
# Set all transitions' most_recent to FALSE
|
27
27
|
transition_class.where(parent_fk => models.map(&:id)).
|
28
28
|
update_all(most_recent: false, updated_at: updated_at)
|
@@ -1,10 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "spec_helper"
|
4
3
|
require "support/generators_shared_examples"
|
5
4
|
require "generators/statesman/active_record_transition_generator"
|
6
5
|
|
7
6
|
describe Statesman::ActiveRecordTransitionGenerator, type: :generator do
|
7
|
+
before do
|
8
|
+
stub_const("Bacon", Class.new(ActiveRecord::Base))
|
9
|
+
stub_const("BaconTransition", Class.new(ActiveRecord::Base))
|
10
|
+
stub_const("Yummy::Bacon", Class.new(ActiveRecord::Base))
|
11
|
+
stub_const("Yummy::BaconTransition", Class.new(ActiveRecord::Base))
|
12
|
+
end
|
13
|
+
|
8
14
|
it_behaves_like "a generator" do
|
9
15
|
let(:migration_name) { "db/migrate/create_bacon_transitions.rb" }
|
10
16
|
end
|
@@ -1,10 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "spec_helper"
|
4
3
|
require "support/generators_shared_examples"
|
5
4
|
require "generators/statesman/migration_generator"
|
6
5
|
|
7
6
|
describe Statesman::MigrationGenerator, type: :generator do
|
7
|
+
before do
|
8
|
+
stub_const("Yummy::Bacon", Class.new(ActiveRecord::Base))
|
9
|
+
stub_const("Yummy::BaconTransition", Class.new(ActiveRecord::Base))
|
10
|
+
end
|
11
|
+
|
8
12
|
it_behaves_like "a generator" do
|
9
13
|
let(:migration_name) { "db/migrate/add_statesman_to_bacon_transitions.rb" }
|
10
14
|
end
|
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
|
@@ -53,6 +77,7 @@ RSpec.configure do |config|
|
|
53
77
|
]
|
54
78
|
tables.each do |table_name|
|
55
79
|
sql = "DROP TABLE IF EXISTS #{table_name};"
|
80
|
+
|
56
81
|
ActiveRecord::Base.connection.execute(sql)
|
57
82
|
end
|
58
83
|
|
@@ -82,7 +107,8 @@ RSpec.configure do |config|
|
|
82
107
|
CreateStiActiveRecordModelTransitionMigration.migrate(:up)
|
83
108
|
StiActiveRecordModelTransition.reset_column_information
|
84
109
|
end
|
85
|
-
|
86
|
-
MyNamespace::MyActiveRecordModelTransition.serialize(:metadata, JSON)
|
87
110
|
end
|
88
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 }
|
@@ -1,17 +1,14 @@
|
|
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
|
-
MyActiveRecordModelTransition.serialize(:metadata, JSON)
|
14
|
-
|
15
12
|
prepare_sti_model_table
|
16
13
|
prepare_sti_transitions_table
|
17
14
|
|
@@ -26,8 +23,10 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
26
23
|
|
27
24
|
after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
|
28
25
|
|
26
|
+
let(:model_class) { MyActiveRecordModel }
|
27
|
+
let(:transition_class) { MyActiveRecordModelTransition }
|
29
28
|
let(:observer) { double(Statesman::Machine, execute: nil) }
|
30
|
-
let(:model) {
|
29
|
+
let(:model) { model_class.create(current_state: :pending) }
|
31
30
|
|
32
31
|
it_behaves_like "an adapter", described_class, MyActiveRecordModelTransition
|
33
32
|
|
@@ -36,17 +35,11 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
36
35
|
before do
|
37
36
|
metadata_column = double
|
38
37
|
allow(metadata_column).to receive_messages(sql_type: "")
|
39
|
-
allow(MyActiveRecordModelTransition).
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
to receive(:type_for_attribute).with("metadata").
|
45
|
-
and_return(ActiveRecord::Type::Value.new)
|
46
|
-
else
|
47
|
-
expect(MyActiveRecordModelTransition).
|
48
|
-
to receive_messages(serialized_attributes: {})
|
49
|
-
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)
|
50
43
|
end
|
51
44
|
|
52
45
|
it "raises an exception" do
|
@@ -91,18 +84,12 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
91
84
|
allow(metadata_column).to receive_messages(sql_type: "jsonb")
|
92
85
|
allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
|
93
86
|
{ "metadata" => metadata_column })
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
)
|
99
|
-
|
100
|
-
to receive(:type_for_attribute).with("metadata").
|
101
|
-
and_return(serialized_type)
|
102
|
-
else
|
103
|
-
expect(MyActiveRecordModelTransition).
|
104
|
-
to receive_messages(serialized_attributes: { "metadata" => "" })
|
105
|
-
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)
|
106
93
|
end
|
107
94
|
|
108
95
|
it "raises an exception" do
|
@@ -117,13 +104,15 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
117
104
|
describe "#create" do
|
118
105
|
subject(:transition) { create }
|
119
106
|
|
120
|
-
let!(:adapter)
|
121
|
-
described_class.new(MyActiveRecordModelTransition, model, observer)
|
122
|
-
end
|
107
|
+
let!(:adapter) { described_class.new(transition_class, model, observer) }
|
123
108
|
let(:from) { :x }
|
124
109
|
let(:to) { :y }
|
125
110
|
let(:create) { adapter.create(from, to) }
|
126
111
|
|
112
|
+
it "only connects to the primary database" do
|
113
|
+
expect { create }.to exactly_query_databases({ primary: [:writing] })
|
114
|
+
end
|
115
|
+
|
127
116
|
context "when there is a race" do
|
128
117
|
it "raises a TransitionConflictError" do
|
129
118
|
adapter2 = adapter.dup
|
@@ -131,7 +120,8 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
131
120
|
adapter.last
|
132
121
|
adapter2.create(:y, :z)
|
133
122
|
expect { adapter.create(:y, :z) }.
|
134
|
-
to raise_exception(Statesman::TransitionConflictError)
|
123
|
+
to raise_exception(Statesman::TransitionConflictError).
|
124
|
+
and exactly_query_databases({ primary: [:writing] })
|
135
125
|
end
|
136
126
|
|
137
127
|
it "does not pollute the state when the transition fails" do
|
@@ -355,12 +345,34 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
355
345
|
end
|
356
346
|
end
|
357
347
|
end
|
348
|
+
|
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
|
370
|
+
end
|
358
371
|
end
|
359
372
|
|
360
373
|
describe "#last" do
|
361
|
-
let(:
|
362
|
-
|
363
|
-
end
|
374
|
+
let(:transition_class) { MyActiveRecordModelTransition }
|
375
|
+
let(:adapter) { described_class.new(transition_class, model, observer) }
|
364
376
|
|
365
377
|
context "with a previously looked up transition" do
|
366
378
|
before { adapter.create(:x, :y) }
|
@@ -377,8 +389,19 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
377
389
|
before { adapter.create(:y, :z, []) }
|
378
390
|
|
379
391
|
it "retrieves the new transition from the database" do
|
392
|
+
expect { adapter.last.to_state }.to exactly_query_databases({ primary: [:writing] })
|
380
393
|
expect(adapter.last.to_state).to eq("z")
|
381
394
|
end
|
395
|
+
|
396
|
+
context "when using the secondary database" do
|
397
|
+
let(:model_class) { SecondaryActiveRecordModel }
|
398
|
+
let(:transition_class) { SecondaryActiveRecordModelTransition }
|
399
|
+
|
400
|
+
it "retrieves the new transition from the database" do
|
401
|
+
expect { adapter.last.to_state }.to exactly_query_databases({ secondary: [:writing] })
|
402
|
+
expect(adapter.last.to_state).to eq("z")
|
403
|
+
end
|
404
|
+
end
|
382
405
|
end
|
383
406
|
|
384
407
|
context "when a new transition has been created elsewhere" do
|
@@ -467,10 +490,6 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
467
490
|
CreateNamespacedARModelTransitionMigration.migrate(:up)
|
468
491
|
end
|
469
492
|
|
470
|
-
before do
|
471
|
-
MyNamespace::MyActiveRecordModelTransition.serialize(:metadata, JSON)
|
472
|
-
end
|
473
|
-
|
474
493
|
let(:observer) { double(Statesman::Machine, execute: nil) }
|
475
494
|
let(:model) do
|
476
495
|
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,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
describe Statesman::Adapters::TypeSafeActiveRecordQueries, active_record: true do
|
3
|
+
describe Statesman::Adapters::TypeSafeActiveRecordQueries, :active_record do
|
6
4
|
def configure(klass, transition_class)
|
7
5
|
klass.send(:extend, described_class)
|
8
6
|
klass.configure_state_machine(
|
@@ -24,7 +24,6 @@ class MyActiveRecordModelTransition < ActiveRecord::Base
|
|
24
24
|
include Statesman::Adapters::ActiveRecordTransition
|
25
25
|
|
26
26
|
belongs_to :my_active_record_model
|
27
|
-
serialize :metadata, JSON
|
28
27
|
end
|
29
28
|
|
30
29
|
class MyActiveRecordModel < ActiveRecord::Base
|
@@ -51,7 +50,11 @@ class MyActiveRecordModelTransitionWithoutInclude < ActiveRecord::Base
|
|
51
50
|
self.table_name = "my_active_record_model_transitions"
|
52
51
|
|
53
52
|
belongs_to :my_active_record_model
|
54
|
-
|
53
|
+
if ::ActiveRecord.gem_version >= Gem::Version.new("7.1")
|
54
|
+
serialize :metadata, coder: JSON
|
55
|
+
else
|
56
|
+
serialize :metadata, JSON
|
57
|
+
end
|
55
58
|
end
|
56
59
|
|
57
60
|
class CreateMyActiveRecordModelMigration < MIGRATION_CLASS
|
@@ -78,7 +81,7 @@ class CreateMyActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
78
81
|
t.text :metadata, default: "{}"
|
79
82
|
end
|
80
83
|
|
81
|
-
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
84
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(ActiveRecord::Base)
|
82
85
|
t.boolean :most_recent, default: true, null: false
|
83
86
|
else
|
84
87
|
t.boolean :most_recent, default: true
|
@@ -95,7 +98,7 @@ class CreateMyActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
95
98
|
%i[my_active_record_model_id sort_key],
|
96
99
|
unique: true, name: "sort_key_index"
|
97
100
|
|
98
|
-
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
101
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(ActiveRecord::Base)
|
99
102
|
add_index :my_active_record_model_transitions,
|
100
103
|
%i[my_active_record_model_id most_recent],
|
101
104
|
unique: true,
|
@@ -129,7 +132,48 @@ class OtherActiveRecordModelTransition < ActiveRecord::Base
|
|
129
132
|
include Statesman::Adapters::ActiveRecordTransition
|
130
133
|
|
131
134
|
belongs_to :other_active_record_model
|
132
|
-
|
135
|
+
end
|
136
|
+
|
137
|
+
class SecondaryRecord < ActiveRecord::Base
|
138
|
+
self.abstract_class = true
|
139
|
+
|
140
|
+
connects_to database: { writing: :secondary, reading: :secondary }
|
141
|
+
end
|
142
|
+
|
143
|
+
class SecondaryActiveRecordModelTransition < SecondaryRecord
|
144
|
+
self.table_name = "my_active_record_model_transitions"
|
145
|
+
|
146
|
+
include Statesman::Adapters::ActiveRecordTransition
|
147
|
+
|
148
|
+
belongs_to :my_active_record_model,
|
149
|
+
class_name: "SecondaryActiveRecordModel",
|
150
|
+
foreign_key: "my_active_record_model_transition_id"
|
151
|
+
end
|
152
|
+
|
153
|
+
class SecondaryActiveRecordModel < SecondaryRecord
|
154
|
+
self.table_name = "my_active_record_models"
|
155
|
+
|
156
|
+
has_many :my_active_record_model_transitions,
|
157
|
+
class_name: "SecondaryActiveRecordModelTransition",
|
158
|
+
foreign_key: "my_active_record_model_id",
|
159
|
+
autosave: false
|
160
|
+
|
161
|
+
alias_method :transitions, :my_active_record_model_transitions
|
162
|
+
|
163
|
+
include Statesman::Adapters::ActiveRecordQueries[
|
164
|
+
transition_class: SecondaryActiveRecordModelTransition,
|
165
|
+
initial_state: :initial
|
166
|
+
]
|
167
|
+
|
168
|
+
def state_machine
|
169
|
+
@state_machine ||= MyStateMachine.new(
|
170
|
+
self, transition_class: SecondaryActiveRecordModelTransition
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
def metadata
|
175
|
+
super || {}
|
176
|
+
end
|
133
177
|
end
|
134
178
|
|
135
179
|
class CreateOtherActiveRecordModelMigration < MIGRATION_CLASS
|
@@ -156,7 +200,7 @@ class CreateOtherActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
156
200
|
t.text :metadata, default: "{}"
|
157
201
|
end
|
158
202
|
|
159
|
-
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
203
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(ActiveRecord::Base)
|
160
204
|
t.boolean :most_recent, default: true, null: false
|
161
205
|
else
|
162
206
|
t.boolean :most_recent, default: true
|
@@ -169,7 +213,7 @@ class CreateOtherActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
169
213
|
%i[other_active_record_model_id sort_key],
|
170
214
|
unique: true, name: "other_sort_key_index"
|
171
215
|
|
172
|
-
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
216
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(ActiveRecord::Base)
|
173
217
|
add_index :other_active_record_model_transitions,
|
174
218
|
%i[other_active_record_model_id most_recent],
|
175
219
|
unique: true,
|
@@ -221,7 +265,6 @@ module MyNamespace
|
|
221
265
|
|
222
266
|
belongs_to :my_active_record_model,
|
223
267
|
class_name: "MyNamespace::MyActiveRecordModel"
|
224
|
-
serialize :metadata, JSON
|
225
268
|
|
226
269
|
def self.table_name_prefix
|
227
270
|
"my_namespace_"
|
@@ -252,7 +295,7 @@ class CreateNamespacedARModelTransitionMigration < MIGRATION_CLASS
|
|
252
295
|
t.text :metadata, default: "{}"
|
253
296
|
end
|
254
297
|
|
255
|
-
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
298
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(ActiveRecord::Base)
|
256
299
|
t.boolean :most_recent, default: true, null: false
|
257
300
|
else
|
258
301
|
t.boolean :most_recent, default: true
|
@@ -264,7 +307,7 @@ class CreateNamespacedARModelTransitionMigration < MIGRATION_CLASS
|
|
264
307
|
add_index :my_namespace_my_active_record_model_transitions, :sort_key,
|
265
308
|
unique: true, name: "my_namespaced_key"
|
266
309
|
|
267
|
-
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
310
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(ActiveRecord::Base)
|
268
311
|
add_index :my_namespace_my_active_record_model_transitions,
|
269
312
|
%i[my_active_record_model_id most_recent],
|
270
313
|
unique: true,
|
@@ -310,7 +353,6 @@ class StiActiveRecordModelTransition < ActiveRecord::Base
|
|
310
353
|
include Statesman::Adapters::ActiveRecordTransition
|
311
354
|
|
312
355
|
belongs_to :sti_active_record_model
|
313
|
-
serialize :metadata, JSON
|
314
356
|
end
|
315
357
|
|
316
358
|
class StiAActiveRecordModelTransition < StiActiveRecordModelTransition
|
@@ -342,7 +384,7 @@ class CreateStiActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
342
384
|
t.text :metadata, default: "{}"
|
343
385
|
end
|
344
386
|
|
345
|
-
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
387
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(ActiveRecord::Base)
|
346
388
|
t.boolean :most_recent, default: true, null: false
|
347
389
|
else
|
348
390
|
t.boolean :most_recent, default: true
|
@@ -355,7 +397,7 @@ class CreateStiActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
355
397
|
%i[type sti_active_record_model_id sort_key],
|
356
398
|
unique: true, name: "sti_sort_key_index"
|
357
399
|
|
358
|
-
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
|
400
|
+
if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(ActiveRecord::Base)
|
359
401
|
add_index :sti_active_record_model_transitions,
|
360
402
|
%i[type sti_active_record_model_id most_recent],
|
361
403
|
unique: true,
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# `expected_dbs` should be a Hash of the form:
|
4
|
+
# {
|
5
|
+
# primary: [:writing, :reading],
|
6
|
+
# replica: [:reading],
|
7
|
+
# }
|
8
|
+
RSpec::Matchers.define :exactly_query_databases do |expected_dbs|
|
9
|
+
match do |block|
|
10
|
+
@expected_dbs = expected_dbs.transform_values(&:to_set).with_indifferent_access
|
11
|
+
@actual_dbs = Hash.new { |h, k| h[k] = Set.new }.with_indifferent_access
|
12
|
+
|
13
|
+
ActiveSupport::Notifications.
|
14
|
+
subscribe("sql.active_record") do |_name, _start, _finish, _id, payload|
|
15
|
+
pool = payload.fetch(:connection).pool
|
16
|
+
|
17
|
+
next if pool.is_a?(ActiveRecord::ConnectionAdapters::NullPool)
|
18
|
+
|
19
|
+
name = pool.db_config.name
|
20
|
+
role = pool.role
|
21
|
+
|
22
|
+
@actual_dbs[name] << role
|
23
|
+
end
|
24
|
+
|
25
|
+
block.call
|
26
|
+
|
27
|
+
@actual_dbs == @expected_dbs
|
28
|
+
end
|
29
|
+
|
30
|
+
failure_message do |_block|
|
31
|
+
"expected to query exactly #{@expected_dbs}, but queried #{@actual_dbs}"
|
32
|
+
end
|
33
|
+
|
34
|
+
supports_block_expectations
|
35
|
+
end
|
data/statesman.gemspec
CHANGED
@@ -24,9 +24,9 @@ Gem::Specification.new do |spec|
|
|
24
24
|
|
25
25
|
spec.add_development_dependency "ammeter", "~> 1.1"
|
26
26
|
spec.add_development_dependency "bundler", "~> 2"
|
27
|
-
spec.add_development_dependency "gc_ruboconfig", "~> 3.
|
27
|
+
spec.add_development_dependency "gc_ruboconfig", "~> 4.3.0"
|
28
28
|
spec.add_development_dependency "mysql2", ">= 0.4", "< 0.6"
|
29
|
-
spec.add_development_dependency "pg", ">= 0.18", "<= 1.
|
29
|
+
spec.add_development_dependency "pg", ">= 0.18", "<= 1.6"
|
30
30
|
spec.add_development_dependency "rails", ">= 5.2"
|
31
31
|
spec.add_development_dependency "rake", "~> 13.0.0"
|
32
32
|
spec.add_development_dependency "rspec", "~> 3.1"
|
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
|
+
version: 12.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GoCardless
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ammeter
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 3.
|
47
|
+
version: 4.3.0
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 3.
|
54
|
+
version: 4.3.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: mysql2
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
version: '0.18'
|
82
82
|
- - "<="
|
83
83
|
- !ruby/object:Gem::Version
|
84
|
-
version: '1.
|
84
|
+
version: '1.6'
|
85
85
|
type: :development
|
86
86
|
prerelease: false
|
87
87
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -91,7 +91,7 @@ dependencies:
|
|
91
91
|
version: '0.18'
|
92
92
|
- - "<="
|
93
93
|
- !ruby/object:Gem::Version
|
94
|
-
version: '1.
|
94
|
+
version: '1.6'
|
95
95
|
- !ruby/object:Gem::Dependency
|
96
96
|
name: rails
|
97
97
|
requirement: !ruby/object:Gem::Requirement
|
@@ -214,6 +214,7 @@ files:
|
|
214
214
|
- ".github/dependabot.yml"
|
215
215
|
- ".github/workflows/tests.yml"
|
216
216
|
- ".gitignore"
|
217
|
+
- ".rspec"
|
217
218
|
- ".rubocop.yml"
|
218
219
|
- ".rubocop_todo.yml"
|
219
220
|
- ".ruby-version"
|
@@ -267,6 +268,7 @@ files:
|
|
267
268
|
- spec/statesman/machine_spec.rb
|
268
269
|
- spec/statesman/utils_spec.rb
|
269
270
|
- spec/support/active_record.rb
|
271
|
+
- spec/support/exactly_query_databases.rb
|
270
272
|
- spec/support/generators_shared_examples.rb
|
271
273
|
- statesman.gemspec
|
272
274
|
homepage: https://github.com/gocardless/statesman
|
@@ -294,7 +296,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
294
296
|
- !ruby/object:Gem::Version
|
295
297
|
version: '0'
|
296
298
|
requirements: []
|
297
|
-
rubygems_version: 3.4.
|
299
|
+
rubygems_version: 3.4.10
|
298
300
|
signing_key:
|
299
301
|
specification_version: 4
|
300
302
|
summary: A statesman-like state machine library
|