stateful_models 0.0.2 → 0.0.4
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/CHANGELOG.md +19 -2
- data/README.md +68 -0
- data/lib/generators/has_states/install/install_generator.rb +27 -9
- data/lib/generators/has_states/install/templates/create_has_states_states.rb.erb +1 -1
- data/lib/generators/has_states/install/templates/create_indexes_on_has_states_states.rb.erb +11 -0
- data/lib/has_states/base.rb +30 -1
- data/lib/has_states/configuration/model_configuration.rb +16 -0
- data/lib/has_states/configuration/state_type_configuration.rb +3 -1
- data/lib/has_states/configuration.rb +75 -16
- data/lib/has_states/state.rb +1 -2
- data/lib/has_states/stateable.rb +11 -8
- data/lib/has_states/version.rb +1 -1
- data/lib/has_states.rb +1 -1
- data/spec/dummy/Gemfile.lock +123 -101
- data/spec/dummy/config/initializers/has_states.rb +44 -41
- data/spec/dummy/db/migrate/20241223212128_create_has_states_states.rb +2 -2
- data/spec/dummy/db/migrate/20250114175939_create_indexes_on_has_states_states.rb +10 -0
- data/spec/dummy/db/schema.rb +26 -22
- data/spec/dummy/log/development.log +88 -0
- data/spec/dummy/log/test.log +33161 -0
- data/spec/dummy/storage/development.sqlite3 +0 -0
- data/spec/dummy/storage/test.sqlite3 +0 -0
- data/spec/generators/has_states/install_generator_spec.rb +1 -1
- data/spec/generators/{tmp/db/migrate/20241223213845_create_has_states_states.rb → templates/db/migrate/20250322001530_create_has_states_states.rb} +1 -1
- data/spec/generators/templates/db/migrate/20250322001530_create_indexes_on_has_states_states.rb +11 -0
- data/spec/has_states/configuration_spec.rb +57 -0
- data/spec/has_states/state_limit_spec.rb +107 -0
- data/spec/has_states/state_metadata_schema_spec.rb +75 -0
- data/spec/has_states/state_spec.rb +38 -15
- data/spec/has_states/stateable_spec.rb +183 -0
- data/spec/rails_helper.rb +1 -0
- metadata +26 -6
- /data/spec/generators/{tmp → templates}/config/initializers/has_states.rb +0 -0
Binary file
|
Binary file
|
@@ -4,7 +4,7 @@ require 'rails_helper'
|
|
4
4
|
require 'generators/has_states/install/install_generator'
|
5
5
|
|
6
6
|
RSpec.describe HasStates::InstallGenerator do
|
7
|
-
let(:destination) { File.expand_path('../
|
7
|
+
let(:destination) { File.expand_path('../templates', __dir__) }
|
8
8
|
|
9
9
|
before do
|
10
10
|
FileUtils.rm_rf(destination)
|
data/spec/generators/templates/db/migrate/20250322001530_create_indexes_on_has_states_states.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateIndexesOnHasStatesStates < ActiveRecord::Migration[8.0]
|
2
|
+
def change
|
3
|
+
change_table :has_states_states do |t|
|
4
|
+
t.index %i[stateable_id state_type]
|
5
|
+
t.index %i[stateable_id state_type status]
|
6
|
+
t.index %i[stateable_id state_type created_at]
|
7
|
+
t.index %i[stateable_id state_type status created_at]
|
8
|
+
t.index %i[stateable_type stateable_id]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -74,6 +74,63 @@ RSpec.describe HasStates::Configuration do
|
|
74
74
|
end
|
75
75
|
end.to raise_error(ArgumentError, /must be an ActiveRecord model/)
|
76
76
|
end
|
77
|
+
|
78
|
+
it 'allows setting a limit on the number of states' do
|
79
|
+
configuration.configure_model User do |model|
|
80
|
+
model.state_type :kyc do |type|
|
81
|
+
type.statuses = %w[pending completed]
|
82
|
+
type.limit = 2
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
expect(configuration.limit_for(User, 'kyc')).to eq(2)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'returns nil for limit when no limit is set' do
|
90
|
+
configuration.configure_model User do |model|
|
91
|
+
model.state_type :kyc do |type|
|
92
|
+
type.statuses = %w[pending completed]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
expect(configuration.limit_for(User, 'kyc')).to be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when a metadata schema is set' do
|
100
|
+
let(:schema) do
|
101
|
+
{
|
102
|
+
type: :object,
|
103
|
+
properties: {
|
104
|
+
name: { type: :string },
|
105
|
+
age: { type: :integer, minimum: 18 }
|
106
|
+
},
|
107
|
+
required: %i[name age]
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
before do
|
112
|
+
configuration.configure_model User do |model|
|
113
|
+
model.state_type :kyc do |type|
|
114
|
+
type.statuses = %w[pending completed]
|
115
|
+
type.metadata_schema = schema
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'allows setting a metadata schema for validation' do
|
121
|
+
expect(configuration.metadata_schema_for(User, 'kyc')).to eq(schema)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'returns nil for metadata_schema when no schema is set' do
|
126
|
+
configuration.configure_model User do |model|
|
127
|
+
model.state_type :kyc do |type|
|
128
|
+
type.statuses = %w[pending completed]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
expect(configuration.metadata_schema_for(User, 'kyc')).to be_nil
|
133
|
+
end
|
77
134
|
end
|
78
135
|
|
79
136
|
describe 'callbacks' do
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe 'State Limit Feature', type: :model do
|
6
|
+
before do
|
7
|
+
HasStates.configuration.clear_callbacks!
|
8
|
+
HasStates.configuration.model_configurations.clear
|
9
|
+
|
10
|
+
HasStates.configure do |config|
|
11
|
+
config.configure_model User do |model|
|
12
|
+
model.state_type :double_limit_state do |type|
|
13
|
+
type.statuses = %w[pending completed]
|
14
|
+
type.limit = 2
|
15
|
+
end
|
16
|
+
|
17
|
+
model.state_type :no_limit_state do |type|
|
18
|
+
type.statuses = %w[pending completed]
|
19
|
+
# No limit set for this type
|
20
|
+
end
|
21
|
+
|
22
|
+
model.state_type :single_limit_state do |type|
|
23
|
+
type.statuses = %w[pending completed]
|
24
|
+
type.limit = 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'state limit validation' do
|
31
|
+
let(:user) { create(:user) }
|
32
|
+
|
33
|
+
context 'with no limit set' do
|
34
|
+
it 'allows creating multiple states of the same type' do
|
35
|
+
# Create multiple onboarding states (no limit)
|
36
|
+
expect do
|
37
|
+
3.times { user.add_state('no_limit_state', status: 'pending') }
|
38
|
+
end.not_to raise_error
|
39
|
+
|
40
|
+
expect(user.states.where(state_type: 'no_limit_state').count).to eq(3)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with a limit set' do
|
45
|
+
it 'allows creating states up to the limit' do
|
46
|
+
# Create double_limit_state states up to the limit (2)
|
47
|
+
expect do
|
48
|
+
2.times { user.add_state('double_limit_state', status: 'pending') }
|
49
|
+
end.not_to raise_error
|
50
|
+
|
51
|
+
expect(user.states.where(state_type: 'double_limit_state').count).to eq(2)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'prevents creating states beyond the limit' do
|
55
|
+
# Create double_limit_state states up to the limit
|
56
|
+
2.times { user.add_state('double_limit_state', status: 'pending') }
|
57
|
+
|
58
|
+
# Try to create one more (exceeding the limit)
|
59
|
+
expect do
|
60
|
+
user.add_state('double_limit_state', status: 'pending')
|
61
|
+
end.to raise_error(ActiveRecord::RecordInvalid, /maximum number of double_limit_state states/)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'enforces a limit of 1 correctly' do
|
65
|
+
# Create one state
|
66
|
+
user.add_state('single_limit_state', status: 'pending')
|
67
|
+
|
68
|
+
# Try to create another one (exceeding the limit of 1)
|
69
|
+
expect do
|
70
|
+
user.add_state('single_limit_state', status: 'completed')
|
71
|
+
end.to raise_error(ActiveRecord::RecordInvalid, /maximum number of single_limit_state states/)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'with different state types' do
|
76
|
+
it 'applies limits independently to each state type' do
|
77
|
+
# Create states up to the limit for double_limit_state
|
78
|
+
2.times { user.add_state('double_limit_state', status: 'pending') }
|
79
|
+
|
80
|
+
# Create states for no_limit_state (no limit)
|
81
|
+
3.times { user.add_state('no_limit_state', status: 'pending') }
|
82
|
+
|
83
|
+
# Create one state for single_limit_state (limit 1)
|
84
|
+
user.add_state('single_limit_state', status: 'pending')
|
85
|
+
|
86
|
+
# Verify counts
|
87
|
+
expect(user.states.where(state_type: 'double_limit_state').count).to eq(2)
|
88
|
+
expect(user.states.where(state_type: 'no_limit_state').count).to eq(3)
|
89
|
+
expect(user.states.where(state_type: 'single_limit_state').count).to eq(1)
|
90
|
+
|
91
|
+
# Try to exceed limits
|
92
|
+
expect do
|
93
|
+
user.add_state('double_limit_state', status: 'completed')
|
94
|
+
end.to raise_error(ActiveRecord::RecordInvalid, /maximum number of double_limit_state states/)
|
95
|
+
|
96
|
+
expect do
|
97
|
+
user.add_state('single_limit_state', status: 'completed')
|
98
|
+
end.to raise_error(ActiveRecord::RecordInvalid, /maximum number of single_limit_state states/)
|
99
|
+
|
100
|
+
# But can still add more no_limit_state states
|
101
|
+
expect do
|
102
|
+
user.add_state('no_limit_state', status: 'completed')
|
103
|
+
end.not_to raise_error
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe 'Metadata Schema Validation', type: :model do
|
6
|
+
let(:user) { create(:user) }
|
7
|
+
let(:schema) do
|
8
|
+
{
|
9
|
+
type: :object,
|
10
|
+
properties: {
|
11
|
+
name: { type: :string },
|
12
|
+
age: {
|
13
|
+
type: :integer,
|
14
|
+
minimum: 18
|
15
|
+
}
|
16
|
+
},
|
17
|
+
required: %i[name age]
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
before do
|
22
|
+
HasStates.configuration.clear_callbacks!
|
23
|
+
HasStates.configuration.model_configurations.clear
|
24
|
+
|
25
|
+
HasStates.configure do |config|
|
26
|
+
config.configure_model User do |model|
|
27
|
+
model.state_type :schema_enabled_state do |type|
|
28
|
+
type.statuses = %w[pending completed rejected]
|
29
|
+
type.metadata_schema = schema
|
30
|
+
end
|
31
|
+
|
32
|
+
model.state_type :no_schema_state do |type|
|
33
|
+
type.statuses = %w[active inactive]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'validation' do
|
40
|
+
context 'with a valid schema' do
|
41
|
+
it 'validates valid metadata' do
|
42
|
+
# Invalid metadata (too young < 18)
|
43
|
+
expect do
|
44
|
+
user.add_state('schema_enabled_state', status: 'pending', metadata: { name: 'John Doe', age: 17 })
|
45
|
+
end.to raise_error(ActiveRecord::RecordInvalid, /did not have a minimum value of 18/)
|
46
|
+
|
47
|
+
# Invalid metadata (missing required fields)
|
48
|
+
expect do
|
49
|
+
user.add_state('schema_enabled_state', status: 'pending', metadata: { age: 25 })
|
50
|
+
end.to raise_error(ActiveRecord::RecordInvalid, /did not contain a required property of 'name'/)
|
51
|
+
|
52
|
+
# Valid metadata
|
53
|
+
state = user.add_state('schema_enabled_state', status: 'pending', metadata: { name: 'John Doe', age: 25 })
|
54
|
+
|
55
|
+
expect(state).to be_valid
|
56
|
+
expect(state.persisted?).to be true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'with no schema defined' do
|
61
|
+
it 'accepts any metadata for state types without a schema' do
|
62
|
+
state = user.add_state('no_schema_state', status: 'active', metadata: {
|
63
|
+
anything: 'goes',
|
64
|
+
nested: {
|
65
|
+
data: 'is fine too'
|
66
|
+
},
|
67
|
+
numbers: [1, 2, 3]
|
68
|
+
})
|
69
|
+
|
70
|
+
expect(state).to be_valid
|
71
|
+
expect(state.persisted?).to be true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -22,6 +22,28 @@ RSpec.describe HasStates::State, type: :model do
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
describe 'indexes' do
|
26
|
+
it 'defines an index on stateable_type and stateable_id' do
|
27
|
+
expect(subject).to have_db_index(%i[stateable_type stateable_id])
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'defines an index on stateable_id and state_type' do
|
31
|
+
expect(subject).to have_db_index(%i[stateable_id state_type])
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'defines an index on stateable_id, state_type, and status' do
|
35
|
+
expect(subject).to have_db_index(%i[stateable_id state_type status])
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'defines an index on stateable_id, state_type, and created_at' do
|
39
|
+
expect(subject).to have_db_index(%i[stateable_id state_type created_at])
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'defines an index on stateable_id, state_type, status, and created_at' do
|
43
|
+
expect(subject).to have_db_index(%i[stateable_id state_type status created_at])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
25
47
|
describe 'validations' do
|
26
48
|
context 'status' do
|
27
49
|
it 'validates status presence' do
|
@@ -63,22 +85,22 @@ RSpec.describe HasStates::State, type: :model do
|
|
63
85
|
describe 'instance methods' do
|
64
86
|
let(:state) { create(:state, state_type: 'kyc', status: 'pending') }
|
65
87
|
let(:model_config) { HasStates.configuration.model_configurations[User] }
|
66
|
-
let(:all_statuses)
|
67
|
-
model_config.state_types.values.flat_map(&:statuses).uniq
|
68
|
-
end
|
88
|
+
let(:all_statuses) { model_config.state_types.values.flat_map(&:statuses).uniq }
|
69
89
|
|
70
|
-
|
71
|
-
|
72
|
-
|
90
|
+
context 'predicate methods' do
|
91
|
+
it 'defines predicate methods for all configured statuses' do
|
92
|
+
all_statuses.each do |status|
|
93
|
+
expect(state).to respond_to("#{status}?")
|
94
|
+
end
|
73
95
|
end
|
74
|
-
end
|
75
96
|
|
76
|
-
|
77
|
-
|
78
|
-
|
97
|
+
it 'returns true if the status is pending' do
|
98
|
+
expect(state.pending?).to be(true)
|
99
|
+
end
|
79
100
|
|
80
|
-
|
81
|
-
|
101
|
+
it 'returns false if the status is not completed' do
|
102
|
+
expect(state.completed?).to be(false)
|
103
|
+
end
|
82
104
|
end
|
83
105
|
end
|
84
106
|
|
@@ -267,11 +289,12 @@ RSpec.describe HasStates::State, type: :model do
|
|
267
289
|
class KYCState < HasStates::Base
|
268
290
|
validates :metadata, presence: true
|
269
291
|
validate :required_metadata_fields
|
270
|
-
|
292
|
+
|
271
293
|
private
|
272
|
-
|
294
|
+
|
273
295
|
def required_metadata_fields
|
274
296
|
return if metadata&.key?('document_type')
|
297
|
+
|
275
298
|
errors.add(:metadata, 'must include document_type')
|
276
299
|
end
|
277
300
|
end
|
@@ -306,7 +329,7 @@ RSpec.describe HasStates::State, type: :model do
|
|
306
329
|
|
307
330
|
it 'defaults to HasStates::State when no state_class specified' do
|
308
331
|
state = user.add_state('kyc', status: 'pending')
|
309
|
-
|
332
|
+
|
310
333
|
expect(state).to be_a(HasStates::State)
|
311
334
|
expect(state).to be_valid
|
312
335
|
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe HasStates::Stateable do
|
4
|
+
# Create a test class that includes the concern
|
5
|
+
let(:test_class) do
|
6
|
+
Class.new do
|
7
|
+
include HasStates::Stateable
|
8
|
+
include ActiveRecord::Model
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:user) { create(:user) } # Assuming you have a User factory
|
13
|
+
|
14
|
+
before do
|
15
|
+
# HasStates.configuration.clear_callbacks!
|
16
|
+
# HasStates.configuration.model_configurations.clear
|
17
|
+
|
18
|
+
HasStates.configure do |config|
|
19
|
+
config.configure_model User do |model|
|
20
|
+
model.state_type :test_type do |type|
|
21
|
+
type.statuses = %w[pending completed]
|
22
|
+
end
|
23
|
+
|
24
|
+
model.state_type :other_test_type do |type|
|
25
|
+
type.statuses = %w[pending completed]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'associations' do
|
32
|
+
it 'has many states' do
|
33
|
+
association = User.reflect_on_association(:states)
|
34
|
+
|
35
|
+
expect(association.macro).to eq :has_many
|
36
|
+
expect(association.options[:as]).to eq :stateable
|
37
|
+
expect(association.options[:dependent]).to eq :destroy
|
38
|
+
expect(association.options[:class_name]).to eq 'HasStates::Base'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#add_state' do
|
43
|
+
context 'with default parameters' do
|
44
|
+
it 'creates a new state with default values' do
|
45
|
+
state = user.add_state('test_type')
|
46
|
+
|
47
|
+
expect(state).to be_persisted
|
48
|
+
expect(state.metadata).to eq({})
|
49
|
+
expect(state.status).to eq 'pending'
|
50
|
+
expect(state.state_type).to eq 'test_type'
|
51
|
+
expect(state.type).to eq 'HasStates::State'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'with custom parameters' do
|
56
|
+
let(:metadata) { { key: 'value' } }
|
57
|
+
|
58
|
+
it 'creates a new state with provided values' do
|
59
|
+
state = user.add_state('test_type', status: 'completed', metadata: metadata)
|
60
|
+
|
61
|
+
expect(state).to be_persisted
|
62
|
+
expect(state.status).to eq 'completed'
|
63
|
+
expect(state.state_type).to eq 'test_type'
|
64
|
+
expect(state.type).to eq 'HasStates::State'
|
65
|
+
expect(state.metadata).to eq metadata.as_json
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with custom state class' do
|
70
|
+
before(:all) do
|
71
|
+
module HasStates
|
72
|
+
class CustomState < State; end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
after(:all) do
|
77
|
+
HasStates.send(:remove_const, :CustomState)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'creates a new state with the specified class' do
|
81
|
+
state = user.add_state('test_type', state_class: HasStates::CustomState)
|
82
|
+
|
83
|
+
expect(state).to be_persisted
|
84
|
+
expect(state.type).to eq 'HasStates::CustomState'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when validation fails' do
|
89
|
+
before do
|
90
|
+
allow_any_instance_of(HasStates::Base).to receive(:save!).and_raise(ActiveRecord::RecordInvalid)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'raises an ActiveRecord::RecordInvalid error' do
|
94
|
+
expect { user.add_state('test_type') }.to raise_error(ActiveRecord::RecordInvalid)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#current_state' do
|
100
|
+
let!(:latest_state) { user.add_state('test_type', status: 'completed') }
|
101
|
+
|
102
|
+
before do
|
103
|
+
travel_to 1.day.ago do
|
104
|
+
3.times { user.add_state('test_type', status: 'pending') }
|
105
|
+
2.times { user.add_state('other_test_type', status: 'pending') }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'returns the most recent state for the given type' do
|
110
|
+
expect(user.current_state('test_type')).to eq latest_state
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'returns nil when no state exists for the given type' do
|
114
|
+
expect(user.current_state('nonexistent')).to be_nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#current_states' do
|
119
|
+
before do
|
120
|
+
3.times { user.add_state('test_type', status: 'pending') }
|
121
|
+
3.times { user.add_state('other_test_type', status: 'completed') }
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'returns all states for the given type ordered by creation time' do
|
125
|
+
test_types = user.current_states('test_type')
|
126
|
+
|
127
|
+
expect(test_types.size).to eq 3
|
128
|
+
expect(test_types).to eq test_types.sort_by(&:created_at).reverse
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'returns empty relation when no states exist for the given type' do
|
132
|
+
nonexistent_states = user.current_states('nonexistent')
|
133
|
+
expect(nonexistent_states).to be_empty
|
134
|
+
expect(nonexistent_states).to be_a(ActiveRecord::Relation)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe 'query methods' do
|
139
|
+
before do
|
140
|
+
2.times { user.add_state('test_type', status: 'pending') }
|
141
|
+
2.times { user.add_state('other_test_type', status: 'pending') }
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'find one methods' do
|
145
|
+
it 'defines query one methods for configured states' do
|
146
|
+
expect(user).to respond_to('test_type')
|
147
|
+
expect(user).to respond_to('other_test_type')
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'returns the most recent state for the given status' do
|
151
|
+
expect(user.test_type).to eq user.current_state('test_type')
|
152
|
+
expect(user.other_test_type).to eq user.current_state('other_test_type')
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context 'find many methods' do
|
157
|
+
it 'defines query all methods for configured states' do
|
158
|
+
expect(user).to respond_to('test_types')
|
159
|
+
expect(user).to respond_to('other_test_types')
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'returns all states for the given status ordered by creation time' do
|
163
|
+
test_types = user.test_types
|
164
|
+
other_test_types = user.other_test_types
|
165
|
+
|
166
|
+
expect(test_types.size).to eq 2
|
167
|
+
expect(other_test_types.size).to eq 2
|
168
|
+
|
169
|
+
expect(test_types).to eq test_types.sort_by(&:created_at).reverse
|
170
|
+
expect(other_test_types).to eq other_test_types.sort_by(&:created_at).reverse
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Optional: Test the destruction of associated states
|
176
|
+
describe 'destroying the stateable object' do
|
177
|
+
let!(:state) { user.add_state('test_type') }
|
178
|
+
|
179
|
+
it 'destroys associated states when the user is destroyed' do
|
180
|
+
expect { user.destroy }.to change { HasStates::Base.count }.by(-1)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
data/spec/rails_helper.rb
CHANGED
@@ -14,5 +14,6 @@ require_relative 'support/database_cleaner'
|
|
14
14
|
RSpec.configure do |config|
|
15
15
|
config.include(Shoulda::Matchers::ActiveModel, type: :model)
|
16
16
|
config.include(Shoulda::Matchers::ActiveRecord, type: :model)
|
17
|
+
config.include(ActiveSupport::Testing::TimeHelpers)
|
17
18
|
# ... rest of your RSpec configuration
|
18
19
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stateful_models
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Scholl
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
11
|
+
date: 2025-03-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json-schema
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
description: "\n HasStates provides state management and event system capabilities
|
14
28
|
for Ruby objects.\n It allows tracking states, state transitions, and triggering
|
15
29
|
callbacks on state changes.\n "
|
@@ -24,6 +38,7 @@ files:
|
|
24
38
|
- README.md
|
25
39
|
- lib/generators/has_states/install/install_generator.rb
|
26
40
|
- lib/generators/has_states/install/templates/create_has_states_states.rb.erb
|
41
|
+
- lib/generators/has_states/install/templates/create_indexes_on_has_states_states.rb.erb
|
27
42
|
- lib/generators/has_states/install/templates/initializer.rb.erb
|
28
43
|
- lib/has_states.rb
|
29
44
|
- lib/has_states/base.rb
|
@@ -71,6 +86,7 @@ files:
|
|
71
86
|
- spec/dummy/config/routes.rb
|
72
87
|
- spec/dummy/db/migrate/20241221171423_create_test_models.rb
|
73
88
|
- spec/dummy/db/migrate/20241223212128_create_has_states_states.rb
|
89
|
+
- spec/dummy/db/migrate/20250114175939_create_indexes_on_has_states_states.rb
|
74
90
|
- spec/dummy/db/schema.rb
|
75
91
|
- spec/dummy/db/seeds.rb
|
76
92
|
- spec/dummy/log/development.log
|
@@ -81,11 +97,15 @@ files:
|
|
81
97
|
- spec/dummy/tmp/local_secret.txt
|
82
98
|
- spec/factories/has_states.rb
|
83
99
|
- spec/generators/has_states/install_generator_spec.rb
|
84
|
-
- spec/generators/
|
85
|
-
- spec/generators/
|
100
|
+
- spec/generators/templates/config/initializers/has_states.rb
|
101
|
+
- spec/generators/templates/db/migrate/20250322001530_create_has_states_states.rb
|
102
|
+
- spec/generators/templates/db/migrate/20250322001530_create_indexes_on_has_states_states.rb
|
86
103
|
- spec/has_states/callback_spec.rb
|
87
104
|
- spec/has_states/configuration_spec.rb
|
105
|
+
- spec/has_states/state_limit_spec.rb
|
106
|
+
- spec/has_states/state_metadata_schema_spec.rb
|
88
107
|
- spec/has_states/state_spec.rb
|
108
|
+
- spec/has_states/stateable_spec.rb
|
89
109
|
- spec/has_states_spec.rb
|
90
110
|
- spec/rails_helper.rb
|
91
111
|
- spec/spec_helper.rb
|
@@ -117,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
137
|
- !ruby/object:Gem::Version
|
118
138
|
version: '0'
|
119
139
|
requirements: []
|
120
|
-
rubygems_version: 3.5.
|
140
|
+
rubygems_version: 3.5.9
|
121
141
|
signing_key:
|
122
142
|
specification_version: 4
|
123
143
|
summary: Simple and flexible state management for Ruby objects
|
File without changes
|