statesman 9.0.0 → 13.0.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +31 -0
  3. data/.devcontainer/docker-compose.yml +39 -0
  4. data/.github/workflows/tests.yml +130 -0
  5. data/.gitignore +65 -15
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +11 -1
  8. data/.rubocop_todo.yml +23 -38
  9. data/.ruby-version +1 -1
  10. data/CHANGELOG.md +229 -43
  11. data/CONTRIBUTING.md +14 -13
  12. data/Gemfile +18 -3
  13. data/README.md +203 -74
  14. data/docs/COMPATIBILITY.md +3 -3
  15. data/lib/generators/statesman/active_record_transition_generator.rb +1 -1
  16. data/lib/generators/statesman/generator_helpers.rb +2 -2
  17. data/lib/statesman/adapters/active_record.rb +69 -52
  18. data/lib/statesman/adapters/active_record_queries.rb +15 -7
  19. data/lib/statesman/adapters/active_record_transition.rb +5 -1
  20. data/lib/statesman/adapters/memory.rb +1 -1
  21. data/lib/statesman/adapters/type_safe_active_record_queries.rb +21 -0
  22. data/lib/statesman/callback.rb +2 -2
  23. data/lib/statesman/config.rb +3 -10
  24. data/lib/statesman/exceptions.rb +9 -7
  25. data/lib/statesman/guard.rb +1 -1
  26. data/lib/statesman/machine.rb +60 -0
  27. data/lib/statesman/version.rb +1 -1
  28. data/lib/statesman.rb +5 -5
  29. data/lib/tasks/statesman.rake +5 -5
  30. data/spec/generators/statesman/active_record_transition_generator_spec.rb +7 -1
  31. data/spec/generators/statesman/migration_generator_spec.rb +5 -1
  32. data/spec/spec_helper.rb +44 -7
  33. data/spec/statesman/adapters/active_record_queries_spec.rb +8 -10
  34. data/spec/statesman/adapters/active_record_spec.rb +144 -55
  35. data/spec/statesman/adapters/active_record_transition_spec.rb +5 -2
  36. data/spec/statesman/adapters/memory_spec.rb +0 -1
  37. data/spec/statesman/adapters/memory_transition_spec.rb +0 -1
  38. data/spec/statesman/adapters/shared_examples.rb +6 -7
  39. data/spec/statesman/adapters/type_safe_active_record_queries_spec.rb +206 -0
  40. data/spec/statesman/callback_spec.rb +0 -2
  41. data/spec/statesman/config_spec.rb +0 -2
  42. data/spec/statesman/exceptions_spec.rb +8 -4
  43. data/spec/statesman/guard_spec.rb +0 -2
  44. data/spec/statesman/machine_spec.rb +231 -19
  45. data/spec/statesman/utils_spec.rb +0 -2
  46. data/spec/support/active_record.rb +156 -29
  47. data/spec/support/exactly_query_databases.rb +35 -0
  48. data/statesman.gemspec +2 -17
  49. metadata +14 -238
  50. data/.circleci/config.yml +0 -127
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/active_record"
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
- # Connect to the database for activerecord tests
32
- db_conn_spec = ENV["DATABASE_URL"]
33
- db_conn_spec ||= { adapter: "sqlite3", database: ":memory:" }
34
- ActiveRecord::Base.establish_connection(db_conn_spec)
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
- config.before(:each, active_record: true) do
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
@@ -48,9 +72,12 @@ RSpec.configure do |config|
48
72
  my_namespace_my_active_record_model_transitions
49
73
  other_active_record_models
50
74
  other_active_record_model_transitions
75
+ sti_active_record_models
76
+ sti_active_record_model_transitions
51
77
  ]
52
78
  tables.each do |table_name|
53
79
  sql = "DROP TABLE IF EXISTS #{table_name};"
80
+
54
81
  ActiveRecord::Base.connection.execute(sql)
55
82
  end
56
83
 
@@ -72,6 +99,16 @@ RSpec.configure do |config|
72
99
  OtherActiveRecordModelTransition.reset_column_information
73
100
  end
74
101
 
75
- MyNamespace::MyActiveRecordModelTransition.serialize(:metadata, JSON)
102
+ def prepare_sti_model_table
103
+ CreateStiActiveRecordModelMigration.migrate(:up)
104
+ end
105
+
106
+ def prepare_sti_transitions_table
107
+ CreateStiActiveRecordModelTransitionMigration.migrate(:up)
108
+ StiActiveRecordModelTransition.reset_column_information
109
+ end
76
110
  end
