steady_state 1.0.0 → 1.2.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 +17 -1
- data/Rakefile +2 -0
- 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 +22 -9
- data/lib/steady_state/version.rb +3 -1
- data/lib/steady_state.rb +2 -0
- metadata +9 -13
- 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: 138a31884498cda0cd7785af5ba7a13c59c71459a2970680652edc20bb9a42e8
|
4
|
+
data.tar.gz: 224574b15d9664ea395a4c38656bfa9d3c3ddb8fe100018a60cf30b2f35a4422
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f7f923baf74fadcfd46973f86a5f67523e8138f65a7bdcdd5e07393468677b96a2e7fafa37c9315099e562d7e467a5d71153152c004bb4c004e1b2224de92ff
|
7
|
+
data.tar.gz: d1669039e0f85a5d5926ac35f344f90cf9039e319162b3a1ce47be3a240969e236d2b07ed6d0e12a573fc175d68c812b0b07e92f52e4b02136aea86622257f8a
|
data/README.md
CHANGED
@@ -210,6 +210,22 @@ steady_state :step, scopes: false do
|
|
210
210
|
end
|
211
211
|
```
|
212
212
|
|
213
|
+
`steady_state` also follows the same `prefix` api as `delegate` in Rails. You may optionally define your scopes to be prefixed to the name of the state machine with `prefix: true`, or you may provide a custom prefix with `prefix: :some_custom_name`. This may be useful when dealing with multiple state machines on one object.
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
steady_state :temperature, scopes: { prefix: true } do
|
217
|
+
state 'cold', default: true
|
218
|
+
end
|
219
|
+
|
220
|
+
steady_state :color_temperature, scopes: { prefix: 'color' } do
|
221
|
+
state 'cold', default: true
|
222
|
+
end
|
223
|
+
|
224
|
+
Material.solid # => query for 'solid' records
|
225
|
+
Material.temperature_cold # => query for records with a cold temperature
|
226
|
+
Material.color_cold # => query for for records with a cold color temperature
|
227
|
+
```
|
228
|
+
|
213
229
|
### Next and Previous States
|
214
230
|
|
215
231
|
The `may_become?` method can be used to see if setting the state to a particular value would be allowed (ignoring all other validations):
|
@@ -277,7 +293,7 @@ class Material
|
|
277
293
|
self.state = 'liquid'
|
278
294
|
valid? # will return `false` if state transition is invalid
|
279
295
|
end
|
280
|
-
|
296
|
+
|
281
297
|
def melt!
|
282
298
|
self.state = 'liquid'
|
283
299
|
validate! # will raise an exception if state transition is invalid
|
data/Rakefile
CHANGED
@@ -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'
|
@@ -17,26 +19,26 @@ module SteadyState
|
|
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
|
@@ -54,8 +56,11 @@ module SteadyState
|
|
54
56
|
|
55
57
|
delegate(*state_machines[attr_name].predicates, to: attr_name, allow_nil: true) if predicates
|
56
58
|
if scopes
|
59
|
+
scopes = {} unless scopes.is_a?(Hash)
|
60
|
+
prefix = SteadyState::Attribute.build_prefix(attr_name, **scopes)
|
61
|
+
|
57
62
|
state_machines[attr_name].states.each do |state|
|
58
|
-
scope state
|
63
|
+
scope :"#{prefix}#{state}", -> { where(attr_name.to_sym => state) }
|
59
64
|
end
|
60
65
|
end
|
61
66
|
|
@@ -63,5 +68,13 @@ module SteadyState
|
|
63
68
|
inclusion: { in: state_machines[attr_name].states }
|
64
69
|
end
|
65
70
|
end
|
71
|
+
|
72
|
+
def self.build_prefix(attr_name, prefix: false)
|
73
|
+
if prefix
|
74
|
+
"#{prefix == true ? attr_name : prefix}_"
|
75
|
+
else
|
76
|
+
""
|
77
|
+
end
|
78
|
+
end
|
66
79
|
end
|
67
80
|
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: 1.
|
4
|
+
version: 1.2.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-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -113,13 +113,11 @@ files:
|
|
113
113
|
- lib/steady_state/attribute/state_machine.rb
|
114
114
|
- lib/steady_state/attribute/transition_validator.rb
|
115
115
|
- lib/steady_state/version.rb
|
116
|
-
|
117
|
-
- spec/steady_state/attribute_spec.rb
|
118
|
-
homepage:
|
116
|
+
homepage:
|
119
117
|
licenses: []
|
120
118
|
metadata:
|
121
119
|
rubygems_mfa_required: 'true'
|
122
|
-
post_install_message:
|
120
|
+
post_install_message:
|
123
121
|
rdoc_options: []
|
124
122
|
require_paths:
|
125
123
|
- lib
|
@@ -127,17 +125,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
127
125
|
requirements:
|
128
126
|
- - ">="
|
129
127
|
- !ruby/object:Gem::Version
|
130
|
-
version:
|
128
|
+
version: '3.0'
|
131
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
130
|
requirements:
|
133
131
|
- - ">="
|
134
132
|
- !ruby/object:Gem::Version
|
135
133
|
version: '0'
|
136
134
|
requirements: []
|
137
|
-
rubygems_version: 3.
|
138
|
-
signing_key:
|
135
|
+
rubygems_version: 3.5.14
|
136
|
+
signing_key:
|
139
137
|
specification_version: 4
|
140
138
|
summary: Minimalist state management via "an enum with guard rails"
|
141
|
-
test_files:
|
142
|
-
- spec/spec_helper.rb
|
143
|
-
- 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(%w(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(%w(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(%w(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(%w(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
|