statesman 12.1.0 → 13.1.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 +4 -4
- data/.devcontainer/devcontainer.json +31 -0
- data/.devcontainer/docker-compose.yml +39 -0
- data/.github/workflows/tests.yml +42 -24
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +1 -4
- data/.rubocop_todo.yml +23 -38
- data/.ruby-version +1 -1
- data/CHANGELOG.md +31 -7
- data/Gemfile +15 -1
- data/README.md +47 -26
- data/docs/COMPATIBILITY.md +1 -1
- data/lib/generators/statesman/active_record_transition_generator.rb +4 -5
- data/lib/generators/statesman/generator_helpers.rb +28 -14
- data/lib/generators/statesman/migration_generator.rb +4 -8
- data/lib/generators/statesman/templates/active_record_transition_model.rb.erb +3 -3
- data/lib/generators/statesman/templates/create_migration.rb.erb +3 -2
- data/lib/generators/statesman/templates/update_migration.rb.erb +3 -2
- data/lib/statesman/adapters/active_record.rb +9 -5
- data/lib/statesman/adapters/active_record_transition.rb +6 -0
- data/lib/statesman/adapters/memory.rb +1 -1
- data/lib/statesman/adapters/memory_transition.rb +3 -1
- data/lib/statesman/exceptions.rb +2 -0
- data/lib/statesman/guard.rb +1 -1
- data/lib/statesman/machine.rb +16 -0
- data/lib/statesman/version.rb +1 -1
- data/spec/generators/statesman/active_record_transition_generator_spec.rb +18 -9
- data/spec/generators/statesman/migration_generator_spec.rb +20 -13
- data/spec/spec_helper.rb +1 -0
- data/spec/statesman/adapters/active_record_queries_spec.rb +2 -2
- data/spec/statesman/adapters/active_record_spec.rb +8 -6
- data/spec/statesman/adapters/memory_transition_spec.rb +4 -2
- data/spec/statesman/adapters/shared_examples.rb +4 -3
- data/spec/statesman/adapters/type_safe_active_record_queries_spec.rb +1 -1
- data/spec/statesman/exceptions_spec.rb +10 -0
- data/spec/statesman/machine_spec.rb +46 -12
- data/spec/support/active_record.rb +18 -2
- data/spec/support/generators_shared_examples.rb +2 -2
- data/statesman.gemspec +1 -15
- metadata +7 -202
@@ -10,36 +10,44 @@ module Statesman
|
|
10
10
|
"app/models/#{klass.underscore}.rb"
|
11
11
|
end
|
12
12
|
|
13
|
-
def migration_class_name
|
14
|
-
klass.gsub("::", "").pluralize
|
15
|
-
end
|
16
|
-
|
17
|
-
def next_migration_number
|
18
|
-
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
19
|
-
end
|
20
|
-
|
21
13
|
def parent_name
|
22
|
-
parent.
|
14
|
+
parent.underscore.split("/").join("_")
|
23
15
|
end
|
24
16
|
|
25
17
|
def parent_table_name
|
26
|
-
parent.
|
18
|
+
parent.underscore.split("/").join("_").tableize
|
27
19
|
end
|
28
20
|
|
29
21
|
def parent_id
|
30
22
|
parent_name + "_id"
|
31
23
|
end
|
32
24
|
|
33
|
-
def
|
25
|
+
def association_name
|
34
26
|
klass.demodulize.underscore.pluralize
|
35
27
|
end
|
36
28
|
|
29
|
+
def table_name
|
30
|
+
klass.underscore.split("/").join("_").tableize
|
31
|
+
end
|
32
|
+
|
33
|
+
def metadata_column_type
|
34
|
+
if ActiveRecord::Base.connection.supports_json?
|
35
|
+
postgres? ? :jsonb : :json
|
36
|
+
else
|
37
|
+
:text
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
37
41
|
def index_name(index_id)
|
38
42
|
"index_#{table_name}_#{index_id}"
|
39
43
|
end
|
40
44
|
|
45
|
+
def postgres?
|
46
|
+
configuration.adapter.try(:match, /postgres/)
|
47
|
+
end
|
48
|
+
|
41
49
|
def mysql?
|
42
|
-
configuration.
|
50
|
+
configuration.adapter.try(:match, /mysql/)
|
43
51
|
end
|
44
52
|
|
45
53
|
# [] is deprecated and will be removed in 6.2
|
@@ -52,11 +60,17 @@ module Statesman
|
|
52
60
|
end
|
53
61
|
|
54
62
|
def database_supports_partial_indexes?
|
55
|
-
Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(
|
63
|
+
Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?(parent.constantize)
|
56
64
|
end
|
57
65
|
|
58
66
|
def metadata_default_value
|
59
|
-
Utils.rails_5_or_higher? ? "{}" : "{}"
|
67
|
+
Utils.rails_5_or_higher? ? "{}" : "'{}'"
|
68
|
+
end
|
69
|
+
|
70
|
+
def metadata_column_config
|
71
|
+
return if mysql?
|
72
|
+
|
73
|
+
", default: #{metadata_default_value}"
|
60
74
|
end
|
61
75
|
end
|
62
76
|
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails/generators"
|
4
|
+
require "rails/generators/active_record"
|
4
5
|
require "generators/statesman/generator_helpers"
|
5
6
|
|
6
7
|
# Add statesman attributes to a pre-existing transition class
|
7
8
|
module Statesman
|
8
9
|
class MigrationGenerator < Rails::Generators::Base
|
9
10
|
include Statesman::GeneratorHelpers
|
11
|
+
include ActiveRecord::Generators::Migration
|
10
12
|
|
11
13
|
desc "Add the required Statesman attributes to your transition model"
|
12
14
|
|
@@ -15,14 +17,8 @@ module Statesman
|
|
15
17
|
|
16
18
|
source_root File.expand_path("templates", __dir__)
|
17
19
|
|
18
|
-
def
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def file_name
|
25
|
-
"db/migrate/#{next_migration_number}_add_statesman_to_#{table_name}.rb"
|
20
|
+
def create_migration_file
|
21
|
+
migration_template("update_migration.rb.erb", File.join(db_migrate_path, "add_statesman_to_#{table_name}.rb"))
|
26
22
|
end
|
27
23
|
end
|
28
24
|
end
|
@@ -9,17 +9,17 @@ class <%= klass %> < <%= Statesman::Utils.rails_5_or_higher? ? 'ApplicationRecor
|
|
9
9
|
# self.updated_timestamp_column = nil
|
10
10
|
|
11
11
|
<%- unless Statesman::Utils.rails_4_or_higher? -%>
|
12
|
-
attr_accessible :to_state, :metadata, :sort_key
|
12
|
+
attr_accessible :from_state, :to_state, :metadata, :sort_key
|
13
13
|
|
14
14
|
<%- end -%>
|
15
|
-
belongs_to :<%= parent_name %><%= class_name_option %>, inverse_of: :<%=
|
15
|
+
belongs_to :<%= parent_name %><%= class_name_option %>, inverse_of: :<%= association_name %>
|
16
16
|
|
17
17
|
after_destroy :update_most_recent, if: :most_recent?
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def update_most_recent
|
22
|
-
last_transition = <%= parent_name %>.<%=
|
22
|
+
last_transition = <%= parent_name %>.<%= association_name %>.order(:sort_key).last
|
23
23
|
return unless last_transition.present?
|
24
24
|
last_transition.update_column(:most_recent, true)
|
25
25
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
-
class
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration<%= "[#{ActiveRecord::Migration.current_version}]" if Statesman::Utils.rails_5_or_higher? %>
|
2
2
|
def change
|
3
3
|
create_table :<%= table_name %> do |t|
|
4
|
+
t.string :from_state, null: false
|
4
5
|
t.string :to_state, null: false
|
5
|
-
t
|
6
|
+
t.<%= metadata_column_type %> :metadata<%= metadata_column_config %>
|
6
7
|
t.integer :sort_key, null: false
|
7
8
|
t.integer :<%= parent_id %>, null: false
|
8
9
|
t.boolean :most_recent<%= ", null: false" if database_supports_partial_indexes? %>
|
@@ -1,7 +1,8 @@
|
|
1
|
-
class
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration<%= "[#{ActiveRecord::Migration.current_version}]" if Statesman::Utils.rails_5_or_higher? %>
|
2
2
|
def change
|
3
|
+
add_column :<%= table_name %>, :from_state, :string, null: false
|
3
4
|
add_column :<%= table_name %>, :to_state, :string, null: false
|
4
|
-
add_column :<%= table_name %>, :metadata,
|
5
|
+
add_column :<%= table_name %>, :metadata, :<%= metadata_column_type %><%= metadata_column_config%>
|
5
6
|
add_column :<%= table_name %>, :sort_key, :integer, null: false
|
6
7
|
add_column :<%= table_name %>, :<%= parent_id %>, :integer, null: false
|
7
8
|
add_column :<%= table_name %>, :most_recent, null: false
|
@@ -81,7 +81,7 @@ module Statesman
|
|
81
81
|
|
82
82
|
def create_transition(from, to, metadata)
|
83
83
|
transition = transitions_for_parent.build(
|
84
|
-
default_transition_attributes(to, metadata),
|
84
|
+
default_transition_attributes(from, to, metadata),
|
85
85
|
)
|
86
86
|
|
87
87
|
transition_class.transaction(requires_new: true) do
|
@@ -116,13 +116,19 @@ module Statesman
|
|
116
116
|
transition
|
117
117
|
end
|
118
118
|
|
119
|
-
def default_transition_attributes(to, metadata)
|
120
|
-
{
|
119
|
+
def default_transition_attributes(from, to, metadata)
|
120
|
+
attributes = {
|
121
121
|
to_state: to,
|
122
122
|
sort_key: next_sort_key,
|
123
123
|
metadata: metadata,
|
124
124
|
most_recent: not_most_recent_value(db_cast: false),
|
125
125
|
}
|
126
|
+
|
127
|
+
if @transition_class.has_attribute?(:from_state)
|
128
|
+
attributes[:from_state] = from
|
129
|
+
end
|
130
|
+
|
131
|
+
attributes
|
126
132
|
end
|
127
133
|
|
128
134
|
def add_after_commit_callback(from, to, transition)
|
@@ -379,11 +385,9 @@ module Statesman
|
|
379
385
|
true
|
380
386
|
end
|
381
387
|
|
382
|
-
# rubocop: disable Naming/PredicateName
|
383
388
|
def has_transactional_callbacks?
|
384
389
|
true
|
385
390
|
end
|
386
|
-
# rubocop: enable Naming/PredicateName
|
387
391
|
|
388
392
|
def committed!(*)
|
389
393
|
@callback.call
|
@@ -20,7 +20,7 @@ module Statesman
|
|
20
20
|
def create(from, to, metadata = {})
|
21
21
|
from = from.to_s
|
22
22
|
to = to.to_s
|
23
|
-
transition = transition_class.new(to, next_sort_key, metadata)
|
23
|
+
transition = transition_class.new(from, to, next_sort_key, metadata)
|
24
24
|
|
25
25
|
@observer.execute(:before, from, to, transition)
|
26
26
|
@history << transition
|
@@ -5,13 +5,15 @@ module Statesman
|
|
5
5
|
class MemoryTransition
|
6
6
|
attr_accessor :created_at
|
7
7
|
attr_accessor :updated_at
|
8
|
+
attr_accessor :from_state
|
8
9
|
attr_accessor :to_state
|
9
10
|
attr_accessor :sort_key
|
10
11
|
attr_accessor :metadata
|
11
12
|
|
12
|
-
def initialize(to, sort_key, metadata = {})
|
13
|
+
def initialize(from, to, sort_key, metadata = {})
|
13
14
|
@created_at = Time.now
|
14
15
|
@updated_at = Time.now
|
16
|
+
@from_state = from
|
15
17
|
@to_state = to
|
16
18
|
@sort_key = sort_key
|
17
19
|
@metadata = metadata
|
data/lib/statesman/exceptions.rb
CHANGED
data/lib/statesman/guard.rb
CHANGED
data/lib/statesman/machine.rb
CHANGED
@@ -39,6 +39,8 @@ module Statesman
|
|
39
39
|
validate_initial_state(name)
|
40
40
|
@initial_state = name
|
41
41
|
end
|
42
|
+
define_state_constant(name)
|
43
|
+
|
42
44
|
states << name
|
43
45
|
end
|
44
46
|
|
@@ -163,6 +165,20 @@ module Statesman
|
|
163
165
|
|
164
166
|
private
|
165
167
|
|
168
|
+
def define_state_constant(state_name)
|
169
|
+
constant_name = state_name.upcase.gsub(/[^A-Z0-9]/, "_")
|
170
|
+
|
171
|
+
if const_defined?(constant_name)
|
172
|
+
return if const_get(constant_name) == state_name
|
173
|
+
|
174
|
+
raise StateConstantConflictError, "Name conflict: '#{name}::#{constant_name}' is already " \
|
175
|
+
"defined as '#{const_get(constant_name)}' attempting to redefine " \
|
176
|
+
"as '#{state_name}'"
|
177
|
+
else
|
178
|
+
const_set(constant_name, state_name)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
166
182
|
def add_callback(callback_type: nil, callback_class: nil,
|
167
183
|
from: nil, to: nil, &block)
|
168
184
|
validate_callback_type_and_class(callback_type, callback_class)
|
data/lib/statesman/version.rb
CHANGED
@@ -11,23 +11,33 @@ describe Statesman::ActiveRecordTransitionGenerator, type: :generator do
|
|
11
11
|
stub_const("Yummy::BaconTransition", Class.new(ActiveRecord::Base))
|
12
12
|
end
|
13
13
|
|
14
|
+
around { |e| Timecop.freeze(Time.parse("2025-01-01 00:00:00"), &e) }
|
15
|
+
|
14
16
|
it_behaves_like "a generator" do
|
15
|
-
let(:migration_name) { "db/migrate/
|
17
|
+
let(:migration_name) { "db/migrate/create_yummy_bacon_transitions.rb" }
|
16
18
|
end
|
17
19
|
|
18
20
|
describe "creates a migration" do
|
19
|
-
subject(:migration) { file("db/migrate
|
21
|
+
subject(:migration) { file("db/migrate/20250101000000_create_yummy_bacon_transitions.rb") }
|
20
22
|
|
21
23
|
before do
|
22
|
-
allow(Time).to receive(:now).and_return(mock_time)
|
23
24
|
run_generator %w[Yummy::Bacon Yummy::BaconTransition]
|
24
25
|
end
|
25
26
|
|
26
|
-
let(:mock_time) { double("Time", utc: double("UTCTime", strftime: time)) }
|
27
|
-
let(:time) { "5678309" }
|
28
|
-
|
29
27
|
it "includes a foreign key" do
|
30
|
-
expect(migration).to contain("add_foreign_key :
|
28
|
+
expect(migration).to contain("add_foreign_key :yummy_bacon_transitions, :yummy_bacons")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "uses the right column type for Postgres", if: postgres? do
|
32
|
+
expect(migration).to contain("t.jsonb :metadata, default: {}")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "uses the right column type for MySQL", if: mysql? do
|
36
|
+
expect(migration).to contain("t.json :metadata")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "uses the right column type for SQLite", if: sqlite? do
|
40
|
+
expect(migration).to contain("t.json :metadata, default: {}")
|
31
41
|
end
|
32
42
|
end
|
33
43
|
|
@@ -36,8 +46,7 @@ describe Statesman::ActiveRecordTransitionGenerator, type: :generator do
|
|
36
46
|
|
37
47
|
before { run_generator %w[Yummy::Bacon Yummy::BaconTransition] }
|
38
48
|
|
39
|
-
it { is_expected.to contain(/:
|
40
|
-
it { is_expected.to_not contain(%r{:yummy/bacon}) }
|
49
|
+
it { is_expected.to contain(/:bacon_transitions/) }
|
41
50
|
it { is_expected.to contain(/class_name: 'Yummy::Bacon'/) }
|
42
51
|
end
|
43
52
|
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "support/generators_shared_examples"
|
4
4
|
require "generators/statesman/migration_generator"
|
5
|
+
require "rails/generators/testing/behavior"
|
5
6
|
|
6
7
|
describe Statesman::MigrationGenerator, type: :generator do
|
7
8
|
before do
|
@@ -9,40 +10,46 @@ describe Statesman::MigrationGenerator, type: :generator do
|
|
9
10
|
stub_const("Yummy::BaconTransition", Class.new(ActiveRecord::Base))
|
10
11
|
end
|
11
12
|
|
13
|
+
around { |e| Timecop.freeze(Time.parse("2025-01-01 00:00:00"), &e) }
|
14
|
+
|
12
15
|
it_behaves_like "a generator" do
|
13
|
-
let(:migration_name) { "db/migrate/
|
16
|
+
let(:migration_name) { "db/migrate/20250101000000_add_statesman_to_yummy_bacon_transitions.rb" }
|
14
17
|
end
|
15
18
|
|
16
19
|
describe "the model contains the correct words" do
|
17
20
|
subject(:migration) do
|
18
21
|
file(
|
19
|
-
"db/migrate
|
22
|
+
"db/migrate/20250101000000_add_statesman_to_yummy_bacon_transitions.rb",
|
20
23
|
)
|
21
24
|
end
|
22
25
|
|
23
|
-
let(:migration_number) { "5678309" }
|
24
|
-
|
25
|
-
let(:mock_time) do
|
26
|
-
double("Time", utc: double("UTCTime", strftime: migration_number))
|
27
|
-
end
|
28
|
-
|
29
26
|
before do
|
30
|
-
allow(Time).to receive(:now).and_return(mock_time)
|
31
27
|
run_generator %w[Yummy::Bacon Yummy::BaconTransition]
|
32
28
|
end
|
33
29
|
|
34
|
-
it { is_expected.to contain(/:
|
35
|
-
it { is_expected.to_not contain(%r{:yummy/bacon}) }
|
30
|
+
it { is_expected.to contain(/:yummy_bacon_transition/) }
|
36
31
|
it { is_expected.to contain(/null: false/) }
|
37
32
|
|
38
33
|
it "names the sorting index appropriately" do
|
39
34
|
expect(migration).
|
40
|
-
to contain("name: \"
|
35
|
+
to contain("name: \"index_yummy_bacon_transitions_parent_sort\"")
|
41
36
|
end
|
42
37
|
|
43
38
|
it "names the most_recent index appropriately" do
|
44
39
|
expect(migration).
|
45
|
-
to contain("name: \"
|
40
|
+
to contain("name: \"index_yummy_bacon_transitions_parent_most_recent\"")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "uses the right column type for Postgres", if: postgres? do
|
44
|
+
expect(migration).to contain(":metadata, :jsonb")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "uses the right column type for MySQL", if: mysql? do
|
48
|
+
expect(migration).to contain(":metadata, :json")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "uses the right column type for SQLite", if: sqlite? do
|
52
|
+
expect(migration).to contain(":metadata, :json")
|
46
53
|
end
|
47
54
|
end
|
48
55
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -214,13 +214,13 @@ describe Statesman::Adapters::ActiveRecordQueries, :active_record do
|
|
214
214
|
context "using old configuration method" do
|
215
215
|
let(:config_type) { :old }
|
216
216
|
|
217
|
-
|
217
|
+
it_behaves_like "testing methods"
|
218
218
|
end
|
219
219
|
|
220
220
|
context "using new configuration method" do
|
221
221
|
let(:config_type) { :new }
|
222
222
|
|
223
|
-
|
223
|
+
it_behaves_like "testing methods"
|
224
224
|
end
|
225
225
|
|
226
226
|
context "with no association with the transition class" do
|
@@ -375,9 +375,10 @@ describe Statesman::Adapters::ActiveRecord, :active_record do
|
|
375
375
|
let(:adapter) { described_class.new(transition_class, model, observer) }
|
376
376
|
|
377
377
|
context "with a previously looked up transition" do
|
378
|
-
before
|
379
|
-
|
380
|
-
|
378
|
+
before do
|
379
|
+
adapter.create(:x, :y)
|
380
|
+
adapter.last
|
381
|
+
end
|
381
382
|
|
382
383
|
it "caches the transition" do
|
383
384
|
expect_any_instance_of(MyActiveRecordModel).
|
@@ -444,9 +445,10 @@ describe Statesman::Adapters::ActiveRecord, :active_record do
|
|
444
445
|
end
|
445
446
|
|
446
447
|
context "with a pre-fetched transition history" do
|
447
|
-
before
|
448
|
-
|
449
|
-
|
448
|
+
before do
|
449
|
+
adapter.create(:x, :y)
|
450
|
+
model.my_active_record_model_transitions.load_target
|
451
|
+
end
|
450
452
|
|
451
453
|
it "doesn't query the database" do
|
452
454
|
expect(MyActiveRecordModelTransition).to_not receive(:connection)
|
@@ -4,10 +4,12 @@ require "statesman/adapters/memory_transition"
|
|
4
4
|
|
5
5
|
describe Statesman::Adapters::MemoryTransition do
|
6
6
|
describe "#initialize" do
|
7
|
+
let(:from) { :n }
|
7
8
|
let(:to) { :y }
|
8
9
|
let(:sort_key) { 0 }
|
9
|
-
let(:create) { described_class.new(to, sort_key) }
|
10
|
+
let(:create) { described_class.new(from, to, sort_key) }
|
10
11
|
|
12
|
+
specify { expect(create.from_state).to equal(from) }
|
11
13
|
specify { expect(create.to_state).to equal(to) }
|
12
14
|
specify { expect(create.created_at).to be_a(Time) }
|
13
15
|
specify { expect(create.updated_at).to be_a(Time) }
|
@@ -15,7 +17,7 @@ describe Statesman::Adapters::MemoryTransition do
|
|
15
17
|
|
16
18
|
context "with metadata passed" do
|
17
19
|
let(:metadata) { { some: :hash } }
|
18
|
-
let(:create) { described_class.new(to, sort_key, metadata) }
|
20
|
+
let(:create) { described_class.new(from, to, sort_key, metadata) }
|
19
21
|
|
20
22
|
specify { expect(create.metadata).to eq(metadata) }
|
21
23
|
end
|
@@ -120,9 +120,10 @@ shared_examples_for "an adapter" do |adapter_class, transition_class, options =
|
|
120
120
|
describe "#last" do
|
121
121
|
subject { adapter.last }
|
122
122
|
|
123
|
-
before
|
124
|
-
|
125
|
-
|
123
|
+
before do
|
124
|
+
adapter.create(:x, :y)
|
125
|
+
adapter.create(:y, :z)
|
126
|
+
end
|
126
127
|
|
127
128
|
it { is_expected.to be_a(transition_class) }
|
128
129
|
specify { expect(adapter.last.to_state.to_sym).to eq(:z) }
|
@@ -51,6 +51,16 @@ describe "Exceptions" do
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
describe "StateConstantConflictError" do
|
55
|
+
subject(:error) { Statesman::StateConstantConflictError.new }
|
56
|
+
|
57
|
+
its(:message) { is_expected.to eq("Statesman::StateConstantConflictError") }
|
58
|
+
|
59
|
+
its "string matches its message" do
|
60
|
+
expect(error.to_s).to eq(error.message)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
54
64
|
describe "TransitionFailedError" do
|
55
65
|
subject(:error) { Statesman::TransitionFailedError.new("from", "to") }
|
56
66
|
|
@@ -1,20 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
describe Statesman::Machine do
|
4
|
-
let(:machine)
|
4
|
+
let(:machine) do
|
5
|
+
Class.new do
|
6
|
+
include Statesman::Machine
|
7
|
+
|
8
|
+
def self.name
|
9
|
+
"MyStateMachine"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
5
13
|
let(:my_model) { Class.new { attr_accessor :current_state }.new }
|
6
14
|
|
7
15
|
describe ".state" do
|
8
|
-
before
|
9
|
-
|
10
|
-
|
16
|
+
before do
|
17
|
+
machine.state(:x)
|
18
|
+
machine.state(:y)
|
19
|
+
end
|
11
20
|
|
12
21
|
specify { expect(machine.states).to eq(%w[x y]) }
|
13
22
|
|
23
|
+
specify { expect(machine::X).to eq "x" }
|
24
|
+
|
25
|
+
specify { expect(machine::Y).to eq "y" }
|
26
|
+
|
14
27
|
context "initial" do
|
15
|
-
before { machine.state(:
|
28
|
+
before { machine.state(:z, initial: true) }
|
16
29
|
|
17
|
-
specify { expect(machine.initial_state).to eq("
|
30
|
+
specify { expect(machine.initial_state).to eq("z") }
|
18
31
|
|
19
32
|
context "when an initial state is already defined" do
|
20
33
|
it "raises an error" do
|
@@ -23,6 +36,27 @@ describe Statesman::Machine do
|
|
23
36
|
end
|
24
37
|
end
|
25
38
|
end
|
39
|
+
|
40
|
+
context "when state name constant is already defined" do
|
41
|
+
context "with the same value" do
|
42
|
+
it "does not raise an error" do
|
43
|
+
machine.const_set(:SOME_CONST, "some_const")
|
44
|
+
expect { machine.state(:some_const) }.to_not raise_error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with a different value" do
|
49
|
+
it "raises an error about state constant conflict" do
|
50
|
+
machine.const_set(:SOME_CONST, "some_const_different")
|
51
|
+
|
52
|
+
expect { machine.state(:some_const) }.to raise_error(
|
53
|
+
Statesman::StateConstantConflictError, "Name conflict: 'MyStateMachine::SOME_CONST' is " \
|
54
|
+
"already defined as 'some_const_different' " \
|
55
|
+
"attempting to redefine as 'some_const'"
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
26
60
|
end
|
27
61
|
|
28
62
|
describe ".remove_state" do
|
@@ -610,9 +644,10 @@ describe Statesman::Machine do
|
|
610
644
|
end
|
611
645
|
|
612
646
|
context "with multiple transitions" do
|
613
|
-
before
|
614
|
-
|
615
|
-
|
647
|
+
before do
|
648
|
+
instance.transition_to!(:y)
|
649
|
+
instance.transition_to!(:z)
|
650
|
+
end
|
616
651
|
|
617
652
|
it { is_expected.to eq("z") }
|
618
653
|
end
|
@@ -627,12 +662,11 @@ describe Statesman::Machine do
|
|
627
662
|
state :y
|
628
663
|
transition from: :x, to: :y
|
629
664
|
end
|
665
|
+
instance.transition_to!(:y)
|
630
666
|
end
|
631
667
|
|
632
668
|
let(:instance) { machine.new(my_model) }
|
633
669
|
|
634
|
-
before { instance.transition_to!(:y) }
|
635
|
-
|
636
670
|
context "when machine is in given state" do
|
637
671
|
let(:state) { "y" }
|
638
672
|
|
@@ -995,7 +1029,7 @@ describe Statesman::Machine do
|
|
995
1029
|
let(:instance) { machine.new(my_model) }
|
996
1030
|
let(:metadata) { { some: :metadata } }
|
997
1031
|
|
998
|
-
context "when it is
|
1032
|
+
context "when it is successful" do
|
999
1033
|
before do
|
1000
1034
|
expect(instance).to receive(:transition_to!).once.
|
1001
1035
|
with(:some_state, metadata).and_return(:some_state)
|