77
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
- require "spec_helper"
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 }
@@ -117,8 +115,8 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
117
115
  subject(:not_in_state) { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
118
116
 
119
117
  it do
120
- expect(not_in_state).to match_array([initial_state_model,
121
- returned_to_initial_model])
118
+ expect(not_in_state).to contain_exactly(initial_state_model,
119
+ returned_to_initial_model)
122
120
  end
123
121
  end
124
122
 
@@ -126,8 +124,8 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
126
124
  subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
127
125
 
128
126
  it do
129
- expect(not_in_state).to match_array([initial_state_model,
130
- returned_to_initial_model])
127
+ expect(not_in_state).to contain_exactly(initial_state_model,
128
+ returned_to_initial_model)
131
129
  end
132
130
  end
133
131
  end
@@ -216,13 +214,13 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
216
214
  context "using old configuration method" do
217
215
  let(:config_type) { :old }
218
216
 
219
- include_examples "testing methods"
217
+ it_behaves_like "testing methods"
220
218
  end
221
219
 
222
220
  context "using new configuration method" do
223
221
  let(:config_type) { :new }
224
222
 
225
- include_examples "testing methods"
223
+ it_behaves_like "testing methods"
226
224
  end
227
225
 
228
226
  context "with no association with the transition class" do
@@ -254,7 +252,7 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
254
252
  end
255
253
 
256
254
  it "does not raise an error" do
257
- expect { check_missing_methods! }.to_not raise_exception(NotImplementedError)
255
+ expect { check_missing_methods! }.to_not raise_exception
258
256
  end
259
257
  end
260
258
 
@@ -1,16 +1,16 @@
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: true do
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)
12
+ prepare_sti_model_table
13
+ prepare_sti_transitions_table
14
14
 
15
15
  Statesman.configure do
16
16
  # Rubocop requires described_class to be used, but this block
@@ -23,8 +23,10 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
23
23
 
24
24
  after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
25
25
 
26
+ let(:model_class) { MyActiveRecordModel }
27
+ let(:transition_class) { MyActiveRecordModelTransition }
26
28
  let(:observer) { double(Statesman::Machine, execute: nil) }
27
- let(:model) { MyActiveRecordModel.create(current_state: :pending) }
29
+ let(:model) { model_class.create(current_state: :pending) }
28
30
 
29
31
  it_behaves_like "an adapter", described_class, MyActiveRecordModelTransition
30
32
 
@@ -33,17 +35,11 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
33
35
  before do
34
36
  metadata_column = double
35
37
  allow(metadata_column).to receive_messages(sql_type: "")
36
- allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
37
- { "metadata" => metadata_column })
38
- if ::ActiveRecord.respond_to?(:gem_version) &&
39
- ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
40
- expect(MyActiveRecordModelTransition).
41
- to receive(:type_for_attribute).with("metadata").
42
- and_return(ActiveRecord::Type::Value.new)
43
- else
44
- expect(MyActiveRecordModelTransition).
45
- to receive_messages(serialized_attributes: {})
46
- 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)
47
43
  end
48
44
 
49
45
  it "raises an exception" do
@@ -60,10 +56,10 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
60
56
  allow(metadata_column).to receive_messages(sql_type: "json")
61
57
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
62
58
  { "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
59
+ if ActiveRecord.respond_to?(:gem_version) &&
60
+ ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
61
+ serialized_type = ActiveRecord::Type::Serialized.new(
62
+ "", ActiveRecord::Coders::JSON
67
63
  )
68
64
  expect(MyActiveRecordModelTransition).
69
65
  to receive(:type_for_attribute).with("metadata").
@@ -88,18 +84,12 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
88
84
  allow(metadata_column).to receive_messages(sql_type: "jsonb")
89
85
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
90
86
  { "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
95
- )
96
- expect(MyActiveRecordModelTransition).
97
- to receive(:type_for_attribute).with("metadata").
98
- and_return(serialized_type)
99
- else
100
- expect(MyActiveRecordModelTransition).
101
- to receive_messages(serialized_attributes: { "metadata" => "" })
102
- 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)
103
93
  end
104
94
 
105
95
  it "raises an exception" do
@@ -112,15 +102,17 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
112
102
  end
113
103
 
114
104
  describe "#create" do
115
- subject { -> { create } }
105
+ subject(:transition) { create }
116
106
 
117
- let!(:adapter) do
118
- described_class.new(MyActiveRecordModelTransition, model, observer)
119
- end
107
+ let!(:adapter) { described_class.new(transition_class, model, observer) }
120
108
  let(:from) { :x }
121
109
  let(:to) { :y }
