statesman 13.0.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/.gitignore +1 -0
- data/CHANGELOG.md +15 -0
- data/README.md +29 -9
- 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 -3
- 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/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/memory_transition_spec.rb +4 -2
- data/spec/statesman/exceptions_spec.rb +10 -0
- data/spec/statesman/machine_spec.rb +36 -3
- data/spec/support/active_record.rb +16 -0
- data/spec/support/generators_shared_examples.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f93bd968475f6cfd090311f9998a2350993df852aa3796fab5d3b52f962d6147
|
4
|
+
data.tar.gz: 3789c10d55df44ee6e921f9d2297d7d0e0767f42521309ac14aed8e49a0986a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13b9e129ec5bb984066a05c408ee99c9965861775269bab232e6aac86ee75b5c5485a14ccc8476b3b96d0bc383cc68a1f67d32cf9d3eea575478e25ed0245d56
|
7
|
+
data.tar.gz: d88507f263405847ea99f8a7cc88ec4e63c3a82addcef3a20e9c0e1c83372df38868278459702c0855e535d7ebca031e18ddaee32031b0beb162932963156325
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,21 @@
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## v13.1.0 9th October 2025
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- State constants for defining available states [#515](https://github.com/gocardless/statesman/pull/515)
|
13
|
+
- from_state method to transition history for easier access to previous states [#493](https://github.com/gocardless/statesman/pull/493)
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
|
17
|
+
- Fixed transition model generator for namespaced models [#555](https://github.com/gocardless/statesman/pull/555)
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
|
21
|
+
- Updated README and clarified wording [#444](https://github.com/gocardless/statesman/pull/444)
|
22
|
+
|
8
23
|
## v13.0.0 29th August 2025
|
9
24
|
|
10
25
|
### Changes
|
data/README.md
CHANGED
@@ -33,7 +33,7 @@ protection.
|
|
33
33
|
To get started, just add Statesman to your `Gemfile`, and then run `bundle`:
|
34
34
|
|
35
35
|
```ruby
|
36
|
-
gem 'statesman', '~>
|
36
|
+
gem 'statesman', '~> 13.0.0'
|
37
37
|
```
|
38
38
|
|
39
39
|
## Usage
|
@@ -233,12 +233,14 @@ or 5. To do that
|
|
233
233
|
t.json :metadata, default: {}
|
234
234
|
```
|
235
235
|
|
236
|
-
|
237
|
-
your transition model
|
238
|
-
|
239
|
-
|
240
|
-
as
|
241
|
-
|
236
|
+
* Remove the `include Statesman::Adapters::ActiveRecordTransition` statement from
|
237
|
+
your transition model, which would've instructed ActiveRecord to serialize the
|
238
|
+
metadata.
|
239
|
+
* The module that you just removed enables customizing the updatated timestamp column
|
240
|
+
as described above. Having removed it, if you want to customise your transition class's
|
241
|
+
"updated timestamp column", you should define a `.updated_timestamp_column` method on
|
242
|
+
your class and return the name of the column as a symbol, or `nil` if you don't want
|
243
|
+
to record an updated timestamp on transitions.
|
242
244
|
|
243
245
|
## Configuration
|
244
246
|
|
@@ -407,6 +409,24 @@ Machine.successors
|
|
407
409
|
}
|
408
410
|
```
|
409
411
|
|
412
|
+
## Class constants
|
413
|
+
Adding a state to a state machine will automatically create a constant for the value, for example:
|
414
|
+
```ruby
|
415
|
+
class OrderStateMachine
|
416
|
+
include Statesman::Machine
|
417
|
+
|
418
|
+
state :pending, initial: true
|
419
|
+
state :checking_out
|
420
|
+
state :cancelled
|
421
|
+
|
422
|
+
# Constants created as a side effect of adding state
|
423
|
+
transition from: PENDING, to: [CHECKING_OUT, CANCELLED]
|
424
|
+
end
|
425
|
+
|
426
|
+
OrderStateMachine::PENDING #=> "pending"
|
427
|
+
OrderStateMachine::CHECKING_OUT # => "checking_out"
|
428
|
+
```
|
429
|
+
|
410
430
|
## Instance methods
|
411
431
|
|
412
432
|
### `Machine#current_state`
|
@@ -541,8 +561,8 @@ model and passing in `transition_class` and `initial_state` as options.
|
|
541
561
|
|
542
562
|
In 4.1.2 and below, these two options had to be defined as methods on the model,
|
543
563
|
but 5.0.0 and above allow this style of configuration as well.
|
544
|
-
The old
|
545
|
-
to be removed in
|
564
|
+
The old way pollutes the model with extra class methods, and is deprecated,
|
565
|
+
to be removed in the future.
|
546
566
|
|
547
567
|
```ruby
|
548
568
|
class Order < ActiveRecord::Base
|
@@ -1,11 +1,13 @@
|
|
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
|
module Statesman
|
7
8
|
class ActiveRecordTransitionGenerator < Rails::Generators::Base
|
8
9
|
include Statesman::GeneratorHelpers
|
10
|
+
include ActiveRecord::Generators::Migration
|
9
11
|
|
10
12
|
desc "Create an ActiveRecord-based transition model" \
|
11
13
|
"with the required attributes"
|
@@ -16,14 +18,11 @@ module Statesman
|
|
16
18
|
source_root File.expand_path("templates", __dir__)
|
17
19
|
|
18
20
|
def create_model_file
|
19
|
-
template("create_migration.rb.erb", migration_file_name)
|
20
21
|
template("active_record_transition_model.rb.erb", model_file_name)
|
21
22
|
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
def migration_file_name
|
26
|
-
"db/migrate/#{next_migration_number}_create_#{table_name}.rb"
|
24
|
+
def create_migration_file
|
25
|
+
migration_template("create_migration.rb.erb", File.join(db_migrate_path, "create_#{table_name}.rb"))
|
27
26
|
end
|
28
27
|
end
|
29
28
|
end
|
@@ -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)
|
@@ -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/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
@@ -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
|
@@ -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,7 +1,15 @@
|
|
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
|
@@ -12,10 +20,14 @@ describe Statesman::Machine do
|
|
12
20
|
|
13
21
|
specify { expect(machine.states).to eq(%w[x y]) }
|
14
22
|
|
23
|
+
specify { expect(machine::X).to eq "x" }
|
24
|
+
|
25
|
+
specify { expect(machine::Y).to eq "y" }
|
26
|
+
|
15
27
|
context "initial" do
|
16
|
-
before { machine.state(:
|
28
|
+
before { machine.state(:z, initial: true) }
|
17
29
|
|
18
|
-
specify { expect(machine.initial_state).to eq("
|
30
|
+
specify { expect(machine.initial_state).to eq("z") }
|
19
31
|
|
20
32
|
context "when an initial state is already defined" do
|
21
33
|
it "raises an error" do
|
@@ -24,6 +36,27 @@ describe Statesman::Machine do
|
|
24
36
|
end
|
25
37
|
end
|
26
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
|
27
60
|
end
|
28
61
|
|
29
62
|
describe ".remove_state" do
|
@@ -70,6 +70,7 @@ end
|
|
70
70
|
class CreateMyActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
71
71
|
def change
|
72
72
|
create_table :my_active_record_model_transitions do |t|
|
73
|
+
t.string :from_state
|
73
74
|
t.string :to_state
|
74
75
|
t.integer :my_active_record_model_id
|
75
76
|
t.integer :sort_key
|
@@ -189,6 +190,7 @@ end
|
|
189
190
|
class CreateOtherActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
190
191
|
def change
|
191
192
|
create_table :other_active_record_model_transitions do |t|
|
193
|
+
t.string :from_state
|
192
194
|
t.string :to_state
|
193
195
|
t.integer :other_active_record_model_id
|
194
196
|
t.integer :sort_key
|
@@ -284,6 +286,7 @@ end
|
|
284
286
|
class CreateNamespacedARModelTransitionMigration < MIGRATION_CLASS
|
285
287
|
def change
|
286
288
|
create_table :my_namespace_my_active_record_model_transitions do |t|
|
289
|
+
t.string :from_state
|
287
290
|
t.string :to_state
|
288
291
|
t.integer :my_active_record_model_id
|
289
292
|
t.integer :sort_key
|
@@ -411,3 +414,16 @@ class CreateStiActiveRecordModelTransitionMigration < MIGRATION_CLASS
|
|
411
414
|
end
|
412
415
|
end
|
413
416
|
end
|
417
|
+
|
418
|
+
def postgres?
|
419
|
+
ActiveRecord::Base.connection.adapter_name.match?(/postgres/i)
|
420
|
+
end
|
421
|
+
|
422
|
+
def mysql?
|
423
|
+
ActiveRecord::Base.connection.adapter_name.match?(/mysql/i) ||
|
424
|
+
ActiveRecord::Base.connection.adapter_name.match?(/trilogy/i)
|
425
|
+
end
|
426
|
+
|
427
|
+
def sqlite?
|
428
|
+
ActiveRecord::Base.connection.adapter_name.match?(/sqlite/i)
|
429
|
+
end
|
@@ -12,8 +12,8 @@ shared_examples "a generator" do
|
|
12
12
|
|
13
13
|
let(:gen) { generator %w[Yummy::Bacon Yummy::BaconTransition] }
|
14
14
|
|
15
|
-
it "invokes
|
16
|
-
expect(gen).to receive(:
|
15
|
+
it "invokes create_migration_file method" do
|
16
|
+
expect(gen).to receive(:create_migration_file)
|
17
17
|
gen.invoke_all
|
18
18
|
end
|
19
19
|
|