stator 0.8.0 → 0.9.0.beta
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/.github/workflows/build.yml +10 -5
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/Appraisals +12 -15
- data/Gemfile +7 -6
- data/README.md +4 -20
- data/gemfiles/{activerecord_7.2.gemfile → activerecord_5.1.gemfile} +2 -1
- data/gemfiles/activerecord_5.1.gemfile.lock +74 -0
- data/gemfiles/{activerecord_8.0.gemfile → activerecord_5.2.gemfile} +2 -1
- data/gemfiles/activerecord_5.2.gemfile.lock +74 -0
- data/gemfiles/{activerecord_7.1.gemfile → activerecord_5.gemfile} +2 -1
- data/gemfiles/activerecord_5.gemfile.lock +74 -0
- data/gemfiles/activerecord_6.0.gemfile +2 -2
- data/gemfiles/activerecord_6.0.gemfile.lock +41 -69
- data/gemfiles/activerecord_6.1.gemfile +2 -2
- data/gemfiles/activerecord_6.1.gemfile.lock +41 -68
- data/gemfiles/activerecord_7.0.gemfile +2 -2
- data/gemfiles/activerecord_7.0.gemfile.lock +40 -66
- data/lib/stator/alias.rb +51 -30
- data/lib/stator/integration.rb +42 -49
- data/lib/stator/machine.rb +59 -58
- data/lib/stator/model.rb +78 -73
- data/lib/stator/transition.rb +61 -60
- data/lib/stator/version.rb +3 -5
- data/lib/stator.rb +13 -0
- data/spec/model_spec.rb +203 -318
- data/spec/spec_helper.rb +6 -2
- data/spec/support/models.rb +26 -45
- data/spec/support/schema.rb +42 -42
- data/stator.gemspec +3 -10
- metadata +19 -75
- data/.github/CODEOWNERS +0 -1
- data/.github/dependabot.yml +0 -24
- data/gemfiles/activerecord_7.1.gemfile.lock +0 -114
- data/gemfiles/activerecord_7.2.gemfile.lock +0 -113
- data/gemfiles/activerecord_8.0.gemfile.lock +0 -116
data/spec/model_spec.rb
CHANGED
@@ -1,239 +1,201 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'spec_helper'
|
4
4
|
|
5
5
|
describe Stator::Model do
|
6
6
|
|
7
|
-
|
8
|
-
u = User.new
|
9
|
-
u.state.should eql("pending")
|
10
|
-
end
|
11
|
-
|
12
|
-
it "should see the initial setting of the state as a change with the initial state as the previous value" do
|
13
|
-
u = User.new
|
14
|
-
u.state = "activated"
|
15
|
-
u.state_in_database.should eql("pending")
|
16
|
-
end
|
17
|
-
|
18
|
-
it "should not obstruct normal validations" do
|
19
|
-
u = User.new
|
20
|
-
u.should_not be_valid
|
21
|
-
u.errors[:email].grep(/length/).should_not be_empty
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should ensure a valid state transition when given a bogus state" do
|
25
|
-
u = User.new
|
26
|
-
u.state = "anythingelse"
|
27
|
-
|
28
|
-
u.should_not be_valid
|
29
|
-
u.errors[:state].should eql(["is not a valid state"])
|
30
|
-
end
|
31
|
-
|
32
|
-
it "should allow creation at any state" do
|
33
|
-
u = User.new(email: "doug@example.com")
|
34
|
-
u.state = "hyperactivated"
|
35
|
-
|
36
|
-
u.should be_valid
|
37
|
-
end
|
7
|
+
let(:u) { User.new }
|
38
8
|
|
39
|
-
|
40
|
-
u = User.new(email: "doug@example.com")
|
9
|
+
describe "basic operations" do
|
41
10
|
|
42
|
-
|
43
|
-
|
44
|
-
|
11
|
+
it 'should set the default state after initialization' do
|
12
|
+
u.state.to_sym.should eql(:pending)
|
13
|
+
end
|
45
14
|
|
46
|
-
|
15
|
+
it 'should see the initial setting of the state as a change with the initial state as the previous value' do
|
16
|
+
u.state = :activated
|
17
|
+
u.state_was.to_sym.should eql(:pending)
|
18
|
+
end
|
47
19
|
|
48
|
-
|
49
|
-
|
50
|
-
|
20
|
+
it 'should not obstruct normal validations' do
|
21
|
+
u.should_not be_valid
|
22
|
+
u.errors[:email].grep(/length/).should_not be_empty
|
23
|
+
end
|
51
24
|
|
52
|
-
|
25
|
+
it 'should ensure a valid state transition when given a bogus state' do
|
26
|
+
u.state = :anythingelse
|
53
27
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
28
|
+
u.should_not be_valid
|
29
|
+
u.errors[:state].should eql(['is not a valid state'])
|
30
|
+
end
|
58
31
|
|
59
|
-
|
60
|
-
|
32
|
+
it 'should allow creation at any state' do
|
33
|
+
u.email = 'doug@example.com'
|
34
|
+
u.state = :hyperactivated
|
61
35
|
|
62
|
-
|
36
|
+
u.should be_valid
|
37
|
+
end
|
63
38
|
|
64
|
-
|
39
|
+
it 'should ensure a valid state transition when given an illegal state based on the current state' do
|
40
|
+
allow(u).to receive(:new_record?).and_return(false)
|
65
41
|
|
66
|
-
|
67
|
-
u.errors[:state].should_not be_empty
|
68
|
-
end
|
42
|
+
u.state = 'hyperactivated'
|
69
43
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
u.hyperactivate!
|
44
|
+
u.should_not be_valid
|
45
|
+
u.errors[:state].should_not be_empty
|
46
|
+
end
|
74
47
|
|
75
|
-
|
48
|
+
it 'should not allow a transition that is currently in a `to` state' do
|
49
|
+
u.email = 'fred@example.com'
|
50
|
+
u.activate!
|
76
51
|
u.hyperactivate!
|
77
|
-
}.should raise_error(/cannot transition to \"hyperactivated\" from \"hyperactivated\"/)
|
78
|
-
end
|
79
|
-
|
80
|
-
it "should run conditional validations" do
|
81
|
-
u = User.new
|
82
|
-
u.state = "semiactivated"
|
83
|
-
u.should_not be_valid
|
84
52
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
it "should invoke callbacks" do
|
90
|
-
u = User.new(activated: true, email: "doug@example.com", name: "doug")
|
91
|
-
u.activated.should == true
|
53
|
+
lambda {
|
54
|
+
u.hyperactivate!
|
55
|
+
}.should raise_error(/cannot transition to hyperactivated from hyperactivated/)
|
56
|
+
end
|
92
57
|
|
93
|
-
|
58
|
+
it 'should run conditional validations' do
|
59
|
+
u.state = 'semiactivated'
|
60
|
+
u.should_not be_valid
|
94
61
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
u.should be_persisted
|
99
|
-
end
|
100
|
-
|
101
|
-
it "should conditionally invoke after_save callbacks when use_previous is true" do
|
102
|
-
u = User.new
|
103
|
-
u.email = "doug@example.com"
|
62
|
+
u.errors[:state].should be_empty
|
63
|
+
u.errors[:email].should_not be_empty
|
64
|
+
end
|
104
65
|
|
105
|
-
|
66
|
+
it 'should invoke callbacks' do
|
67
|
+
u.assign_attributes(activated: true, email: 'doug@example.com', name: 'doug')
|
68
|
+
u.activated.should == true
|
106
69
|
|
107
|
-
|
108
|
-
u.activation_notification_published.should_not be true
|
70
|
+
u.deactivate
|
109
71
|
|
110
|
-
|
72
|
+
u.activated.should == false
|
73
|
+
u.state.should eql :deactivated
|
74
|
+
u.activated_state_at.should be_nil
|
75
|
+
u.should be_persisted
|
76
|
+
end
|
111
77
|
|
112
|
-
|
113
|
-
|
114
|
-
|
78
|
+
it 'should blow up if the record is invalid and a bang method is used' do
|
79
|
+
u.assign_attributes(email: 'doug@other.com', name: 'doug')
|
80
|
+
-> { u.activate! }.should raise_error(ActiveRecord::RecordInvalid)
|
81
|
+
end
|
115
82
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
u.activate!
|
120
|
-
}.should raise_error(ActiveRecord::RecordInvalid)
|
121
|
-
end
|
83
|
+
it 'should allow for other fields to be used other than state' do
|
84
|
+
a = Animal.new
|
85
|
+
a.should be_valid
|
122
86
|
|
123
|
-
|
124
|
-
|
125
|
-
a.should be_valid
|
87
|
+
a.birth!
|
88
|
+
end
|
126
89
|
|
127
|
-
|
128
|
-
|
90
|
+
it 'should create implicit transitions for state declarations' do
|
91
|
+
a = Animal.new
|
92
|
+
a.should_not be_grown_up
|
93
|
+
a.status = 'grown_up'
|
94
|
+
a.save
|
95
|
+
end
|
129
96
|
|
130
|
-
|
131
|
-
|
132
|
-
a.should_not be_grown_up
|
133
|
-
a.status = "grown_up"
|
134
|
-
a.save
|
135
|
-
end
|
97
|
+
it 'should allow multiple machines in the same model' do
|
98
|
+
f = Farm.new
|
136
99
|
|
137
|
-
|
138
|
-
|
139
|
-
f.should be_dirty
|
140
|
-
f.should be_house_dirty
|
100
|
+
f.should be_dirty
|
101
|
+
f.should be_house_dirty
|
141
102
|
|
142
|
-
|
103
|
+
f.cleanup
|
143
104
|
|
144
|
-
|
145
|
-
|
105
|
+
f.should_not be_dirty
|
106
|
+
f.should be_house_dirty
|
146
107
|
|
147
|
-
|
108
|
+
f.house_cleanup # the house namespace
|
148
109
|
|
149
|
-
|
150
|
-
|
110
|
+
f.should_not be_dirty
|
111
|
+
f.should_not be_house_dirty
|
112
|
+
end
|
151
113
|
|
152
|
-
|
153
|
-
|
154
|
-
|
114
|
+
it 'should allow saving to be skipped' do
|
115
|
+
f = Farm.new
|
116
|
+
f.cleanup(false)
|
155
117
|
|
156
|
-
|
157
|
-
|
118
|
+
f.should_not be_persisted
|
119
|
+
end
|
158
120
|
|
159
|
-
|
160
|
-
|
161
|
-
|
121
|
+
it 'should allow no initial state' do
|
122
|
+
f = Factory.new
|
123
|
+
f.state.should be_nil
|
162
124
|
|
163
|
-
|
125
|
+
f.construct.should eql(true)
|
164
126
|
|
165
|
-
|
166
|
-
|
127
|
+
f.state.should eql(:constructed)
|
128
|
+
end
|
167
129
|
|
168
|
-
|
169
|
-
|
170
|
-
u.email = "doug@example.com"
|
130
|
+
it 'should allow any transition if validations are opted out of' do
|
131
|
+
u.email = 'doug@example.com'
|
171
132
|
|
172
|
-
|
173
|
-
|
133
|
+
u.can_hyperactivate?.should eql(false)
|
134
|
+
u.hyperactivate.should eql(false)
|
174
135
|
|
175
|
-
|
136
|
+
u.current_state.should eql :pending
|
176
137
|
|
177
|
-
|
178
|
-
|
179
|
-
|
138
|
+
u.without_state_transition_validations do
|
139
|
+
u.can_hyperactivate?.should eql(true)
|
140
|
+
u.hyperactivate.should eql(true)
|
141
|
+
end
|
180
142
|
end
|
181
|
-
end
|
182
143
|
|
183
|
-
|
184
|
-
|
185
|
-
u.email = "doug@example.com"
|
144
|
+
it 'should skip tracking timestamps if opted out of' do
|
145
|
+
u.email = 'doug@example.com'
|
186
146
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
147
|
+
u.without_state_transition_tracking do
|
148
|
+
u.semiactivate!
|
149
|
+
u.state.should eql :semiactivated
|
150
|
+
u.semiactivated_state_at.should be_nil
|
151
|
+
end
|
152
|
+
|
153
|
+
# Make sure that tracking is ensured back to
|
154
|
+
# original value
|
155
|
+
u.activate!
|
156
|
+
u.activated_state_at.should_not be_nil
|
191
157
|
end
|
192
158
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
end
|
159
|
+
it 'should skip tracking timestamps if opted out of with thread safety' do
|
160
|
+
threads = []
|
161
|
+
skip = User.new(email: 'skip@example.com', state: :pending)
|
162
|
+
nope = User.new(email: 'nope@example.com', state: :pending)
|
198
163
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
164
|
+
threads << Thread.new do
|
165
|
+
sleep 0.5
|
166
|
+
nope.semiactivate!
|
167
|
+
end
|
203
168
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
skip.without_state_transition_tracking do
|
210
|
-
sleep 1
|
211
|
-
skip.semiactivate!
|
169
|
+
threads << Thread.new do
|
170
|
+
skip.without_state_transition_tracking do
|
171
|
+
sleep 1
|
172
|
+
skip.semiactivate!
|
173
|
+
end
|
212
174
|
end
|
213
|
-
end
|
214
175
|
|
215
|
-
|
176
|
+
threads.each(&:join)
|
216
177
|
|
217
|
-
|
218
|
-
|
219
|
-
|
178
|
+
nope.semiactivated_state_at.should_not be_nil
|
179
|
+
skip.semiactivated_state_at.should be_nil
|
180
|
+
end
|
220
181
|
|
221
|
-
|
222
|
-
|
223
|
-
|
182
|
+
it 'should not inherit _integration cache on dup' do
|
183
|
+
u.email = 'user@example.com'
|
184
|
+
u.save!
|
224
185
|
|
225
|
-
|
186
|
+
u_duped = u.dup
|
226
187
|
|
227
|
-
|
188
|
+
u.semiactivate!
|
228
189
|
|
229
|
-
|
190
|
+
u_duped_integration = u_duped.send(:_stator_integration)
|
230
191
|
|
231
|
-
|
232
|
-
|
192
|
+
u_duped_integration.state.should_not eql(u.state)
|
193
|
+
u_duped_integration.instance_values['record'].should eq(u_duped)
|
194
|
+
end
|
233
195
|
end
|
234
196
|
|
235
|
-
describe
|
236
|
-
it
|
197
|
+
describe 'helper methods' do
|
198
|
+
it 'should answer the question of whether the state is currently the one invoked' do
|
237
199
|
a = Animal.new
|
238
200
|
a.should be_unborn
|
239
201
|
a.should_not be_born
|
@@ -244,7 +206,7 @@ describe Stator::Model do
|
|
244
206
|
a.should_not be_unborn
|
245
207
|
end
|
246
208
|
|
247
|
-
it
|
209
|
+
it 'should determine if it can validly execute a transition' do
|
248
210
|
a = Animal.new
|
249
211
|
a.can_birth?.should eql(true)
|
250
212
|
|
@@ -254,87 +216,18 @@ describe Stator::Model do
|
|
254
216
|
end
|
255
217
|
end
|
256
218
|
|
257
|
-
|
258
|
-
is_active_record_6_or_higher = Gem::Requirement.new(">= 6.0").satisfied_by?(ActiveRecord.version)
|
259
|
-
|
260
|
-
u = User.create!(email: 'doug@example.com')
|
261
|
-
u.state.should eql('pending')
|
262
|
-
|
263
|
-
lambda {
|
264
|
-
ActiveRecord::Base.transaction do
|
265
|
-
# The state change will be applied to the model object in memory.
|
266
|
-
# An UPDATE query will be sent to the db, but it will be rolled back
|
267
|
-
# when the error is raised below.
|
268
|
-
u.activate!
|
269
|
-
raise "Some error"
|
270
|
-
end
|
271
|
-
}.should raise_error("Some error")
|
272
|
-
|
273
|
-
u.state.should eql("activated")
|
274
|
-
|
275
|
-
# Rails 6.0 fixed a bug where a model's dirty state would be incorrect
|
276
|
-
# in a scenario like this one, where a model is updated within a transaction,
|
277
|
-
# and the transaction is then rolled back:
|
278
|
-
#
|
279
|
-
# https://github.com/rails/rails/pull/35987
|
280
|
-
#
|
281
|
-
# We show this dirty behavior change in Rails 6.0 below to clarify why stator itself
|
282
|
-
# behaves differently starting in Rails 6.0.
|
283
|
-
if is_active_record_6_or_higher
|
284
|
-
# On Rails 6.0 or higher, attribute_in_database is "pending" — which is correct,
|
285
|
-
# because the db transaction was rolled back.
|
286
|
-
u.attribute_in_database("state").should eql("pending")
|
287
|
-
|
288
|
-
# Attempting a state change to "hyperactivated" fails, which is correct,
|
289
|
-
# because the previous state change to "activated" did not succeed.
|
290
|
-
lambda {
|
291
|
-
u.hyperactivate!
|
292
|
-
}.should raise_error(ActiveRecord::RecordInvalid, 'Validation failed: State cannot transition to "hyperactivated" from "pending"')
|
293
|
-
else
|
294
|
-
# On Rails < 6.0, attribute_in_database is "activated" — which is incorrect,
|
295
|
-
# because the db transaction was rolled back.
|
296
|
-
u.attribute_in_database("state").should eql("activated")
|
297
|
-
|
298
|
-
# On Rails < 6.0, stator incorrectly allows the state change to "hyperactivated"
|
299
|
-
# because it incorrectly thinks the state has been successfully updated to "activated".
|
300
|
-
lambda {
|
301
|
-
u.hyperactivate!
|
302
|
-
}.should_not raise_error
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
it "should not support multiple state changes made between saves" do
|
307
|
-
u = User.create!(email: "doug@example.com")
|
308
|
-
u.state.should eql("pending")
|
309
|
-
|
310
|
-
u.activate(false) # change state, but do not save to db
|
311
|
-
u.state.should eql("activated")
|
312
|
-
|
313
|
-
lambda {
|
314
|
-
# Fails because the db state is still "pending", and
|
315
|
-
# the db state is what stator uses for the "previous" state
|
316
|
-
# to check that the state transition is valid.
|
317
|
-
u.hyperactivate!
|
318
|
-
}.should raise_error(ActiveRecord::RecordInvalid, 'Validation failed: State cannot transition to "hyperactivated" from "pending"')
|
319
|
-
|
320
|
-
# The model is updated in-memory with the new state value,
|
321
|
-
# but remains invalid.
|
322
|
-
u.state.should eql("hyperactivated")
|
323
|
-
u.valid?.should be false
|
324
|
-
end
|
325
|
-
|
326
|
-
describe "tracker methods" do
|
219
|
+
describe 'tracker methods' do
|
327
220
|
before do
|
328
|
-
Time.zone =
|
221
|
+
Time.zone = 'Eastern Time (US & Canada)'
|
329
222
|
end
|
330
223
|
|
331
|
-
it
|
224
|
+
it 'should store the initial state timestamp when the record is created' do
|
332
225
|
a = Animal.new
|
333
226
|
a.save
|
334
227
|
a.unborn_status_at.should be_within(1).of(Time.zone.now)
|
335
228
|
end
|
336
229
|
|
337
|
-
it
|
230
|
+
it 'should store when a record changed state for the first time' do
|
338
231
|
a = Animal.new
|
339
232
|
a.unborn_status_at.should be_nil
|
340
233
|
a.born_status_at.should be_nil
|
@@ -343,7 +236,7 @@ describe Stator::Model do
|
|
343
236
|
a.born_status_at.should be_within(1).of(Time.zone.now)
|
344
237
|
end
|
345
238
|
|
346
|
-
it
|
239
|
+
it 'should store when a record change states' do
|
347
240
|
a = Animal.new
|
348
241
|
a.status_changed_at.should be_nil
|
349
242
|
|
@@ -353,15 +246,14 @@ describe Stator::Model do
|
|
353
246
|
|
354
247
|
previous_status_changed_at = a.status_changed_at
|
355
248
|
|
356
|
-
a.name =
|
249
|
+
a.name = 'new name'
|
357
250
|
a.save
|
358
251
|
|
359
252
|
a.status_changed_at.should eql(previous_status_changed_at)
|
360
253
|
end
|
361
254
|
|
362
|
-
it
|
363
|
-
u =
|
364
|
-
u.email = "doug@example.com"
|
255
|
+
it 'should prepend the setting of the timestamp so other callbacks can use it' do
|
256
|
+
u.email = 'doug@example.com'
|
365
257
|
|
366
258
|
u.tagged_at.should be_nil
|
367
259
|
u.semiactivate!
|
@@ -370,35 +262,34 @@ describe Stator::Model do
|
|
370
262
|
u.tagged_at.should_not be_nil
|
371
263
|
end
|
372
264
|
|
373
|
-
it
|
374
|
-
t = Time.at(Time.now.to_i - 3600)
|
265
|
+
it 'should respect the timestamp if explicitly provided' do
|
266
|
+
t = Time.zone.at(Time.now.to_i - 3600)
|
375
267
|
|
376
|
-
u =
|
377
|
-
u.
|
378
|
-
u.state = "semiactivated"
|
268
|
+
u.email = 'doug@example.com'
|
269
|
+
u.state = 'semiactivated'
|
379
270
|
u.semiactivated_state_at = t
|
380
271
|
u.save!
|
381
272
|
|
382
|
-
u.state.should eql(
|
273
|
+
u.state.should eql('semiactivated')
|
383
274
|
u.semiactivated_state_at.should eql(t)
|
384
275
|
end
|
385
276
|
|
386
|
-
it
|
387
|
-
t = Time.at(Time.now.to_i - 3600)
|
277
|
+
it 'should respect the timestamp if explicitly provided via create' do
|
278
|
+
t = Time.zone.at(Time.now.to_i - 3600)
|
388
279
|
|
389
280
|
u = User.create!(
|
390
|
-
email:
|
391
|
-
state:
|
281
|
+
email: 'doug@example.com',
|
282
|
+
state: 'semiactivated',
|
392
283
|
semiactivated_state_at: t
|
393
284
|
)
|
394
285
|
|
395
|
-
u.state.should eql(
|
286
|
+
u.state.should eql('semiactivated')
|
396
287
|
u.semiactivated_state_at.should eql(t)
|
397
288
|
end
|
398
289
|
|
399
|
-
it
|
400
|
-
z = ZooKeeper.new(name:
|
401
|
-
z.employment_state.should eql(
|
290
|
+
it 'should allow opting into track by namespace' do
|
291
|
+
z = ZooKeeper.new(name: 'Doug')
|
292
|
+
z.employment_state.should eql('hired')
|
402
293
|
z.employment_fire!
|
403
294
|
z.fired_employment_state_at.should_not be_nil
|
404
295
|
|
@@ -411,52 +302,53 @@ describe Stator::Model do
|
|
411
302
|
z.ended_working_state_at.should be_nil
|
412
303
|
end
|
413
304
|
|
414
|
-
describe
|
415
|
-
it
|
416
|
-
t = Time.now
|
417
|
-
u = User.create!(
|
418
|
-
u.state_by?(:activated, Time.at(t.to_i + 1)).should be true
|
419
|
-
u.activated_state_by?(Time.at(t.to_i + 1)).should be true
|
305
|
+
describe '#state_by?' do
|
306
|
+
it 'should be true when the transition is earlier' do
|
307
|
+
t = Time.zone.now
|
308
|
+
u = User.create!(email: 'doug@example.com', activated_state_at: t)
|
309
|
+
u.state_by?(:activated, Time.zone.at(t.to_i + 1)).should be true
|
310
|
+
u.activated_state_by?(Time.zone.at(t.to_i + 1)).should be true
|
420
311
|
end
|
421
312
|
|
422
|
-
it
|
423
|
-
t = Time.now
|
424
|
-
u = User.create!(
|
313
|
+
it 'should be true when the transition is at the same time' do
|
314
|
+
t = Time.zone.now
|
315
|
+
u = User.create!(email: 'doug@example.com', activated_state_at: t)
|
425
316
|
u.state_by?(:activated, t).should be true
|
426
317
|
u.activated_state_by?(t).should be true
|
427
318
|
end
|
428
319
|
|
429
|
-
it
|
430
|
-
t = Time.now
|
431
|
-
u = User.create!(
|
432
|
-
u.state_by?(:activated, Time.at(t.to_i - 1)).should be false
|
433
|
-
u.activated_state_by?(Time.at(t.to_i - 1)).should be false
|
320
|
+
it 'should be false when the transition is later' do
|
321
|
+
t = Time.zone.now
|
322
|
+
u = User.create!(email: 'doug@example.com', activated_state_at: t)
|
323
|
+
u.state_by?(:activated, Time.zone.at(t.to_i - 1)).should be false
|
324
|
+
u.activated_state_by?(Time.zone.at(t.to_i - 1)).should be false
|
434
325
|
end
|
435
326
|
|
436
|
-
it
|
437
|
-
t = Time.now
|
438
|
-
u = User.create!(
|
327
|
+
it 'should be false when the transition is nil' do
|
328
|
+
t = Time.zone.now
|
329
|
+
u = User.create!(email: 'doug@example.com', activated_state_at: nil)
|
439
330
|
u.state_by?(:activated, t).should be false
|
440
331
|
u.activated_state_by?(t).should be false
|
441
332
|
end
|
442
333
|
|
443
|
-
it
|
444
|
-
u = User.create!(
|
334
|
+
it 'should be true when the transition is not nil and the time is nil' do
|
335
|
+
u = User.create!(email: 'doug@example.com', activated_state_at: Time.zone.now)
|
445
336
|
u.state_by?(:activated, nil).should be true
|
446
337
|
u.activated_state_by?(nil).should be true
|
447
338
|
end
|
448
339
|
|
449
|
-
it
|
450
|
-
u = User.create!(email:
|
340
|
+
it 'should be false when both are nil' do
|
341
|
+
u = User.create!(email: 'doug@example.com', activated_state_at: nil)
|
451
342
|
u.state_by?(:activated, nil).should be false
|
452
343
|
u.activated_state_by?(nil).should be false
|
453
344
|
end
|
454
345
|
end
|
455
346
|
end
|
456
347
|
|
457
|
-
describe
|
458
|
-
it
|
459
|
-
u = User.new(email:
|
348
|
+
describe 'aliasing' do
|
349
|
+
it 'should allow aliasing within the dsl' do
|
350
|
+
u = User.new(email: 'doug@example.com')
|
351
|
+
|
460
352
|
u.should respond_to(:active?)
|
461
353
|
u.should respond_to(:inactive?)
|
462
354
|
|
@@ -473,34 +365,28 @@ describe Stator::Model do
|
|
473
365
|
u.should be_active
|
474
366
|
u.should_not be_inactive
|
475
367
|
|
476
|
-
User::ACTIVE_STATES.should eql(%
|
477
|
-
User::INACTIVE_STATES.should eql(%
|
478
|
-
|
479
|
-
is_active_record_72_or_higher = Gem::Requirement.new(">= 7.2").satisfied_by?(ActiveRecord.version)
|
480
|
-
|
481
|
-
if (is_active_record_72_or_higher)
|
482
|
-
User.active.to_sql.gsub(" ", " ").should eq("SELECT 'users'.* FROM 'users' WHERE 'users'.'state' IN ('activated', 'hyperactivated')")
|
483
|
-
User.inactive.to_sql.gsub(" ", " ").should eq("SELECT 'users'.* FROM 'users' WHERE 'users'.'state' IN ('pending', 'deactivated', 'semiactivated')")
|
484
|
-
else
|
485
|
-
User.active.to_sql.gsub(" ", " ").should eq("SELECT users.* FROM users WHERE users.state IN ('activated', 'hyperactivated')")
|
486
|
-
User.inactive.to_sql.gsub(" ", " ").should eq("SELECT users.* FROM users WHERE users.state IN ('pending', 'deactivated', 'semiactivated')")
|
487
|
-
end
|
368
|
+
User::ACTIVE_STATES.should eql(%i[activated hyperactivated])
|
369
|
+
User::INACTIVE_STATES.should eql(%i[pending deactivated semiactivated])
|
488
370
|
|
371
|
+
User.active.to_sql.gsub(' ',
|
372
|
+
' ').should eq("SELECT users.* FROM users WHERE users.state IN ('activated', 'hyperactivated')")
|
373
|
+
User.inactive.to_sql.gsub(' ',
|
374
|
+
' ').should eq("SELECT users.* FROM users WHERE users.state IN ('pending', 'deactivated', 'semiactivated')")
|
489
375
|
end
|
490
376
|
|
491
|
-
it
|
377
|
+
it 'should evaluate inverses correctly' do
|
492
378
|
f = Farm.new
|
493
|
-
f.house_state =
|
379
|
+
f.house_state = 'dirty'
|
494
380
|
f.should_not be_house_cleaned
|
495
381
|
|
496
|
-
f.house_state =
|
382
|
+
f.house_state = 'disgusting'
|
497
383
|
f.should_not be_house_cleaned
|
498
384
|
|
499
|
-
f.house_state =
|
385
|
+
f.house_state = 'clean'
|
500
386
|
f.should be_house_cleaned
|
501
387
|
end
|
502
388
|
|
503
|
-
it
|
389
|
+
it 'should namespace aliases just like everything else' do
|
504
390
|
f = Farm.new
|
505
391
|
f.should respond_to(:house_cleaned?)
|
506
392
|
|
@@ -510,24 +396,23 @@ describe Stator::Model do
|
|
510
396
|
f.should be_house_cleaned
|
511
397
|
end
|
512
398
|
|
513
|
-
it
|
399
|
+
it 'should allow for explicit constant and scope names to be provided' do
|
514
400
|
User.should respond_to(:luke_warmers)
|
515
|
-
(
|
401
|
+
(!defined?(User::LUKE_WARMERS).nil?).should eql(true)
|
516
402
|
u = User.new
|
517
403
|
u.should respond_to(:luke_warm?)
|
518
404
|
end
|
519
405
|
|
520
|
-
it
|
406
|
+
it 'should not create constants or scopes by default' do
|
521
407
|
u = User.new
|
522
408
|
u.should respond_to(:iced_tea?)
|
523
|
-
(
|
409
|
+
(!defined?(User::ICED_TEA_STATES).nil?).should eql(false)
|
524
410
|
User.should_not respond_to(:iced_tea)
|
525
411
|
end
|
526
412
|
|
527
|
-
it
|
528
|
-
states = User._stator(
|
529
|
-
states.should eql(%
|
413
|
+
it 'should determine the full list of states correctly' do
|
414
|
+
states = User._stator('').states
|
415
|
+
states.should eql(%i[pending activated deactivated semiactivated hyperactivated])
|
530
416
|
end
|
531
417
|
end
|
532
|
-
|
533
418
|
end
|