122
110
  let(:create) { adapter.create(from, to) }
123
111
 
112
+ it "only connects to the primary database" do
113
+ expect { create }.to exactly_query_databases({ primary: [:writing] })
114
+ end
115
+
124
116
  context "when there is a race" do
125
117
  it "raises a TransitionConflictError" do
126
118
  adapter2 = adapter.dup
@@ -128,7 +120,8 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
128
120
  adapter.last
129
121
  adapter2.create(:y, :z)
130
122
  expect { adapter.create(:y, :z) }.
131
- to raise_exception(Statesman::TransitionConflictError)
123
+ to raise_exception(Statesman::TransitionConflictError).
124
+ and exactly_query_databases({ primary: [:writing] })
132
125
  end
133
126
 
134
127
  it "does not pollute the state when the transition fails" do
@@ -165,27 +158,25 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
165
158
 
166
159
  context "ActiveRecord::RecordNotUnique unrelated to this transition" do
167
160
  let(:error) do
168
- if ::ActiveRecord.respond_to?(:gem_version) &&
169
- ::ActiveRecord.gem_version >= Gem::Version.new("4.0.0")
161
+ if ActiveRecord.respond_to?(:gem_version) &&
162
+ ActiveRecord.gem_version >= Gem::Version.new("4.0.0")
170
163
  ActiveRecord::RecordNotUnique.new("unrelated")
171
164
  else
172
165
  ActiveRecord::RecordNotUnique.new("unrelated", nil)
173
166
  end
174
167
  end
175
168
 
176
- it { is_expected.to raise_exception(ActiveRecord::RecordNotUnique) }
169
+ it { expect { transition }.to raise_exception(ActiveRecord::RecordNotUnique) }
177
170
  end
178
171
 
179
172
  context "other errors" do
180
173
  let(:error) { StandardError }
181
174
 
182
- it { is_expected.to raise_exception(StandardError) }
175
+ it { expect { transition }.to raise_exception(StandardError) }
183
176
  end
184
177
  end
185
178
 
186
179
  describe "updating the most_recent column" do
187
- subject { create }
188
-
189
180
  context "with no previous transition" do
190
181
  its(:most_recent) { is_expected.to eq(true) }
191
182
  end
@@ -302,18 +293,92 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
302
293
  from(true).to be_falsey
303
294
  end
304
295
  end
296
+
297
+ context "when transition uses STI" do
298
+ let(:sti_model) { StiActiveRecordModel.create }
299
+
300
+ let(:adapter_a) do
301
+ described_class.new(
302
+ StiAActiveRecordModelTransition,
303
+ sti_model,
304
+ observer,
305
+ { association_name: :sti_a_active_record_model_transitions },
306
+ )
307
+ end
308
+ let(:adapter_b) do
309
+ described_class.new(
310
+ StiBActiveRecordModelTransition,
311
+ sti_model,
312
+ observer,
313
+ { association_name: :sti_b_active_record_model_transitions },
314
+ )
315
+ end
316
+ let(:create) { adapter_a.create(from, to) }
317
+
318
+ context "with a previous unrelated transition" do
319
+ let!(:transition_b) { adapter_b.create(from, to) }
320
+
321
+ its(:most_recent) { is_expected.to eq(true) }
322
+
323
+ it "doesn't update the previous transition's most_recent flag" do
324
+ expect { create }.
325
+ to_not(change { transition_b.reload.most_recent })
326
+ end
327
+ end
328
+
329
+ context "with previous related and unrelated transitions" do
330
+ let!(:transition_a) { adapter_a.create(from, to) }
331
+ let!(:transition_b) { adapter_b.create(from, to) }
332
+
333
+ its(:most_recent) { is_expected.to eq(true) }
334
+
335
+ it "updates the previous transition's most_recent flag" do
336
+ expect { create }.
337
+ to change { transition_a.reload.most_recent }.
338
+ from(true).to be_falsey
339
+ end
340
+
341
+ it "doesn't update the previous unrelated transition's most_recent flag" do
342
+ expect { create }.
343
+ to_not(change { transition_b.reload.most_recent })
344
+ end
345
+ end
346
+ end
305
347
  end
306
- end
307
348
 
308
- describe "#last" do
309
- let(:adapter) do
310
- described_class.new(MyActiveRecordModelTransition, model, observer)
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
311
370
  end
371
+ end
312
372
 
313
- before { adapter.create(:x, :y) }
373
+ describe "#last" do
374
+ let(:transition_class) { MyActiveRecordModelTransition }
375
+ let(:adapter) { described_class.new(transition_class, model, observer) }
314
376
 
