statesman 10.0.0 → 10.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 172b3bbeecd2cbd2df8fc9a5485c548c0d729cf0994fda1771d25c8e44f41877
4
- data.tar.gz: 3556cc13e2433e920ce9bd275bce155a095f6a0e64547a5ff003e0f5fa4dfc74
3
+ metadata.gz: 5f18f4e346afec7b89e6057602992d3d69f73f7c18f99d88bb3604d34724f9b0
4
+ data.tar.gz: 9888c2c3bc47bb78a52f2b086d9bc3729ddefec9ab776663ebf04ec9118c4d05
5
5
  SHA512:
6
- metadata.gz: 21f91dd2537c3e5e480dc0f5adb592ebbeaef9974cfbede1d94aa8b6946c26e9394199029e0281372a2ec4452e1173cb5cba86f0bf1507de19677c398df20350
7
- data.tar.gz: 58ee880508df011be1caea1f85a475760cf79e07c1ad60f241f4ef4146983997560f17fe1d3a93f5856c6e519831d71eae6c4bd8f7e70f6d0d4ce49253f1ac3f
6
+ metadata.gz: 50cdb42aa87bf9ab25e4f5a0de6f4f52647866453d9298811351ac16fdec88e7ff7b210948f756b9af59983e56cc5ca842232ae240be54f1a9e37185958eee9d
7
+ data.tar.gz: c9835aa4537d9e8adab3a75d6f63b020eaac4d4ec5aa15a8be4a4f2a6f41a869a54b5398862b23b84929cf219aff6b86eac00af3fd4a86b3e4c0ff3a6c84bae7
@@ -0,0 +1,106 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - "master"
7
+ pull_request:
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ rubocop:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v3
18
+ - uses: ruby/setup-ruby@v1
19
+ with:
20
+ bundler-cache: true
21
+ - run: bundle exec rubocop --extra-details --display-style-guide --parallel --force-exclusion
22
+
23
+ postgres:
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ ruby-version: ["2.7", "3.0", "3.1", "3.2"]
28
+ rails-version:
29
+ - "6.1.5"
30
+ - "7.0.4"
31
+ - "main"
32
+ postgres-version: ["9.6", "11", "14"]
33
+ exclude:
34
+ - ruby-version: "3.2"
35
+ rails-version: "6.1.5"
36
+ runs-on: ubuntu-latest
37
+ services:
38
+ postgres:
39
+ image: postgres:${{ matrix.postgres-version }}
40
+ env:
41
+ POSTGRES_USER: postgres
42
+ POSTGRES_DB: statesman_test
43
+ POSTGRES_PASSWORD: statesman
44
+ ports:
45
+ - 5432:5432
46
+ options: >-
47
+ --health-cmd pg_isready
48
+ --health-interval 10s
49
+ --health-timeout 5s
50
+ --health-retries 10
51
+ env:
52
+ DATABASE_URL: postgres://postgres:statesman@localhost/statesman_test
53
+ DATABASE_DEPENDENCY_PORT: "5432"
54
+ steps:
55
+ - uses: actions/checkout@v3
56
+ - name: Set up Ruby
57
+ uses: ruby/setup-ruby@v1
58
+ with:
59
+ bundler-cache: true
60
+ ruby-version: "${{ matrix.ruby-version }}"
61
+ - name: Run specs
62
+ run: |
63
+ bundle exec rspec --profile --format progress --format RSpec::Github::Formatter
64
+
65
+ mysql:
66
+ strategy:
67
+ fail-fast: false
68
+ matrix:
69
+ ruby-version: ["2.7", "3.0", "3.1", "3.2"]
70
+ rails-version:
71
+ - "6.1.5"
72
+ - "7.0.4"
73
+ - "main"
74
+ mysql-version: ["5.7", "8.0"]
75
+ exclude:
76
+ - ruby-version: 3.2
77
+ rails-version: "6.1.5"
78
+ runs-on: ubuntu-latest
79
+ services:
80
+ mysql:
81
+ image: mysql:${{ matrix.mysql-version }}
82
+ env:
83
+ MYSQL_ROOT_PASSWORD: password
84
+ MYSQL_USER: foobar
85
+ MYSQL_PASSWORD: password
86
+ MYSQL_DATABASE: statesman_test
87
+ ports:
88
+ - "3306:3306"
89
+ options: >-
90
+ --health-cmd "mysqladmin ping"
91
+ --health-interval 10s
92
+ --health-timeout 5s
93
+ --health-retries 5
94
+ env:
95
+ DATABASE_URL: mysql2://foobar:password@127.0.0.1/statesman_test
96
+ DATABASE_DEPENDENCY_PORT: "3306"
97
+ steps:
98
+ - uses: actions/checkout@v3
99
+ - name: Set up Ruby
100
+ uses: ruby/setup-ruby@v1
101
+ with:
102
+ bundler-cache: true
103
+ ruby-version: "${{ matrix.ruby-version }}"
104
+ - name: Run specs
105
+ run: |
106
+ bundle exec rspec --profile --format progress --format RSpec::Github::Formatter
data/.rubocop.yml CHANGED
@@ -4,7 +4,8 @@ inherit_gem:
4
4
  gc_ruboconfig: rubocop.yml
5
5
 
6
6
  AllCops:
7
- TargetRubyVersion: 3.0
7
+ TargetRubyVersion: 2.7
8
+ NewCops: enable
8
9
 
9
10
  Metrics/AbcSize:
