statesman 10.0.0 → 10.2.0

Sign up to get free protection for your applications and to get access to all the features.
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