315
377
  context "with a previously looked up transition" do
316
- before { adapter.last }
378
+ before do
379
+ adapter.create(:x, :y)
380
+ adapter.last
381
+ end
317
382
 
318
383
  it "caches the transition" do
319
384
  expect_any_instance_of(MyActiveRecordModel).
@@ -325,8 +390,19 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
325
390
  before { adapter.create(:y, :z, []) }
326
391
 
327
392
  it "retrieves the new transition from the database" do
393
+ expect { adapter.last.to_state }.to exactly_query_databases({ primary: [:writing] })
328
394
  expect(adapter.last.to_state).to eq("z")
329
395
  end
396
+
397
+ context "when using the secondary database" do
398
+ let(:model_class) { SecondaryActiveRecordModel }
399
+ let(:transition_class) { SecondaryActiveRecordModelTransition }
400
+
401
+ it "retrieves the new transition from the database" do
402
+ expect { adapter.last.to_state }.to exactly_query_databases({ secondary: [:writing] })
403
+ expect(adapter.last.to_state).to eq("z")
404
+ end
405
+ end
330
406
  end
331
407
 
332
408
  context "when a new transition has been created elsewhere" do
@@ -369,15 +445,32 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
369
445
  end
370
446
 
371
447
  context "with a pre-fetched transition history" do
372
- before { adapter.create(:x, :y) }
373
-
374
- before { model.my_active_record_model_transitions.load_target }
448
+ before do
449
+ adapter.create(:x, :y)
450
+ model.my_active_record_model_transitions.load_target
451
+ end
375
452
 
376
453
  it "doesn't query the database" do
377
454
  expect(MyActiveRecordModelTransition).to_not receive(:connection)
378
455
  expect(adapter.last.to_state).to eq("y")
379
456
  end
380
457
  end
458
+
459
+ context "without previous transitions" do
460
+ it "does query the database only once" do
461
+ expect(model.my_active_record_model_transitions).
462
+ to receive(:order).once.and_call_original
463
+
464
+ expect(adapter.last).to eq(nil)
465
+ expect(adapter.last).to eq(nil)
466
+ end
467
+ end
468
+ end
469
+
470
+ describe "#reset" do
471
+ it "works with empty cache" do
472
+ expect { model.state_machine.reset }.to_not raise_error
473
+ end
381
474
  end
382
475
 
383
476
  it "resets last with #reload" do
@@ -399,10 +492,6 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
399
492
  CreateNamespacedARModelTransitionMigration.migrate(:up)
400
493
  end
401
494
 
402
- before do
403
- MyNamespace::MyActiveRecordModelTransition.serialize(:metadata, JSON)
404
- end
405
-
406
495
  let(:observer) { double(Statesman::Machine, execute: nil) }
407
496
  let(:model) do
408
497
  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
- expect(transition_class).to receive(:serialize).with(:metadata, JSON).once
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,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "spec_helper"
4
3
  require "statesman/adapters/shared_examples"
5
4
  require "statesman/adapters/memory_transition"
6
5
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "spec_helper"
4
3
  require "statesman/adapters/memory_transition"
5
4
 
6
5
  describe Statesman::Adapters::MemoryTransition do
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "spec_helper"
4
-
5
3
  # All adpators must define seven methods:
6
4
  # initialize: Accepts a transition class, parent model and state_attr.
7
5
  # transition_class: Returns the transition class object passed to initialize.
@@ -30,14 +28,14 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
30
28
  end
31
29
 
32
30
  describe "#create" do
33
- subject { -> { create } }
31
+ subject(:transition) { create }
34
32
 
35
33
  let(:from) { :x }
36
34
  let(:to) { :y }
37
35
  let(:there) { :z }
38
36
  let(:create) { adapter.create(from, to) }
39
37
 
40
- it { is_expected.to change(adapter.history, :count).by(1) }
38
+ it { expect { transition }.to change(adapter.history, :count).by(1) }
41
39
 
42
40
  context "the new transition" do
43
41
  subject(:instance) { create }
@@ -122,9 +120,10 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
122
120
  describe "#last" do
123
121
  subject { adapter.last }
124
122
 
125
- before { adapter.create(:x, :y) }
126
-
127
- before { adapter.create(:y, :z) }
123
+ before do
124
+ adapter.create(:x, :y)
125
+ adapter.create(:y, :z)
126
+ end
128
127
 
129
128
  it { is_expected.to be_a(transition_class) }
130
129
  specify { expect(adapter.last.to_state.to_sym).to eq(:z) }