10
11
  Max: 60
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.2
1
+ 3.2.0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## v10.2.0 3rd April 2023
2
+
3
+ ### Changed
4
+ - Fixed caching of `last_transition` [#505](https://github.com/gocardless/statesman/pull/505)
5
+
6
+ ## v10.1.0 10th March 2023
7
+
8
+ ### CHanged
9
+ - Add the source location of the guard callback to `Statesman::GuardFailedError`
10
+
1
11
  ## v10.0.0 17th May 2022
2
12
 
3
13
  ### Changed
data/Gemfile CHANGED
@@ -11,5 +11,6 @@ elsif ENV['RAILS_VERSION']
11
11
  end
12
12
  group :development do
13
13
  # test/unit is no longer bundled with Ruby 2.2, but required by Rails
14
+ gem "pry"
14
15
  gem "test-unit", "~> 3.3" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.0")
15
16
  end
data/README.md CHANGED
@@ -611,6 +611,30 @@ describe "some callback" do
611
611
  end
612
612
  ```
613
613
 
614
+ ## Compatibility with type checkers
615
+
616
+ Including ActiveRecordQueries to your model can cause issues with type checkers
617
+ such as Sorbet, this is because this technically is using a dynamic include,
618
+ which is not supported by Sorbet.
619
+
620
+ To avoid these issues you can instead include the TypeSafeActiveRecordQueries
621
+ module and pass in configuration.
622
+
623
+ ```ruby
624
+ class Order < ActiveRecord::Base
625
+ has_many :order_transitions, autosave: false
626
+
627
+ include Statesman::Adapters::TypeSafeActiveRecordQueries
628
+
629
+ configure_state_machine transition_class: OrderTransition,
630
+ initial_state: :pending
631
+
632
+ def state_machine
633
+ @state_machine ||= OrderStateMachine.new(self, transition_class: OrderTransition)
634
+ end
635
+ end
636
+ ```
637
+
614
638
  # Third-party extensions
615
639
 
616
640
  [statesman-sequel](https://github.com/badosu/statesman-sequel) - An adapter to make Statesman work with [Sequel](https://github.com/jeremyevans/sequel)
@@ -7,7 +7,7 @@ module Statesman
7
7
  class ActiveRecordTransitionGenerator < Rails::Generators::Base
8
8
  include Statesman::GeneratorHelpers
9
9
 
10
- desc "Create an ActiveRecord-based transition model"\
10
+ desc "Create an ActiveRecord-based transition model" \
11
11
  "with the required attributes"
12
12
 
13
13
  argument :parent, type: :string, desc: "Your parent model name"
@@ -52,7 +52,7 @@ module Statesman
52
52
 
53
53
  raise
54
54
  ensure
55
- @last_transition = nil
55
+ remove_instance_variable(:@last_transition)
56
56
  end
57
57
 
58
58
  def history(force_reload: false)
@@ -65,23 +65,22 @@ module Statesman
65
65
  end
66
66
  end
67
67
 
68
- # rubocop:disable Naming/MemoizedInstanceVariableName
69
68
  def last(force_reload: false)
70
69
  if force_reload
71
70
  @last_transition = history(force_reload: true).last
71
+ elsif instance_variable_defined?(:@last_transition)
72
+ @last_transition
72
73
  else
73
- @last_transition ||= history.last
74
+ @last_transition = history.last
74
75
  end
75
76
  end
76
- # rubocop:enable Naming/MemoizedInstanceVariableName
77
77
 
78
78
  def reset
79
- @last_transition = nil
79
+ remove_instance_variable(:@last_transition)
80
80
  end
81
81
 
82
82
  private
83
83
 
84
- # rubocop:disable Metrics/MethodLength
85
84
  def create_transition(from, to, metadata)
86
85
  transition = transitions_for_parent.build(
87
86
  default_transition_attributes(to, metadata),
@@ -118,7 +117,6 @@ module Statesman
118
117
 
119
118
  transition
120
119
  end
121
- # rubocop:enable Metrics/MethodLength
122
120
 
123
121
  def default_transition_attributes(to, metadata)
124
122
  {
@@ -231,7 +229,7 @@ module Statesman
231
229
  end
232
230
 
233
231
  def next_sort_key
234
- (last && last.sort_key + 10) || 10
232
+ (last && (last.sort_key + 10)) || 10
235
233
  end
236
234
 
237
235
  def serialized?(transition_class)
@@ -95,18 +95,18 @@ module Statesman
95
95
  def states_where(states)
96
96
  if initial_state.to_s.in?(states.map(&:to_s))
97
97
  "#{most_recent_transition_alias}.to_state IN (?) OR " \
98
- "#{most_recent_transition_alias}.to_state IS NULL"
98
+ "#{most_recent_transition_alias}.to_state IS NULL"
99
99
  else
100
100
  "#{most_recent_transition_alias}.to_state IN (?) AND " \
101
- "#{most_recent_transition_alias}.to_state IS NOT NULL"
101
+ "#{most_recent_transition_alias}.to_state IS NOT NULL"
102
102
  end
103
103
  end
104
104
 
105
105
  def most_recent_transition_join
106
106
  "LEFT OUTER JOIN #{model_table} AS #{most_recent_transition_alias} " \
107
- "ON #{model.table_name}.#{model_primary_key} = " \
108
- "#{most_recent_transition_alias}.#{model_foreign_key} " \
109
- "AND #{most_recent_transition_alias}.most_recent = #{db_true}"
107
+ "ON #{model.table_name}.#{model_primary_key} = " \
108
+ "#{most_recent_transition_alias}.#{model_foreign_key} " \
109
+ "AND #{most_recent_transition_alias}.most_recent = #{db_true}"
110
110
  end
111
111
 
112
112
  private
@@ -44,7 +44,7 @@ module Statesman
44
44
  private
45
45
 
46
46
  def next_sort_key
47
- (last && last.sort_key + 10) || 10
47
+ (last && (last.sort_key + 10)) || 10
48
48
  end
49
49
  end
50
50
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Statesman
4
+ module Adapters
5
+ module TypeSafeActiveRecordQueries
6
+ def configure_state_machine(args = {})
7
+ transition_class = args.fetch(:transition_class)
8
+ initial_state = args.fetch(:initial_state)
9
+
10
+ include(
11
+ ActiveRecordQueries::ClassMethods.new(
12
+ transition_class: transition_class,
13
+ initial_state: initial_state,
14
+ most_recent_transition_alias: try(:most_recent_transition_alias),
15
+ transition_name: try(:transition_name),
16
+ ),
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -28,13 +28,15 @@ module Statesman
28
28
  end
29
29
 
30
30
  class GuardFailedError < StandardError
31
- def initialize(from, to)
31
+ def initialize(from, to, callback)
32
32
  @from = from
33
33
  @to = to
34
+ @callback = callback
34
35
  super(_message)
36
+ set_backtrace(callback.source_location.join(":")) if callback&.source_location
35
37
  end
36
38
 
37
- attr_reader :from, :to
39
+ attr_reader :from, :to, :callback
38
40
 
39
41
  private
40
42
 
@@ -52,8 +54,8 @@ module Statesman
52
54
 
53
55
  def _message(transition_class_name)
54
56
  "#{transition_class_name}#metadata is not serialized. If you " \
55
- "are using a non-json column type, you should `include " \
56
- "Statesman::Adapters::ActiveRecordTransition`"
57
+ "are using a non-json column type, you should `include " \
58
+ "Statesman::Adapters::ActiveRecordTransition`"
57
59
  end
58
60
  end
59
61
 
@@ -66,9 +68,9 @@ module Statesman
66
68
 
67
69
  def _message(transition_class_name)
68
70
  "#{transition_class_name}#metadata column type cannot be json " \
69
- "and serialized simultaneously. If you are using a json " \
70
- "column type, it is not necessary to `include " \
71
- "Statesman::Adapters::ActiveRecordTransition`"
71
+ "and serialized simultaneously. If you are using a json " \
72
+ "column type, it is not necessary to `include " \
73
+ "Statesman::Adapters::ActiveRecordTransition`"
72
74
  end
73
75
  end
74
76
  end
@@ -6,7 +6,7 @@ require_relative "exceptions"
6
6
  module Statesman
7
7
  class Guard < Callback
8
8
  def call(*args)
9
- raise GuardFailedError.new(from, to) unless super(*args)
9
+ raise GuardFailedError.new(from, to, callback) unless super(*args)
10
10
  end
11
11
  end
12
12
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Statesman
4
- VERSION = "10.0.0"
4
+ VERSION = "10.2.0"
5
5
  end
data/lib/statesman.rb CHANGED
@@ -14,6 +14,8 @@ module Statesman
14
14
  "statesman/adapters/active_record_transition"
15
15
  autoload :ActiveRecordQueries,
16
16
  "statesman/adapters/active_record_queries"
17
+ autoload :TypeSafeActiveRecordQueries,
18
+ "statesman/adapters/type_safe_active_record_queries"
17
19
  end
18
20
  require "statesman/railtie" if defined?(::Rails::Railtie)
19
21
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :statesman do
4
- desc "Set most_recent to false for old transitions and to true for the "\
4
+ desc "Set most_recent to false for old transitions and to true for the " \
5
5
  "latest one. Safe to re-run"
6
6
  task :backfill_most_recent, [:parent_model_name] => :environment do |_, args|
7
7
  parent_model_name = args.parent_model_name
@@ -56,8 +56,8 @@ namespace :statesman do
56
56
  end
57
57
 
58
58
  done_models += batch_size
59
- puts "Updated #{transition_class.name.pluralize} for "\
60
- "#{[done_models, total_models].min}/#{total_models} "\
59
+ puts "Updated #{transition_class.name.pluralize} for " \
60
+ "#{[done_models, total_models].min}/#{total_models} " \
61
61
  "#{parent_model_name.pluralize}"
62
62
  end
63
63
  end
@@ -117,8 +117,8 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
117
117
  subject(:not_in_state) { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
118
118
 
119
119
  it do
120
- expect(not_in_state).to match_array([initial_state_model,
121
- returned_to_initial_model])
120
+ expect(not_in_state).to contain_exactly(initial_state_model,
121
+ returned_to_initial_model)
122
122
  end
123
123
  end
124
124
 
@@ -126,8 +126,8 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
126
126
  subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
127
127
 
128
128
  it do
129
- expect(not_in_state).to match_array([initial_state_model,
130
- returned_to_initial_model])
129
+ expect(not_in_state).to contain_exactly(initial_state_model,
130
+ returned_to_initial_model)
131
131
  end
132
132
  end
133
133
  end
@@ -254,7 +254,7 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
254
254
  end
255
255
 
256
256
  it "does not raise an error" do
257
- expect { check_missing_methods! }.to_not raise_exception(NotImplementedError)
257
+ expect { check_missing_methods! }.to_not raise_exception
258
258
  end
259
259
  end
260
260
 
@@ -35,8 +35,8 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
35
35
  allow(metadata_column).to receive_messages(sql_type: "")
36
36
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
37
37
  { "metadata" => metadata_column })
38
- if ::ActiveRecord.respond_to?(:gem_version) &&
39
- ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
38
+ if ActiveRecord.respond_to?(:gem_version) &&
39
+ ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
40
40
  expect(MyActiveRecordModelTransition).
41
41
  to receive(:type_for_attribute).with("metadata").
42
42
  and_return(ActiveRecord::Type::Value.new)
@@ -60,10 +60,10 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
60
60
  allow(metadata_column).to receive_messages(sql_type: "json")
61
61
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
62
62
  { "metadata" => metadata_column })
