steady_state 0.1.0 → 1.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/README.md +27 -0
- data/Rakefile +13 -1
- data/lib/steady_state/attribute/state.rb +2 -0
- data/lib/steady_state/attribute/state_machine.rb +2 -0
- data/lib/steady_state/attribute/transition_validator.rb +3 -1
- data/lib/steady_state/attribute.rb +11 -9
- data/lib/steady_state/version.rb +3 -1
- data/lib/steady_state.rb +2 -0
- metadata +32 -21
- data/spec/spec_helper.rb +0 -1
- data/spec/steady_state/attribute_spec.rb +0 -403
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02e5f7eb48f3ada429e0d38598535c77489ab57172d920c2dbb6083a19dc5466
|
4
|
+
data.tar.gz: bbc957eeed78c83b1a7e2c49395df741ba34eea99d3d4adaabdbbabe1298bb06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2f28a60fae258f08f3bb57925419c86015d4dc5d738667fffe8e943223fe3603a0a90960653ff2b1d171dac4f077e50b113b4f5b5024da5cb5c8f58a950a096
|
7
|
+
data.tar.gz: 3fd2023e60bb8ff68c5ea40dcc4a263184fe0698ff3d3507dd7b02ecacf8417bcb9e9b0f44c1d789513994ac5010211b1c50716cdedc7f4eb39f21213a481f5f
|
data/README.md
CHANGED
@@ -231,6 +231,33 @@ As it stands, state history is not preserved, but it is still possible to get a
|
|
231
231
|
material.state.previous_values # => ['solid']
|
232
232
|
```
|
233
233
|
|
234
|
+
### The "States Getter"
|
235
|
+
|
236
|
+
A pluralized, class-level helper method can be used to access all possible state values:
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
Material.states # => ['solid', 'liquid', 'gas']
|
240
|
+
```
|
241
|
+
|
242
|
+
These values respond to reflection methods like `may_become?`, `next_values`, and `previous_values`.
|
243
|
+
|
244
|
+
```
|
245
|
+
Material.states.first.solid? # => true
|
246
|
+
Material.states[1].may_become?('solid') # => false
|
247
|
+
Material.states[1].next_values # => ['gas']
|
248
|
+
```
|
249
|
+
|
250
|
+
The automatic definition of this class method can be disabled by passing `states_getter: false`:
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
steady_state :step, states_getter: false do
|
254
|
+
# ...
|
255
|
+
end
|
256
|
+
|
257
|
+
MyClass.steps # => NoMethodError
|
258
|
+
```
|
259
|
+
|
260
|
+
|
234
261
|
### ActiveModel Support
|
235
262
|
|
236
263
|
SteadyState is also available to classes that are not database-backed, as long as they include the `ActiveModel::Model` mixin:
|
data/Rakefile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'bundler/setup'
|
3
5
|
rescue LoadError
|
@@ -13,4 +15,14 @@ require 'rspec/core'
|
|
13
15
|
require 'rspec/core/rake_task'
|
14
16
|
RSpec::Core::RakeTask.new(:spec)
|
15
17
|
|
16
|
-
|
18
|
+
def default_task
|
19
|
+
if ENV['APPRAISAL_INITIALIZED'] || ENV['CI']
|
20
|
+
%i(rubocop spec)
|
21
|
+
else
|
22
|
+
require 'appraisal'
|
23
|
+
Appraisal::Task.new
|
24
|
+
%i(appraisal)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
task(:default).clear.enhance(default_task)
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SteadyState
|
2
4
|
module Attribute
|
3
5
|
class TransitionValidator < ActiveModel::EachValidator
|
4
6
|
def validate_each(obj, attr_name, _value)
|
5
|
-
obj.errors.add(attr_name, :invalid) if obj.instance_variable_defined?("@last_valid_#{attr_name}")
|
7
|
+
obj.errors.add(attr_name, :invalid) if obj.instance_variable_defined?(:"@last_valid_#{attr_name}")
|
6
8
|
end
|
7
9
|
end
|
8
10
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'steady_state/attribute/state'
|
2
4
|
require 'steady_state/attribute/state_machine'
|
3
5
|
require 'steady_state/attribute/transition_validator'
|
@@ -13,30 +15,30 @@ module SteadyState
|
|
13
15
|
end
|
14
16
|
|
15
17
|
class_methods do
|
16
|
-
def steady_state(attr_name, predicates: true, states_getter: true, scopes: SteadyState.active_record?(self), &block) # rubocop:disable Metrics/
|
18
|
+
def steady_state(attr_name, predicates: true, states_getter: true, scopes: SteadyState.active_record?(self), &block) # rubocop:disable Metrics/
|
17
19
|
overrides = Module.new do
|
18
20
|
define_method :"validate_#{attr_name}_transition_to" do |next_value|
|
19
21
|
if public_send(attr_name).may_become?(next_value)
|
20
|
-
remove_instance_variable("@last_valid_#{attr_name}") if instance_variable_defined?("@last_valid_#{attr_name}")
|
21
|
-
elsif !instance_variable_defined?("@last_valid_#{attr_name}")
|
22
|
-
instance_variable_set("@last_valid_#{attr_name}", public_send(attr_name))
|
22
|
+
remove_instance_variable(:"@last_valid_#{attr_name}") if instance_variable_defined?(:"@last_valid_#{attr_name}")
|
23
|
+
elsif !instance_variable_defined?(:"@last_valid_#{attr_name}")
|
24
|
+
instance_variable_set(:"@last_valid_#{attr_name}", public_send(attr_name))
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
28
|
define_method :"#{attr_name}=" do |value|
|
27
|
-
unless instance_variable_defined?("@#{attr_name}_state_initialized")
|
28
|
-
instance_variable_set("@#{attr_name}_state_initialized", true)
|
29
|
+
unless instance_variable_defined?(:"@#{attr_name}_state_initialized")
|
30
|
+
instance_variable_set(:"@#{attr_name}_state_initialized", true)
|
29
31
|
end
|
30
32
|
public_send(:"validate_#{attr_name}_transition_to", value) if public_send(attr_name).present?
|
31
33
|
super(value)
|
32
34
|
end
|
33
35
|
|
34
36
|
define_method :"#{attr_name}" do |*args, &blk|
|
35
|
-
unless instance_variable_defined?("@#{attr_name}_state_initialized")
|
37
|
+
unless instance_variable_defined?(:"@#{attr_name}_state_initialized")
|
36
38
|
public_send(:"#{attr_name}=", state_machines[attr_name].start) if super(*args, &blk).blank?
|
37
|
-
instance_variable_set("@#{attr_name}_state_initialized", true)
|
39
|
+
instance_variable_set(:"@#{attr_name}_state_initialized", true)
|
38
40
|
end
|
39
|
-
last_valid_value = instance_variable_get("@last_valid_#{attr_name}") if instance_variable_defined?("@last_valid_#{attr_name}")
|
41
|
+
last_valid_value = instance_variable_get(:"@last_valid_#{attr_name}") if instance_variable_defined?(:"@last_valid_#{attr_name}")
|
40
42
|
state_machines[attr_name].new_state super(*args, &blk), last_valid_value
|
41
43
|
end
|
42
44
|
end
|
data/lib/steady_state/version.rb
CHANGED
data/lib/steady_state.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: steady_state
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Griffith
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -16,30 +16,30 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '5.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '5.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '5.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: appraisal
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -53,7 +53,21 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: betterlint
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - ">="
|
@@ -67,7 +81,7 @@ dependencies:
|
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
84
|
+
name: rspec
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - ">="
|
@@ -99,12 +113,11 @@ files:
|
|
99
113
|
- lib/steady_state/attribute/state_machine.rb
|
100
114
|
- lib/steady_state/attribute/transition_validator.rb
|
101
115
|
- lib/steady_state/version.rb
|
102
|
-
|
103
|
-
- spec/steady_state/attribute_spec.rb
|
104
|
-
homepage:
|
116
|
+
homepage:
|
105
117
|
licenses: []
|
106
|
-
metadata:
|
107
|
-
|
118
|
+
metadata:
|
119
|
+
rubygems_mfa_required: 'true'
|
120
|
+
post_install_message:
|
108
121
|
rdoc_options: []
|
109
122
|
require_paths:
|
110
123
|
- lib
|
@@ -112,17 +125,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
125
|
requirements:
|
113
126
|
- - ">="
|
114
127
|
- !ruby/object:Gem::Version
|
115
|
-
version: '0'
|
128
|
+
version: '3.0'
|
116
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
130
|
requirements:
|
118
131
|
- - ">="
|
119
132
|
- !ruby/object:Gem::Version
|
120
133
|
version: '0'
|
121
134
|
requirements: []
|
122
|
-
rubygems_version: 3.
|
123
|
-
signing_key:
|
135
|
+
rubygems_version: 3.5.1
|
136
|
+
signing_key:
|
124
137
|
specification_version: 4
|
125
138
|
summary: Minimalist state management via "an enum with guard rails"
|
126
|
-
test_files:
|
127
|
-
- spec/spec_helper.rb
|
128
|
-
- spec/steady_state/attribute_spec.rb
|
139
|
+
test_files: []
|
data/spec/spec_helper.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'steady_state'
|
@@ -1,403 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe SteadyState::Attribute do
|
4
|
-
let(:steady_state_class) do
|
5
|
-
Class.new do
|
6
|
-
include ActiveModel::Model
|
7
|
-
include SteadyState
|
8
|
-
|
9
|
-
def self.model_name
|
10
|
-
ActiveModel::Name.new(self, nil, 'steady_state_class')
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
subject { steady_state_class.new }
|
15
|
-
|
16
|
-
shared_examples 'a basic state machine' do
|
17
|
-
it 'starts on initial state' do
|
18
|
-
expect(subject.state).to eq 'solid'
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'allows initialization to the initial state' do
|
22
|
-
expect(steady_state_class.new(state: 'solid')).to be_valid
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'allows initialization to other states' do
|
26
|
-
expect(steady_state_class.new(state: 'plasma')).to be_valid
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'adds validation errors when initializing to an invalid state' do
|
30
|
-
object = steady_state_class.new(state: 'banana')
|
31
|
-
expect(object).not_to be_valid
|
32
|
-
expect(object.errors[:state]).to match_array(['is not included in the list'])
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'allows valid transitions' do
|
36
|
-
expect(subject.state.may_become?('liquid')).to eq true
|
37
|
-
expect(subject.state.next_values).to match_array(['liquid'])
|
38
|
-
expect(subject.state.previous_values).to match_array([])
|
39
|
-
expect { subject.state = 'liquid' }.to change { subject.state }.from('solid').to('liquid')
|
40
|
-
expect(subject).to be_valid
|
41
|
-
|
42
|
-
expect(subject.state.may_become?('gas')).to eq true
|
43
|
-
expect(subject.state.next_values).to match_array(['gas'])
|
44
|
-
expect(subject.state.previous_values).to match_array(['solid'])
|
45
|
-
expect { subject.state = 'gas' }.to change { subject.state }.from('liquid').to('gas')
|
46
|
-
expect(subject).to be_valid
|
47
|
-
|
48
|
-
expect(subject.state.may_become?('plasma')).to eq true
|
49
|
-
expect(subject.state.next_values).to match_array(['plasma'])
|
50
|
-
expect(subject.state.previous_values).to match_array(['liquid'])
|
51
|
-
expect { subject.state = 'plasma' }.to change { subject.state }.from('gas').to('plasma')
|
52
|
-
expect(subject).to be_valid
|
53
|
-
expect(subject.state.next_values).to be_empty
|
54
|
-
expect(subject.state.previous_values).to match_array(['gas'])
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'adds validation errors for invalid transitions' do
|
58
|
-
expect(subject.state.may_become?('gas')).to eq false
|
59
|
-
expect { subject.state = 'gas' }.to change { subject.state }.from('solid').to('gas')
|
60
|
-
expect(subject).not_to be_valid
|
61
|
-
expect(subject.errors[:state]).to match_array(['is invalid'])
|
62
|
-
expect(subject.state.next_values).to match_array(['liquid'])
|
63
|
-
expect(subject.state.previous_values).to match_array([])
|
64
|
-
|
65
|
-
expect(subject.state.may_become?('plasma')).to eq false
|
66
|
-
expect { subject.state = 'plasma' }.to change { subject.state }.from('gas').to('plasma')
|
67
|
-
expect(subject).not_to be_valid
|
68
|
-
expect(subject.errors[:state]).to match_array(['is invalid'])
|
69
|
-
expect(subject.state.next_values).to match_array(['liquid'])
|
70
|
-
expect(subject.state.previous_values).to match_array([])
|
71
|
-
|
72
|
-
expect(subject.state.may_become?('solid')).to eq false
|
73
|
-
expect { subject.state = 'solid' }.to change { subject.state }.from('plasma').to('solid')
|
74
|
-
expect(subject).not_to be_valid
|
75
|
-
expect(subject.errors[:state]).to match_array(['is invalid'])
|
76
|
-
expect(subject.state.next_values).to match_array(['liquid'])
|
77
|
-
expect(subject.state.previous_values).to match_array([])
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
context 'with a single field and nothing fancy' do
|
82
|
-
before do
|
83
|
-
steady_state_class.module_eval do
|
84
|
-
attr_accessor :state
|
85
|
-
|
86
|
-
steady_state :state do
|
87
|
-
state 'solid', default: true
|
88
|
-
state 'liquid', from: 'solid'
|
89
|
-
state 'gas', from: 'liquid'
|
90
|
-
state 'plasma', from: 'gas'
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
it_behaves_like 'a basic state machine'
|
96
|
-
|
97
|
-
context 'with inheritance' do
|
98
|
-
let(:subclass) do
|
99
|
-
Class.new(steady_state_class) do
|
100
|
-
def initialize
|
101
|
-
# I do my own thing.
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
subject { subclass.new }
|
106
|
-
|
107
|
-
it_behaves_like 'a basic state machine'
|
108
|
-
end
|
109
|
-
|
110
|
-
context 'with an existing state value' do
|
111
|
-
before do
|
112
|
-
steady_state_class.module_eval do
|
113
|
-
def state
|
114
|
-
@state ||= 'liquid'
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
it 'starts on existing state' do
|
120
|
-
expect(subject.state).to eq 'liquid'
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'does not allow initialization to an invalid next state' do
|
124
|
-
object = steady_state_class.new(state: 'solid')
|
125
|
-
expect(object).not_to be_valid
|
126
|
-
expect(object.errors[:state]).to match_array(['is invalid'])
|
127
|
-
end
|
128
|
-
|
129
|
-
it 'allows initialization to a valid next state' do
|
130
|
-
expect(steady_state_class.new(state: 'gas')).to be_valid
|
131
|
-
end
|
132
|
-
|
133
|
-
it 'adds validation errors when initializing to an invalid state' do
|
134
|
-
object = steady_state_class.new(state: 'banana')
|
135
|
-
expect(object).not_to be_valid
|
136
|
-
expect(object.errors[:state]).to match_array(['is invalid', 'is not included in the list'])
|
137
|
-
end
|
138
|
-
|
139
|
-
it 'allows valid transitions' do
|
140
|
-
expect(subject).to be_valid
|
141
|
-
expect(subject.state.may_become?('gas')).to eq true
|
142
|
-
expect(subject.state.next_values).to match_array(['gas'])
|
143
|
-
expect(subject.state.previous_values).to match_array(['solid'])
|
144
|
-
expect { subject.state = 'gas' }.to change { subject.state }.from('liquid').to('gas')
|
145
|
-
expect(subject).to be_valid
|
146
|
-
|
147
|
-
expect(subject.state.may_become?('plasma')).to eq true
|
148
|
-
expect(subject.state.next_values).to match_array(['plasma'])
|
149
|
-
expect(subject.state.previous_values).to match_array(['liquid'])
|
150
|
-
expect { subject.state = 'plasma' }.to change { subject.state }.from('gas').to('plasma')
|
151
|
-
expect(subject).to be_valid
|
152
|
-
expect(subject.state.next_values).to be_empty
|
153
|
-
expect(subject.state.previous_values).to match_array(['gas'])
|
154
|
-
end
|
155
|
-
|
156
|
-
it 'adds validation errors for invalid transitions' do
|
157
|
-
expect(subject.state.may_become?('plasma')).to eq false
|
158
|
-
expect { subject.state = 'plasma' }.to change { subject.state }.from('liquid').to('plasma')
|
159
|
-
expect(subject).not_to be_valid
|
160
|
-
expect(subject.errors[:state]).to match_array(['is invalid'])
|
161
|
-
expect(subject.state.next_values).to match_array(['gas'])
|
162
|
-
expect(subject.state.previous_values).to match_array(['solid'])
|
163
|
-
|
164
|
-
expect(subject.state.may_become?('solid')).to eq false
|
165
|
-
expect { subject.state = 'solid' }.to change { subject.state }.from('plasma').to('solid')
|
166
|
-
expect(subject).not_to be_valid
|
167
|
-
expect(subject.errors[:state]).to match_array(['is invalid'])
|
168
|
-
expect(subject.state.next_values).to match_array(['gas'])
|
169
|
-
expect(subject.state.previous_values).to match_array(['solid'])
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
context 'with a field reachable by multiple states' do
|
175
|
-
before do
|
176
|
-
steady_state_class.module_eval do
|
177
|
-
attr_accessor :step
|
178
|
-
|
179
|
-
steady_state :step do
|
180
|
-
state 'step-1', default: true
|
181
|
-
state 'step-2', from: 'step-1'
|
182
|
-
state 'cancelled', from: %w(step-1 step-2)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
it 'allows transition from first state' do
|
188
|
-
expect(subject.step.may_become?('step-1')).to eq false
|
189
|
-
expect(subject.step.may_become?('step-2')).to eq true
|
190
|
-
expect(subject.step.may_become?('cancelled')).to eq true
|
191
|
-
expect(subject.step.next_values).to match_array(['cancelled', 'step-2'])
|
192
|
-
expect(subject.step.previous_values).to match_array([])
|
193
|
-
expect { subject.step = 'cancelled' }.to change { subject.step }.from('step-1').to('cancelled')
|
194
|
-
expect(subject.step.next_values).to match_array([])
|
195
|
-
expect(subject.step.previous_values).to match_array(['step-1', 'step-2'])
|
196
|
-
expect(subject).to be_valid
|
197
|
-
end
|
198
|
-
|
199
|
-
it 'allows transition from second state' do
|
200
|
-
expect(subject.step.may_become?('step-1')).to eq false
|
201
|
-
expect(subject.step.may_become?('step-2')).to eq true
|
202
|
-
expect(subject.step.may_become?('cancelled')).to eq true
|
203
|
-
expect(subject.step.next_values).to match_array(['cancelled', 'step-2'])
|
204
|
-
expect(subject.step.previous_values).to match_array([])
|
205
|
-
expect { subject.step = 'step-2' }.to change { subject.step }.from('step-1').to('step-2')
|
206
|
-
expect(subject).to be_valid
|
207
|
-
|
208
|
-
expect(subject.step.may_become?('step-1')).to eq false
|
209
|
-
expect(subject.step.may_become?('step-2')).to eq false
|
210
|
-
expect(subject.step.may_become?('cancelled')).to eq true
|
211
|
-
expect(subject.step.next_values).to match_array(['cancelled'])
|
212
|
-
expect(subject.step.previous_values).to match_array(['step-1'])
|
213
|
-
expect { subject.step = 'cancelled' }.to change { subject.step }.from('step-2').to('cancelled')
|
214
|
-
expect(subject.step.next_values).to match_array([])
|
215
|
-
expect(subject.step.previous_values).to match_array(['step-1', 'step-2'])
|
216
|
-
expect(subject).to be_valid
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
context 'with the predicates option' do
|
221
|
-
before do
|
222
|
-
options = opts
|
223
|
-
steady_state_class.module_eval do
|
224
|
-
attr_accessor :door
|
225
|
-
|
226
|
-
steady_state :door, options do
|
227
|
-
state 'open', default: true
|
228
|
-
state 'closed', from: 'open'
|
229
|
-
state 'locked', from: 'closed'
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
context 'default' do
|
235
|
-
let(:opts) { {} }
|
236
|
-
|
237
|
-
it 'defines a predicate method for each state' do
|
238
|
-
expect(subject).to respond_to(:open?)
|
239
|
-
expect(subject).to respond_to(:closed?)
|
240
|
-
expect(subject).to respond_to(:locked?)
|
241
|
-
|
242
|
-
expect(subject.open?).to eq true
|
243
|
-
expect(subject.closed?).to eq false
|
244
|
-
expect(subject.locked?).to eq false
|
245
|
-
|
246
|
-
subject.door = 'closed'
|
247
|
-
expect(subject.open?).to eq false
|
248
|
-
expect(subject.closed?).to eq true
|
249
|
-
expect(subject.locked?).to eq false
|
250
|
-
|
251
|
-
subject.door = 'locked'
|
252
|
-
expect(subject.open?).to eq false
|
253
|
-
expect(subject.closed?).to eq false
|
254
|
-
expect(subject.locked?).to eq true
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
context 'enabled' do
|
259
|
-
let(:opts) { { predicates: true } }
|
260
|
-
|
261
|
-
it 'defines a predicate method for each state' do
|
262
|
-
expect(subject).to respond_to(:open?)
|
263
|
-
expect(subject).to respond_to(:closed?)
|
264
|
-
expect(subject).to respond_to(:locked?)
|
265
|
-
|
266
|
-
expect(subject.open?).to eq true
|
267
|
-
expect(subject.closed?).to eq false
|
268
|
-
expect(subject.locked?).to eq false
|
269
|
-
|
270
|
-
subject.door = 'closed'
|
271
|
-
expect(subject.open?).to eq false
|
272
|
-
expect(subject.closed?).to eq true
|
273
|
-
expect(subject.locked?).to eq false
|
274
|
-
|
275
|
-
subject.door = 'locked'
|
276
|
-
expect(subject.open?).to eq false
|
277
|
-
expect(subject.closed?).to eq false
|
278
|
-
expect(subject.locked?).to eq true
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
context 'disabled' do
|
283
|
-
let(:opts) { { predicates: false } }
|
284
|
-
|
285
|
-
it 'does not define predicate methods' do
|
286
|
-
expect(subject).not_to respond_to(:open?)
|
287
|
-
expect(subject).not_to respond_to(:closed?)
|
288
|
-
expect(subject).not_to respond_to(:locked?)
|
289
|
-
end
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
context 'with the states_getter option' do
|
294
|
-
let(:query_object) { double(where: []) } # rubocop:disable RSpec/VerifiedDoubles
|
295
|
-
|
296
|
-
before do
|
297
|
-
options = opts
|
298
|
-
steady_state_class.module_eval do
|
299
|
-
attr_accessor :car
|
300
|
-
|
301
|
-
steady_state :car, options do
|
302
|
-
state 'driving', default: true
|
303
|
-
state 'stopped', from: 'driving'
|
304
|
-
state 'parked', from: 'stopped'
|
305
|
-
end
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
context 'default' do
|
310
|
-
let(:opts) { {} }
|
311
|
-
|
312
|
-
it 'defines states getter method' do
|
313
|
-
expect(steady_state_class.cars).to eq %w(driving stopped parked)
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
context 'disabled' do
|
318
|
-
let(:opts) { { states_getter: false } }
|
319
|
-
|
320
|
-
it 'does not define states getter method' do
|
321
|
-
expect { steady_state_class.cars }.to raise_error(NoMethodError, /undefined method `cars'/)
|
322
|
-
end
|
323
|
-
end
|
324
|
-
end
|
325
|
-
|
326
|
-
context 'with the scopes option' do
|
327
|
-
let(:query_object) { double(where: []) } # rubocop:disable RSpec/VerifiedDoubles
|
328
|
-
|
329
|
-
before do
|
330
|
-
options = opts
|
331
|
-
steady_state_class.module_eval do
|
332
|
-
attr_accessor :car
|
333
|
-
|
334
|
-
def self.defined_scopes
|
335
|
-
@defined_scopes ||= {}
|
336
|
-
end
|
337
|
-
|
338
|
-
def self.scope(name, callable)
|
339
|
-
defined_scopes[name] ||= callable
|
340
|
-
end
|
341
|
-
|
342
|
-
steady_state :car, options do
|
343
|
-
state 'driving', default: true
|
344
|
-
state 'stopped', from: 'driving'
|
345
|
-
state 'parked', from: 'stopped'
|
346
|
-
end
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
context 'default' do
|
351
|
-
let(:opts) { {} }
|
352
|
-
|
353
|
-
it 'does not define scope methods' do
|
354
|
-
expect(steady_state_class.defined_scopes.keys).to eq []
|
355
|
-
end
|
356
|
-
|
357
|
-
context 'on an ActiveRecord' do
|
358
|
-
let(:steady_state_class) do
|
359
|
-
stub_const('ActiveRecord::Base', Class.new)
|
360
|
-
|
361
|
-
Class.new(ActiveRecord::Base) do
|
362
|
-
include ActiveModel::Model
|
363
|
-
include SteadyState
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
it 'defines a scope for each state' do
|
368
|
-
expect(steady_state_class.defined_scopes.keys).to eq %i(driving stopped parked)
|
369
|
-
|
370
|
-
expect(query_object).to receive(:where).with(car: 'driving')
|
371
|
-
query_object.instance_exec(&steady_state_class.defined_scopes[:driving])
|
372
|
-
expect(query_object).to receive(:where).with(car: 'stopped')
|
373
|
-
query_object.instance_exec(&steady_state_class.defined_scopes[:stopped])
|
374
|
-
expect(query_object).to receive(:where).with(car: 'parked')
|
375
|
-
query_object.instance_exec(&steady_state_class.defined_scopes[:parked])
|
376
|
-
end
|
377
|
-
end
|
378
|
-
end
|
379
|
-
|
380
|
-
context 'enabled' do
|
381
|
-
let(:opts) { { scopes: true } }
|
382
|
-
|
383
|
-
it 'defines a scope for each state' do
|
384
|
-
expect(steady_state_class.defined_scopes.keys).to eq %i(driving stopped parked)
|
385
|
-
|
386
|
-
expect(query_object).to receive(:where).with(car: 'driving')
|
387
|
-
query_object.instance_exec(&steady_state_class.defined_scopes[:driving])
|
388
|
-
expect(query_object).to receive(:where).with(car: 'stopped')
|
389
|
-
query_object.instance_exec(&steady_state_class.defined_scopes[:stopped])
|
390
|
-
expect(query_object).to receive(:where).with(car: 'parked')
|
391
|
-
query_object.instance_exec(&steady_state_class.defined_scopes[:parked])
|
392
|
-
end
|
393
|
-
end
|
394
|
-
|
395
|
-
context 'disabled' do
|
396
|
-
let(:opts) { { scopes: false } }
|
397
|
-
|
398
|
-
it 'does not define scope methods' do
|
399
|
-
expect(steady_state_class.defined_scopes.keys).to eq []
|
400
|
-
end
|
401
|
-
end
|
402
|
-
end
|
403
|
-
end
|