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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c80b6cce9dbf7702cdf8fd0d97c21794bcbc6a67c2ea688fcf5b150ee217df5a
4
- data.tar.gz: 67bc8bc9e117e55ac21b4dcba713dbf97627354c1997e906fbb317ef06b42ac6
3
+ metadata.gz: 02e5f7eb48f3ada429e0d38598535c77489ab57172d920c2dbb6083a19dc5466
4
+ data.tar.gz: bbc957eeed78c83b1a7e2c49395df741ba34eea99d3d4adaabdbbabe1298bb06
5
5
  SHA512:
6
- metadata.gz: 374352c07075b3ccb50c1c018e6b8e654ebcc84675d44e4024406dde923ba28699f58daf7b67f8161cff8ee41d9e4f75c77f59680f165b62e527787b7dd00e65
7
- data.tar.gz: 6d493623807a7332a1e3b96a9849d46b6a33d59e8405dbb935ef66de4d9b054b2c471ca8421feb7f75810a2aa6694b0173a64be694a69a31a6210de2232018b0
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
- task default: %i(rubocop spec)
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SteadyState
2
4
  module Attribute
3
5
  class State < SimpleDelegator
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SteadyState
2
4
  module Attribute
3
5
  class StateMachine
@@ -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/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/LineLength
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SteadyState
2
- VERSION = '0.1.0'.freeze
4
+ VERSION = '1.1.0'
3
5
  end
data/lib/steady_state.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support'
2
4
  require 'active_support/core_ext'
3
5
  require 'active_model'
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: 0.1.0
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: 2022-02-10 00:00:00.000000000 Z
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: '4.0'
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: '4.0'
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: '4.0'
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: '4.0'
40
+ version: '5.2'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rake
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: rspec
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: rubocop-betterment
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
- - spec/spec_helper.rb
103
- - spec/steady_state/attribute_spec.rb
104
- homepage:
116
+ homepage:
105
117
  licenses: []
106
- metadata: {}
107
- post_install_message:
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.3.7
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