63
- if ::ActiveRecord.respond_to?(:gem_version) &&
64
- ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
65
- serialized_type = ::ActiveRecord::Type::Serialized.new(
66
- "", ::ActiveRecord::Coders::JSON
63
+ if ActiveRecord.respond_to?(:gem_version) &&
64
+ ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
65
+ serialized_type = ActiveRecord::Type::Serialized.new(
66
+ "", ActiveRecord::Coders::JSON
67
67
  )
68
68
  expect(MyActiveRecordModelTransition).
69
69
  to receive(:type_for_attribute).with("metadata").
@@ -88,10 +88,10 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
88
88
  allow(metadata_column).to receive_messages(sql_type: "jsonb")
89
89
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
90
90
  { "metadata" => metadata_column })
91
- if ::ActiveRecord.respond_to?(:gem_version) &&
92
- ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
93
- serialized_type = ::ActiveRecord::Type::Serialized.new(
94
- "", ::ActiveRecord::Coders::JSON
91
+ if ActiveRecord.respond_to?(:gem_version) &&
92
+ ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
93
+ serialized_type = ActiveRecord::Type::Serialized.new(
94
+ "", ActiveRecord::Coders::JSON
95
95
  )
96
96
  expect(MyActiveRecordModelTransition).
97
97
  to receive(:type_for_attribute).with("metadata").
@@ -112,7 +112,7 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
112
112
  end
113
113
 
114
114
  describe "#create" do
115
- subject { -> { create } }
115
+ subject(:transition) { create }
116
116
 
117
117
  let!(:adapter) do
118
118
  described_class.new(MyActiveRecordModelTransition, model, observer)
@@ -165,27 +165,25 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
165
165
 
166
166
  context "ActiveRecord::RecordNotUnique unrelated to this transition" do
167
167
  let(:error) do
168
- if ::ActiveRecord.respond_to?(:gem_version) &&
169
- ::ActiveRecord.gem_version >= Gem::Version.new("4.0.0")
168
+ if ActiveRecord.respond_to?(:gem_version) &&
169
+ ActiveRecord.gem_version >= Gem::Version.new("4.0.0")
170
170
  ActiveRecord::RecordNotUnique.new("unrelated")
171
171
  else
172
172
  ActiveRecord::RecordNotUnique.new("unrelated", nil)
173
173
  end
174
174
  end
175
175
 
176
- it { is_expected.to raise_exception(ActiveRecord::RecordNotUnique) }
176
+ it { expect { transition }.to raise_exception(ActiveRecord::RecordNotUnique) }
177
177
  end
178
178
 
179
179
  context "other errors" do
180
180
  let(:error) { StandardError }
181
181
 
182
- it { is_expected.to raise_exception(StandardError) }
182
+ it { expect { transition }.to raise_exception(StandardError) }
183
183
  end
184
184
  end
185
185
 
186
186
  describe "updating the most_recent column" do
187
- subject { create }
188
-
189
187
  context "with no previous transition" do
190
188
  its(:most_recent) { is_expected.to eq(true) }
191
189
  end
@@ -310,9 +308,9 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
310
308
  described_class.new(MyActiveRecordModelTransition, model, observer)
311
309
  end
312
310
 
313
- before { adapter.create(:x, :y) }
314
-
315
311
  context "with a previously looked up transition" do
312
+ before { adapter.create(:x, :y) }
313
+
316
314
  before { adapter.last }
317
315
 
318
316
  it "caches the transition" do
@@ -378,6 +376,16 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
378
376
  expect(adapter.last.to_state).to eq("y")
379
377
  end
380
378
  end
379
+
380
+ context "without previous transitions" do
381
+ it "does query the database only once" do
382
+ expect(model.my_active_record_model_transitions).
383
+ to receive(:order).once.and_call_original
384
+
385
+ expect(adapter.last).to eq(nil)
386
+ expect(adapter.last).to eq(nil)
387
+ end
388
+ end
381
389
  end
382
390
 
383
391
  it "resets last with #reload" do
@@ -30,14 +30,14 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
30
30
  end
31
31
 
32
32
  describe "#create" do
33
- subject { -> { create } }
33
+ subject(:transition) { create }
34
34
 
35
35
  let(:from) { :x }
36
36
  let(:to) { :y }
37
37
  let(:there) { :z }
38
38
  let(:create) { adapter.create(from, to) }
39
39
 
40
- it { is_expected.to change(adapter.history, :count).by(1) }
40
+ it { expect { transition }.to change(adapter.history, :count).by(1) }
41
41
 
42
42
  context "the new transition" do
43
43
  subject(:instance) { create }
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe Statesman::Adapters::TypeSafeActiveRecordQueries, active_record: true do
6
+ def configure(klass, transition_class)
7
+ klass.send(:extend, described_class)
8
+ klass.configure_state_machine(
9
+ transition_class: transition_class,
10
+ initial_state: :initial,
11
+ )
12
+ end
13
+
14
+ before do
15
+ prepare_model_table
16
+ prepare_transitions_table
17
+ prepare_other_model_table
18
+ prepare_other_transitions_table
19
+
20
+ Statesman.configure do
21
+ storage_adapter(Statesman::Adapters::ActiveRecord)
22
+ end
23
+ end
24
+
25
+ after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
26
+
27
+ let!(:model) do
28
+ model = MyActiveRecordModel.create
29
+ model.state_machine.transition_to(:succeeded)
30
+ model
31
+ end
32
+
33
+ let!(:other_model) do
34
+ model = MyActiveRecordModel.create
35
+ model.state_machine.transition_to(:failed)
36
+ model
37
+ end
38
+
39
+ let!(:initial_state_model) { MyActiveRecordModel.create }
40
+
41
+ let!(:returned_to_initial_model) do
42
+ model = MyActiveRecordModel.create
43
+ model.state_machine.transition_to(:failed)
44
+ model.state_machine.transition_to(:initial)
45
+ model
46
+ end
47
+
48
+ shared_examples "testing methods" do
49
+ before do
50
+ configure(MyActiveRecordModel, MyActiveRecordModelTransition)
51
+ configure(OtherActiveRecordModel, OtherActiveRecordModelTransition)
52
+
53
+ MyActiveRecordModel.send(:has_one, :other_active_record_model)
54
+ OtherActiveRecordModel.send(:belongs_to, :my_active_record_model)
55
+ end
56
+
57
+ describe ".in_state" do
58
+ context "given a single state" do
59
+ subject { MyActiveRecordModel.in_state(:succeeded) }
60
+
61
+ it { is_expected.to include model }
62
+ it { is_expected.to_not include other_model }
63
+ end
64
+
65
+ context "given multiple states" do
66
+ subject { MyActiveRecordModel.in_state(:succeeded, :failed) }
67
+
68
+ it { is_expected.to include model }
69
+ it { is_expected.to include other_model }
70
+ end
71
+
72
+ context "given the initial state" do
73
+ subject { MyActiveRecordModel.in_state(:initial) }
74
+
75
+ it { is_expected.to include initial_state_model }
76
+ it { is_expected.to include returned_to_initial_model }
77
+ end
78
+
79
+ context "given an array of states" do
80
+ subject { MyActiveRecordModel.in_state(%i[succeeded failed]) }
81
+
82
+ it { is_expected.to include model }
83
+ it { is_expected.to include other_model }
84
+ end
85
+
86
+ context "merging two queries" do
87
+ subject do
88
+ MyActiveRecordModel.in_state(:succeeded).
89
+ joins(:other_active_record_model).
90
+ merge(OtherActiveRecordModel.in_state(:initial))
91
+ end
92
+
93
+ it { is_expected.to be_empty }
94
+ end
95
+ end
96
+
97
+ describe ".not_in_state" do
98
+ context "given a single state" do
99
+ subject { MyActiveRecordModel.not_in_state(:failed) }
100
+
101
+ it { is_expected.to include model }
102
+ it { is_expected.to_not include other_model }
103
+ end
104
+
105
+ context "given multiple states" do
106
+ subject(:not_in_state) { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
107
+
108
+ it do
109
+ expect(not_in_state).to contain_exactly(initial_state_model,
110
+ returned_to_initial_model)
111
+ end
112
+ end
113
+
114
+ context "given an array of states" do
115
+ subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
116
+
117
+ it do
118
+ expect(not_in_state).to contain_exactly(initial_state_model,
119
+ returned_to_initial_model)
120
+ end
121
+ end
122
+ end
123
+
124
+ context "with a custom name for the transition association" do
125
+ before do
126
+ # Switch to using OtherActiveRecordModelTransition, so the existing
127
+ # relation with MyActiveRecordModelTransition doesn't interfere with
128
+ # this spec.
129
+ MyActiveRecordModel.send(:has_many,
130
+ :custom_name,
131
+ class_name: "OtherActiveRecordModelTransition")
132
+
133
+ MyActiveRecordModel.class_eval do
134
+ def self.transition_class
135
+ OtherActiveRecordModelTransition
136
+ end
137
+ end
138
+ end
139
+
140
+ describe ".in_state" do
141
+ subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
142
+
143
+ specify { expect { query }.to_not raise_error }
144
+ end
145
+ end
146
+
147
+ context "with a custom primary key for the model" do
148
+ before do
149
+ # Switch to using OtherActiveRecordModelTransition, so the existing
150
+ # relation with MyActiveRecordModelTransition doesn't interfere with
151
+ # this spec.
152
+ # Configure the relationship to use a different primary key,
153
+ MyActiveRecordModel.send(:has_many,
154
+ :custom_name,
155
+ class_name: "OtherActiveRecordModelTransition",
156
+ primary_key: :external_id)
157
+
158
+ MyActiveRecordModel.class_eval do
159
+ def self.transition_class
160
+ OtherActiveRecordModelTransition
161
+ end
162
+ end
163
+ end
164
+
165
+ describe ".in_state" do
166
+ subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
167
+
168
+ specify { expect { query }.to_not raise_error }
169
+ end
170
+ end
171
+
172
+ context "after_commit transactional integrity" do
173
+ before do
174
+ MyStateMachine.class_eval do
175
+ cattr_accessor(:after_commit_callback_executed) { false }
176
+
177
+ after_transition(from: :initial, to: :succeeded, after_commit: true) do
178
+ # This leaks state in a testable way if transactional integrity is broken.
179
+ MyStateMachine.after_commit_callback_executed = true
180
+ end
181
+ end
182
+ end
183
+
184
+ after do
185
+ MyStateMachine.class_eval do
186
+ callbacks[:after_commit] = []
187
+ end
188
+ end
189
+
190
+ let!(:model) do
191
+ MyActiveRecordModel.create
192
+ end
193
+
194
+ it do
195
+ expect do
196
+ ActiveRecord::Base.transaction do
197
+ model.state_machine.transition_to!(:succeeded)
198
+ raise ActiveRecord::Rollback
199
+ end
200
+ end.to_not change(MyStateMachine, :after_commit_callback_executed)
201
+ end
202
+ end
203
+ end
204
+
205
+ context "using configuration method" do
206
+ include_examples "testing methods"
207
+ end
208
+ end
@@ -64,12 +64,18 @@ describe Statesman do
64
64
  end
65
65
 
66
66
  describe "GuardFailedError" do
67
- subject(:error) { Statesman::GuardFailedError.new("from", "to") }
67
+ subject(:error) { Statesman::GuardFailedError.new("from", "to", callback) }
68
+
69
+ let(:callback) { -> { "hello" } }
68
70
 
69
71
  its(:message) do
70
72
  is_expected.to eq("Guard on transition from: 'from' to 'to' returned false")
71
73
  end
72
74
 
75
+ its(:backtrace) do
76
+ is_expected.to eq([callback.source_location.join(":")])
77
+ end
78
+
73
79
  its "string matches its message" do
74
80
  expect(error.to_s).to eq(error.message)
75
81
  end
@@ -28,7 +28,7 @@ describe Statesman::Machine do
28
28
  end
29
29
 
30
30
  describe ".remove_state" do
31
- subject(:remove_state) { -> { machine.remove_state(:x) } }
31
+ subject(:remove_state) { machine.remove_state(:x) }
32
32
 
33
33
  before do
34
34
  machine.class_eval do
@@ -39,7 +39,7 @@ describe Statesman::Machine do
39
39
  end
40
40
 
41
41
  it "removes the state" do
42
- expect(remove_state).
42
+ expect { remove_state }.
43
43
  to change(machine, :states).
44
44
  from(match_array(%w[x y z])).
45
45
  to(%w[y z])
@@ -49,7 +49,7 @@ describe Statesman::Machine do
49
49
  before { machine.transition from: :x, to: :y }
50
50
 
51
51
  it "removes the transition" do
52
- expect(remove_state).
52
+ expect { remove_state }.
53
53
  to change(machine, :successors).
54
54
  from({ "x" => ["y"] }).
55
55
  to({})
@@ -59,7 +59,7 @@ describe Statesman::Machine do
59
59
  before { machine.transition from: :x, to: :z }
60
60
 
61
61
  it "removes all transitions" do
62
- expect(remove_state).
62
+ expect { remove_state }.
63
63
  to change(machine, :successors).
64
64
  from({ "x" => %w[y z] }).
65
65
  to({})
@@ -71,7 +71,7 @@ describe Statesman::Machine do
71
71
  before { machine.transition from: :y, to: :x }
72
72
 
73
73
  it "removes the transition" do
74
- expect(remove_state).
74
+ expect { remove_state }.
75
75
  to change(machine, :successors).
76
76
  from({ "y" => ["x"] }).
77
77
  to({})
@@ -81,7 +81,7 @@ describe Statesman::Machine do
81
81
  before { machine.transition from: :z, to: :x }
82
82
 
83
83
  it "removes all transitions" do
84
- expect(remove_state).
84
+ expect { remove_state }.
85
85
  to change(machine, :successors).
86
86
  from({ "y" => ["x"], "z" => ["x"] }).
87
87
  to({})
@@ -104,7 +104,7 @@ describe Statesman::Machine do
104
104
  end
105
105
 
106
106
  it "removes the guard" do
107
- expect(remove_state).
107
+ expect { remove_state }.
108
108
  to change(machine, :callbacks).
109
109
  from(a_hash_including(guards: match_array(guards))).
110
110
  to(a_hash_including(guards: []))
@@ -125,7 +125,7 @@ describe Statesman::Machine do
125
125
  end
126
126
 
127
127
  it "removes the guard" do
128
- expect(remove_state).
128
+ expect { remove_state }.
129
129
  to change(machine, :callbacks).
130
130
  from(a_hash_including(guards: match_array(guards))).
131
131
  to(a_hash_including(guards: []))
@@ -935,10 +935,10 @@ describe Statesman::Machine do
935
935
  it { is_expected.to be(:some_state) }
936
936
  end
937
937
 
938
- context "when it is unsuccesful" do
938
+ context "when it is unsuccessful" do
939
939
  before do
940
940
  allow(instance).to receive(:transition_to!).
941
- and_raise(Statesman::GuardFailedError.new(:x, :some_state))
941
+ and_raise(Statesman::GuardFailedError.new(:x, :some_state, nil))
942
942
  end
943
943
 
944
944
  it { is_expected.to be_falsey }
@@ -64,7 +64,6 @@ class CreateMyActiveRecordModelMigration < MIGRATION_CLASS
64
64
  end
65
65
 
66
66
  # TODO: make this a module we can extend from the app? Or a generator?
67
- # rubocop:disable Metrics/MethodLength
68
67
  class CreateMyActiveRecordModelTransitionMigration < MIGRATION_CLASS
69
68
  def change
70
69
  create_table :my_active_record_model_transitions do |t|
@@ -110,7 +109,6 @@ class CreateMyActiveRecordModelTransitionMigration < MIGRATION_CLASS
110
109
  end
111
110
  end
112
111
  end
113
- # rubocop:enable Metrics/MethodLength
114
112
 
115
113
  class OtherActiveRecordModel < ActiveRecord::Base
116
114
  has_many :other_active_record_model_transitions, autosave: false
@@ -144,7 +142,6 @@ class CreateOtherActiveRecordModelMigration < MIGRATION_CLASS
144
142
  end
145
143
  end
146
144
 
147
- # rubocop:disable Metrics/MethodLength
148
145
  class CreateOtherActiveRecordModelTransitionMigration < MIGRATION_CLASS
149
146
  def change
150
147
  create_table :other_active_record_model_transitions do |t|
@@ -177,18 +174,17 @@ class CreateOtherActiveRecordModelTransitionMigration < MIGRATION_CLASS
177
174
  %i[other_active_record_model_id most_recent],
178
175
  unique: true,
179
176
  where: "most_recent",
180
- name: "index_other_active_record_model_transitions_"\
177
+ name: "index_other_active_record_model_transitions_" \
181
178
  "parent_latest"
182
179
  else
183
180
  add_index :other_active_record_model_transitions,
184
181
  %i[other_active_record_model_id most_recent],
185
182
  unique: true,
186
- name: "index_other_active_record_model_transitions_"\
183
+ name: "index_other_active_record_model_transitions_" \
187
184
  "parent_latest"
188
185
  end
189
186
  end
190
187
  end
191
- # rubocop:enable Metrics/MethodLength
192
188
 
193
189
  class DropMostRecentColumn < MIGRATION_CLASS
194
190
  def change
@@ -242,7 +238,6 @@ class CreateNamespacedARModelMigration < MIGRATION_CLASS
242
238
  end
243
239
  end
244
240
 
245
- # rubocop:disable Metrics/MethodLength
246
241
  class CreateNamespacedARModelTransitionMigration < MIGRATION_CLASS
247
242
  def change
248
243
  create_table :my_namespace_my_active_record_model_transitions do |t|
@@ -282,5 +277,4 @@ class CreateNamespacedARModelTransitionMigration < MIGRATION_CLASS
282
277
  name: "index_namespace_model_transitions_parent_latest"
283
278
  end
284
279
  end
285
- # rubocop:enable Metrics/MethodLength
286
280
  end
data/statesman.gemspec CHANGED
@@ -18,24 +18,22 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
20
20
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
21
  spec.require_paths = ["lib"]
23
22
 
24
23
  spec.required_ruby_version = ">= 2.7"
25
24
 
26
25
  spec.add_development_dependency "ammeter", "~> 1.1"
27
26
  spec.add_development_dependency "bundler", "~> 2"
28
- spec.add_development_dependency "gc_ruboconfig", "~> 2.26.0"
27
+ spec.add_development_dependency "gc_ruboconfig", "~> 3.6.0"
29
28
  spec.add_development_dependency "mysql2", ">= 0.4", "< 0.6"
30
- spec.add_development_dependency "pg", ">= 0.18", "<= 1.3"
31
- spec.add_development_dependency "pry"
29
+ spec.add_development_dependency "pg", ">= 0.18", "<= 1.5"
32
30
  spec.add_development_dependency "rails", ">= 5.2"
33
31
  spec.add_development_dependency "rake", "~> 13.0.0"
34
32
  spec.add_development_dependency "rspec", "~> 3.1"
33
+ spec.add_development_dependency "rspec-github", "~> 2.4.0"
35
34
  spec.add_development_dependency "rspec-its", "~> 1.1"
36
- spec.add_development_dependency "rspec_junit_formatter", "~> 0.5.1"
37
- spec.add_development_dependency "rspec-rails", "~> 3.1"
38
- spec.add_development_dependency "sqlite3", "~> 1.4.2"
35
+ spec.add_development_dependency "rspec-rails", "~> 6.0"
36
+ spec.add_development_dependency "sqlite3", "~> 1.6.1"
39
37
  spec.add_development_dependency "timecop", "~> 0.9.1"
40
38
 
41
39
  spec.metadata = {
@@ -44,5 +42,6 @@ Gem::Specification.new do |spec|
44
42
  "documentation_uri" => "#{GITHUB_URL}/blob/master/README.md",
45
43
  "homepage_uri" => GITHUB_URL,
46
44
  "source_code_uri" => GITHUB_URL,
45
+ "rubygems_mfa_required" => "true",
47
46
  }
48
47
  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.0.0
4
+ version: 10.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GoCardless
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-17 00:00:00.000000000 Z
11
+ date: 2023-04-03 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: 2.26.0
47
+ version: 3.6.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: 2.26.0
54
+ version: 3.6.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.3'
84
+ version: '1.5'
85
85
  type: :development
86
86
  prerelease: false
87
87
  version_requirements: !ruby/object:Gem::Requirement
@@ -91,21 +91,7 @@ dependencies:
91
91
  version: '0.18'
92
92
  - - "<="
93
93
  - !ruby/object:Gem::Version
94
- version: '1.3'
95
- - !ruby/object:Gem::Dependency
96
- name: pry
97
- requirement: !ruby/object:Gem::Requirement
98
- requirements:
99
- - - ">="
100
- - !ruby/object:Gem::Version
101
- version: '0'
102
- type: :development
103
- prerelease: false
104
- version_requirements: !ruby/object:Gem::Requirement
105
- requirements:
106
- - - ">="
107
- - !ruby/object:Gem::Version
108
- version: '0'
94
+ version: '1.5'
109
95
  - !ruby/object:Gem::Dependency
110
96
  name: rails
111
97
  requirement: !ruby/object:Gem::Requirement
@@ -149,61 +135,61 @@ dependencies:
149
135
  - !ruby/object:Gem::Version
150
136
  version: '3.1'
151
137
  - !ruby/object:Gem::Dependency
152
- name: rspec-its
138
+ name: rspec-github
153
139
  requirement: !ruby/object:Gem::Requirement
154
140
  requirements:
155
141
  - - "~>"
156
142
  - !ruby/object:Gem::Version
157
- version: '1.1'
143
+ version: 2.4.0
158
144
  type: :development
159
145
  prerelease: false
160
146
  version_requirements: !ruby/object:Gem::Requirement
161
147
  requirements:
162
148
  - - "~>"
163
149
  - !ruby/object:Gem::Version
164
- version: '1.1'
150
+ version: 2.4.0
165
151
  - !ruby/object:Gem::Dependency
166
- name: rspec_junit_formatter
152
+ name: rspec-its
167
153
  requirement: !ruby/object:Gem::Requirement
168
154
  requirements:
169
155
  - - "~>"
170
156
  - !ruby/object:Gem::Version
171
- version: 0.5.1
157
+ version: '1.1'
172
158
  type: :development
173
159
  prerelease: false
174
160
  version_requirements: !ruby/object:Gem::Requirement
175
161
  requirements:
176
162
  - - "~>"
177
163
  - !ruby/object:Gem::Version
178
- version: 0.5.1
164
+ version: '1.1'
179
165
  - !ruby/object:Gem::Dependency
180
166
  name: rspec-rails
181
167
  requirement: !ruby/object:Gem::Requirement
182
168
  requirements:
183
169
  - - "~>"
184
170
  - !ruby/object:Gem::Version
185
- version: '3.1'
171
+ version: '6.0'
186
172
  type: :development
187
173
  prerelease: false
188
174
  version_requirements: !ruby/object:Gem::Requirement
189
175
  requirements:
190
176
  - - "~>"
191
177
  - !ruby/object:Gem::Version
192
- version: '3.1'
178
+ version: '6.0'
193
179
  - !ruby/object:Gem::Dependency
194
180
  name: sqlite3
195
181
  requirement: !ruby/object:Gem::Requirement
196
182
  requirements:
197
183
  - - "~>"
198
184
  - !ruby/object:Gem::Version
199
- version: 1.4.2
185
+ version: 1.6.1
200
186
  type: :development
201
187
  prerelease: false
202
188
  version_requirements: !ruby/object:Gem::Requirement
203
189
  requirements:
204
190
  - - "~>"
205
191
  - !ruby/object:Gem::Version
206
- version: 1.4.2
192
+ version: 1.6.1
207
193
  - !ruby/object:Gem::Dependency
208
194
  name: timecop
209
195
  requirement: !ruby/object:Gem::Requirement
@@ -225,8 +211,8 @@ executables: []
225
211
  extensions: []
226
212
  extra_rdoc_files: []
227
213
  files:
228
- - ".circleci/config.yml"
229
214
  - ".github/dependabot.yml"
215
+ - ".github/workflows/tests.yml"
230
216
  - ".gitignore"
231
217
  - ".rubocop.yml"
232
218
  - ".rubocop_todo.yml"
@@ -251,6 +237,7 @@ files:
251
237
  - lib/statesman/adapters/active_record_transition.rb
252
238
  - lib/statesman/adapters/memory.rb
253
239
  - lib/statesman/adapters/memory_transition.rb
240
+ - lib/statesman/adapters/type_safe_active_record_queries.rb
254
241
  - lib/statesman/callback.rb
255
242
  - lib/statesman/config.rb
256
243
  - lib/statesman/exceptions.rb
@@ -272,6 +259,7 @@ files:
272
259
  - spec/statesman/adapters/memory_spec.rb
273
260
  - spec/statesman/adapters/memory_transition_spec.rb
274
261
  - spec/statesman/adapters/shared_examples.rb
262
+ - spec/statesman/adapters/type_safe_active_record_queries_spec.rb
275
263
  - spec/statesman/callback_spec.rb
276
264
  - spec/statesman/config_spec.rb
277
265
  - spec/statesman/exceptions_spec.rb
@@ -290,6 +278,7 @@ metadata:
290
278
  documentation_uri: https://github.com/gocardless/statesman/blob/master/README.md
291
279
  homepage_uri: https://github.com/gocardless/statesman
292
280
  source_code_uri: https://github.com/gocardless/statesman
281
+ rubygems_mfa_required: 'true'
293
282
  post_install_message:
294
283
  rdoc_options: []
295
284
  require_paths:
@@ -305,28 +294,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
305
294
  - !ruby/object:Gem::Version
306
295
  version: '0'
307
296
  requirements: []
308
- rubygems_version: 3.2.22
297
+ rubygems_version: 3.4.1
309
298
  signing_key:
310
299
  specification_version: 4
311
300
  summary: A statesman-like state machine library
312
- test_files:
313
- - spec/fixtures/add_constraints_to_most_recent_for_bacon_transitions_with_partial_index.rb
314
- - spec/fixtures/add_constraints_to_most_recent_for_bacon_transitions_without_partial_index.rb
315
- - spec/fixtures/add_most_recent_to_bacon_transitions.rb
316
- - spec/generators/statesman/active_record_transition_generator_spec.rb
317
- - spec/generators/statesman/migration_generator_spec.rb
318
- - spec/spec_helper.rb
319
- - spec/statesman/adapters/active_record_queries_spec.rb
320
- - spec/statesman/adapters/active_record_spec.rb
321
- - spec/statesman/adapters/active_record_transition_spec.rb
322
- - spec/statesman/adapters/memory_spec.rb
323
- - spec/statesman/adapters/memory_transition_spec.rb
324
- - spec/statesman/adapters/shared_examples.rb
325
- - spec/statesman/callback_spec.rb
326
- - spec/statesman/config_spec.rb
327
- - spec/statesman/exceptions_spec.rb
328
- - spec/statesman/guard_spec.rb
329
- - spec/statesman/machine_spec.rb
330
- - spec/statesman/utils_spec.rb
331
- - spec/support/active_record.rb
332
- - spec/support/generators_shared_examples.rb
301
+ test_files: []
data/.circleci/config.yml DELETED
@@ -1,127 +0,0 @@
1
- ---
2
- version: 2.1
3
-
4
- references:
5
- bundle_install: &bundle_install
6
- run:
7
- name: Bundle
8
- command: |
9
- gem install bundler --no-document && \
10
- bundle config set no-cache 'true' && \
11
- bundle config set jobs '4' && \
12
- bundle config set retry '3' && \
13
- bundle install
14
-
15
- cache_bundle: &cache_bundle
16
- save_cache:
17
- key: bundle-<< parameters.ruby_version >>-<< parameters.rails_version >>-{{ checksum "statesman.gemspec" }}-{{ checksum "Gemfile" }}
18
- paths:
19
- - vendor/bundle
20
-
21
- restore_bundle: &restore_bundle
22
- restore_cache:
23
- key: bundle-<< parameters.ruby_version >>-<< parameters.rails_version >>-{{ checksum "statesman.gemspec" }}-{{ checksum "Gemfile" }}
24
-
25
- steps: &steps
26
- - add_ssh_keys
27
- - checkout
28
- - run:
29
- name: "Add dependencies"
30
- command: |
31
- sudo apt-get update && sudo apt-get install -y sqlite3 libsqlite3-dev
32
- - *restore_bundle
33
- - *bundle_install
34
- - *cache_bundle
35
- - run: dockerize -wait tcp://localhost:$DATABASE_DEPENDENCY_PORT -timeout 1m
36
- - run:
37
- name: Run specs
38
- command: |
39
- bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) --profile --format progress --format RspecJunitFormatter -o /tmp/circle_artifacts/rspec.xml
40
- - run:
41
- name: "Rubocop"
42
- command: bundle exec rubocop --extra-details --display-style-guide --parallel --force-exclusion
43
- - store_artifacts:
44
- path: /tmp/circle_artifacts/
45
- - store_test_results:
46
- path: /tmp/circle_artifacts/
47
-
48
- ruby_versions: &ruby_versions
49
- - "2.7"
50
- - "3.0"
51
- - "3.1"
52
-
53
- rails_versions: &rails_versions
54
- - "5.2.7"
55
- - "6.0.4"
56
- - "6.1.5"
57
- - "7.0.2"
58
- - "main"
59
-
60
- mysql_versions: &mysql_versions
61
- - "5.7"
62
-
63
- psql_versions: &psql_versions
64
- - "9.6"
65
-
66
- jobs:
67
- rspec_mysql:
68
- working_directory: /mnt/ramdisk
69
- parameters:
70
- ruby_version:
71
- type: string
72
- rails_version:
73
- type: string
74
- mysql_version:
75
- type: string
76
- docker:
77
- - image: cimg/ruby:<< parameters.ruby_version >>
78
- environment:
79
- CIRCLE_TEST_REPORTS: /tmp/circle_artifacts/
80
- DATABASE_URL: mysql2://foobar:password@127.0.0.1/statesman_test
81
- DATABASE_DEPENDENCY_PORT: "3306"
82
- - image: cimg/mysql:<< parameters.mysql_version >>
83
- environment:
84
- MYSQL_ROOT_PASSWORD: password
85
- MYSQL_USER: foobar
86
- MYSQL_PASSWORD: password
87
- MYSQL_DATABASE: statesman_test
88
- steps: *steps
89
-
90
- rspec_postgres:
91
- working_directory: /mnt/ramdisk
92
- parameters:
93
- ruby_version:
94
- type: string
95
- rails_version:
96
- type: string
97
- psql_version:
98
- type: string
99
- docker:
100
- - image: cimg/ruby:<< parameters.ruby_version >>
101
- environment:
102
- CIRCLE_TEST_REPORTS: /tmp/circle_artifacts/
103
- DATABASE_URL: postgres://postgres@localhost/statesman_test
104
- DATABASE_DEPENDENCY_PORT: "5432"
105
- - image: circleci/postgres:<< parameters.psql_version >>
106
- environment:
107
- POSTGRES_USER: postgres
108
- POSTGRES_DB: statesman_test
109
- POSTGRES_PASSWORD: statesman
110
- steps: *steps
111
-
112
- workflows:
113
- version: 2
114
- tests:
115
- jobs:
116
- - rspec_mysql:
117
- matrix:
118
- parameters:
119
- mysql_version: *mysql_versions
120
- ruby_version: *ruby_versions
121
- rails_version: *rails_versions
122
- - rspec_postgres:
123
- matrix:
124
- parameters:
125
- psql_version: *psql_versions
126
- ruby_version: *ruby_versions
127
- rails_version: *rails_versions