chrono_model 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -1
  3. data/lib/chrono_model/time_machine.rb +1 -1
  4. data/lib/chrono_model/time_machine/history_model.rb +4 -0
  5. data/lib/chrono_model/version.rb +1 -1
  6. data/spec/chrono_model/adapter/base_spec.rb +157 -0
  7. data/spec/chrono_model/adapter/ddl_spec.rb +243 -0
  8. data/spec/chrono_model/adapter/indexes_spec.rb +72 -0
  9. data/spec/chrono_model/adapter/migrations_spec.rb +312 -0
  10. data/spec/chrono_model/history_models_spec.rb +32 -0
  11. data/spec/chrono_model/time_machine/as_of_spec.rb +188 -0
  12. data/spec/chrono_model/time_machine/changes_spec.rb +50 -0
  13. data/spec/chrono_model/{adapter → time_machine}/counter_cache_race_spec.rb +2 -2
  14. data/spec/chrono_model/time_machine/default_scope_spec.rb +37 -0
  15. data/spec/chrono_model/time_machine/history_spec.rb +104 -0
  16. data/spec/chrono_model/time_machine/keep_cool_spec.rb +27 -0
  17. data/spec/chrono_model/time_machine/manipulations_spec.rb +84 -0
  18. data/spec/chrono_model/time_machine/model_identification_spec.rb +46 -0
  19. data/spec/chrono_model/time_machine/sequence_spec.rb +74 -0
  20. data/spec/chrono_model/time_machine/sti_spec.rb +100 -0
  21. data/spec/chrono_model/{time_query_spec.rb → time_machine/time_query_spec.rb} +22 -5
  22. data/spec/chrono_model/time_machine/timeline_spec.rb +63 -0
  23. data/spec/chrono_model/time_machine/timestamps_spec.rb +43 -0
  24. data/spec/chrono_model/time_machine/transactions_spec.rb +69 -0
  25. data/spec/support/adapter/helpers.rb +53 -0
  26. data/spec/support/adapter/structure.rb +44 -0
  27. data/spec/support/time_machine/helpers.rb +47 -0
  28. data/spec/support/time_machine/structure.rb +111 -0
  29. metadata +48 -14
  30. data/spec/chrono_model/adapter/sti_bug_spec.rb +0 -49
  31. data/spec/chrono_model/adapter_spec.rb +0 -788
  32. data/spec/chrono_model/time_machine_spec.rb +0 -749
  33. data/spec/support/helpers.rb +0 -198
@@ -1,749 +0,0 @@
1
- require 'spec_helper'
2
- require 'support/helpers'
3
-
4
- describe ChronoModel::TimeMachine do
5
- include ChronoTest::Helpers::TimeMachine
6
-
7
- setup_schema!
8
- define_models!
9
-
10
- # Set up two associated records, with intertwined updates
11
- #
12
- foo = ts_eval { Foo.create! :name => 'foo', :fooity => 1 }
13
- ts_eval(foo) { update_attributes! :name => 'foo bar' }
14
-
15
- #
16
- bar = ts_eval { Bar.create! :name => 'bar', :foo => foo }
17
- ts_eval(bar) { update_attributes! :name => 'foo bar' }
18
-
19
- #
20
- subbar = ts_eval { SubBar.create! :name => 'sub-bar', :bar => bar }
21
- ts_eval(subbar) { update_attributes! :name => 'bar sub-bar' }
22
-
23
- ts_eval(foo) { update_attributes! :name => 'new foo' }
24
-
25
- ts_eval(bar) { update_attributes! :name => 'bar bar' }
26
- ts_eval(bar) { update_attributes! :name => 'new bar' }
27
-
28
- ts_eval(subbar) { update_attributes! :name => 'sub-bar sub-bar' }
29
- ts_eval(subbar) { update_attributes! :name => 'new sub-bar' }
30
-
31
- #
32
- foos = Array.new(2) {|i| ts_eval { Foo.create! :name => "foo #{i}" } }
33
- bars = Array.new(2) {|i| ts_eval { Bar.create! :name => "bar #{i}", :foo => foos[i] } }
34
-
35
- #
36
- baz = Baz.create :name => 'baz', :bar => bar
37
-
38
- # Specs start here
39
- #
40
- describe '.chrono?' do
41
- subject { model.chrono? }
42
-
43
- context 'on a temporal model' do
44
- let(:model) { Foo }
45
- it { is_expected.to be(true) }
46
- end
47
-
48
- context 'on a plain model' do
49
- let(:model) { Plain }
50
- it { is_expected.to be(false) }
51
- end
52
- end
53
-
54
- describe '.history?' do
55
- subject { model.history? }
56
-
57
- context 'on a temporal parent model' do
58
- let(:model) { Foo }
59
- it { is_expected.to be(false) }
60
- end
61
-
62
- context 'on a temporal history model' do
63
- let(:model) { Foo::History }
64
- it { is_expected.to be(true) }
65
- end
66
-
67
- context 'on a plain model' do
68
- let(:model) { Plain }
69
- it { expect { subject }.to raise_error(NoMethodError) }
70
- end
71
- end
72
-
73
- describe '.descendants' do
74
- subject { Element.descendants }
75
- it { is_expected.to_not include(Element::History) }
76
- it { is_expected.to include(Publication) }
77
- end
78
-
79
- describe '.descendants_with_history' do
80
- subject { Element.descendants_with_history }
81
- it { is_expected.to include(Element::History) }
82
- it { is_expected.to include(Publication) }
83
- end
84
-
85
- describe '.history_models' do
86
- subject { ChronoModel.history_models }
87
-
88
- it { is_expected.to eq(
89
- 'articles' => Article::History,
90
- 'foos' => Foo::History,
91
- 'defoos' => Defoo::History,
92
- 'bars' => Bar::History,
93
- 'elements' => Element::History,
94
- 'sections' => Section::History,
95
- 'sub_bars' => SubBar::History,
96
- 'animals' => Animal::History,
97
- ) }
98
- end
99
-
100
- describe 'does not interfere with AR standard behaviour' do
101
- all_foos = [ foo ] + foos
102
- all_bars = [ bar ] + bars
103
-
104
- it { expect(Foo.count).to eq all_foos.size }
105
- it { expect(Bar.count).to eq all_bars.size }
106
-
107
- it { expect(Foo.includes(bars: :sub_bars)).to eq all_foos }
108
- it { expect(Foo.includes(:bars).preload(bars: :sub_bars)).to eq all_foos }
109
-
110
- it { expect(Foo.includes(:bars).first.name).to eq 'new foo' }
111
- it { expect(Foo.includes(:bars).as_of(foo.ts[0]).first.name).to eq 'foo' }
112
-
113
- it { expect(Foo.joins(:bars).map(&:bars).flatten).to eq all_bars }
114
- it { expect(Foo.joins(:bars).first.bars.joins(:sub_bars).first.name).to eq 'new bar' }
115
-
116
- it { expect(Foo.joins(bars: :sub_bars).first.bars.joins(:sub_bars).first.sub_bars.first.name).to eq 'new sub-bar' }
117
-
118
- it { expect(Foo.first.bars.includes(:sub_bars)).to eq [ bar ] }
119
-
120
- end
121
-
122
- describe '#as_of' do
123
- describe 'accepts a Time instance' do
124
- it { expect(foo.as_of(Time.now).name).to eq 'new foo' }
125
- it { expect(bar.as_of(Time.now).name).to eq 'new bar' }
126
- end
127
-
128
- describe 'ignores time zones' do
129
- it { expect(foo.as_of(Time.now.in_time_zone('America/Havana')).name).to eq 'new foo' }
130
- it { expect(bar.as_of(Time.now.in_time_zone('America/Havana')).name).to eq 'new bar' }
131
- end
132
-
133
- describe 'returns records as they were before' do
134
- it { expect(foo.as_of(foo.ts[0]).name).to eq 'foo' }
135
- it { expect(foo.as_of(foo.ts[1]).name).to eq 'foo bar' }
136
- it { expect(foo.as_of(foo.ts[2]).name).to eq 'new foo' }
137
-
138
- it { expect(bar.as_of(bar.ts[0]).name).to eq 'bar' }
139
- it { expect(bar.as_of(bar.ts[1]).name).to eq 'foo bar' }
140
- it { expect(bar.as_of(bar.ts[2]).name).to eq 'bar bar' }
141
- it { expect(bar.as_of(bar.ts[3]).name).to eq 'new bar' }
142
- end
143
-
144
- describe 'takes care of associated records' do
145
- it { expect(foo.as_of(foo.ts[0]).bars).to eq [] }
146
- it { expect(foo.as_of(foo.ts[1]).bars).to eq [] }
147
- it { expect(foo.as_of(foo.ts[2]).bars).to eq [bar] }
148
-
149
- it { expect(foo.as_of(foo.ts[2]).bars.first.name).to eq 'foo bar' }
150
-
151
-
152
- it { expect(foo.as_of(bar.ts[0]).bars).to eq [bar] }
153
- it { expect(foo.as_of(bar.ts[1]).bars).to eq [bar] }
154
- it { expect(foo.as_of(bar.ts[2]).bars).to eq [bar] }
155
- it { expect(foo.as_of(bar.ts[3]).bars).to eq [bar] }
156
-
157
- it { expect(foo.as_of(bar.ts[0]).bars.first.name).to eq 'bar' }
158
- it { expect(foo.as_of(bar.ts[1]).bars.first.name).to eq 'foo bar' }
159
- it { expect(foo.as_of(bar.ts[2]).bars.first.name).to eq 'bar bar' }
160
- it { expect(foo.as_of(bar.ts[3]).bars.first.name).to eq 'new bar' }
161
-
162
-
163
- it { expect(bar.as_of(bar.ts[0]).foo).to eq foo }
164
- it { expect(bar.as_of(bar.ts[1]).foo).to eq foo }
165
- it { expect(bar.as_of(bar.ts[2]).foo).to eq foo }
166
- it { expect(bar.as_of(bar.ts[3]).foo).to eq foo }
167
-
168
- it { expect(bar.as_of(bar.ts[0]).foo.name).to eq 'foo bar' }
169
- it { expect(bar.as_of(bar.ts[1]).foo.name).to eq 'foo bar' }
170
- it { expect(bar.as_of(bar.ts[2]).foo.name).to eq 'new foo' }
171
- it { expect(bar.as_of(bar.ts[3]).foo.name).to eq 'new foo' }
172
- end
173
-
174
- describe 'supports historical queries with includes()' do
175
- it { expect(Foo.as_of(foo.ts[0]).includes(:bars).first.bars).to eq [] }
176
- it { expect(Foo.as_of(foo.ts[1]).includes(:bars).first.bars).to eq [] }
177
- it { expect(Foo.as_of(foo.ts[2]).includes(:bars).first.bars).to eq [bar] }
178
-
179
- it { expect(Foo.as_of(bar.ts[0]).includes(:bars).first.bars.first.name).to eq 'bar' }
180
- it { expect(Foo.as_of(bar.ts[1]).includes(:bars).first.bars.first.name).to eq 'foo bar' }
181
- it { expect(Foo.as_of(bar.ts[2]).includes(:bars).first.bars.first.name).to eq 'bar bar' }
182
- it { expect(Foo.as_of(bar.ts[3]).includes(:bars).first.bars.first.name).to eq 'new bar' }
183
-
184
-
185
- it { expect(Foo.as_of(foo.ts[0]).includes(bars: :sub_bars).first.bars).to eq [] }
186
- it { expect(Foo.as_of(foo.ts[1]).includes(bars: :sub_bars).first.bars).to eq [] }
187
- it { expect(Foo.as_of(foo.ts[2]).includes(bars: :sub_bars).first.bars).to eq [bar] }
188
-
189
- it { expect(Foo.as_of(bar.ts[0]).includes(bars: :sub_bars).first.bars.first.name).to eq 'bar' }
190
- it { expect(Foo.as_of(bar.ts[1]).includes(bars: :sub_bars).first.bars.first.name).to eq 'foo bar' }
191
- it { expect(Foo.as_of(bar.ts[2]).includes(bars: :sub_bars).first.bars.first.name).to eq 'bar bar' }
192
- it { expect(Foo.as_of(bar.ts[3]).includes(bars: :sub_bars).first.bars.first.name).to eq 'new bar' }
193
-
194
-
195
- it { expect(Bar.as_of(bar.ts[0]).includes(:foo).first.foo).to eq foo }
196
- it { expect(Bar.as_of(bar.ts[1]).includes(:foo).first.foo).to eq foo }
197
- it { expect(Bar.as_of(bar.ts[2]).includes(:foo).first.foo).to eq foo }
198
- it { expect(Bar.as_of(bar.ts[3]).includes(:foo).first.foo).to eq foo }
199
-
200
- it { expect(Bar.as_of(bar.ts[0]).includes(:foo).first.foo.name).to eq 'foo bar' }
201
- it { expect(Bar.as_of(bar.ts[1]).includes(:foo).first.foo.name).to eq 'foo bar' }
202
- it { expect(Bar.as_of(bar.ts[2]).includes(:foo).first.foo.name).to eq 'new foo' }
203
- it { expect(Bar.as_of(bar.ts[3]).includes(:foo).first.foo.name).to eq 'new foo' }
204
-
205
-
206
- it { expect(Bar.as_of(bar.ts[0]).includes(foo: :sub_bars).first.foo).to eq foo }
207
- it { expect(Bar.as_of(bar.ts[1]).includes(foo: :sub_bars).first.foo).to eq foo }
208
- it { expect(Bar.as_of(bar.ts[2]).includes(foo: :sub_bars).first.foo).to eq foo }
209
- it { expect(Bar.as_of(bar.ts[3]).includes(foo: :sub_bars).first.foo).to eq foo }
210
-
211
- it { expect(Bar.as_of(bar.ts[0]).includes(foo: :sub_bars).first.foo.name).to eq 'foo bar' }
212
- it { expect(Bar.as_of(bar.ts[1]).includes(foo: :sub_bars).first.foo.name).to eq 'foo bar' }
213
- it { expect(Bar.as_of(bar.ts[2]).includes(foo: :sub_bars).first.foo.name).to eq 'new foo' }
214
- it { expect(Bar.as_of(bar.ts[3]).includes(foo: :sub_bars).first.foo.name).to eq 'new foo' }
215
-
216
- it { expect(Foo.as_of(foo.ts[0]).includes(:bars, :sub_bars).first.sub_bars.count).to eq 0 }
217
- it { expect(Foo.as_of(foo.ts[1]).includes(:bars, :sub_bars).first.sub_bars.count).to eq 0 }
218
- it { expect(Foo.as_of(foo.ts[2]).includes(:bars, :sub_bars).first.sub_bars.count).to eq 1 }
219
-
220
- it { expect(Foo.as_of(foo.ts[0]).includes(:bars, :sub_bars).first.sub_bars.first).to be nil }
221
- it { expect(Foo.as_of(foo.ts[1]).includes(:bars, :sub_bars).first.sub_bars.first).to be nil }
222
-
223
- it { expect(Foo.as_of(subbar.ts[0]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'sub-bar' }
224
- it { expect(Foo.as_of(subbar.ts[1]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'bar sub-bar' }
225
- it { expect(Foo.as_of(subbar.ts[2]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'sub-bar sub-bar' }
226
- it { expect(Foo.as_of(subbar.ts[3]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'new sub-bar' }
227
- end
228
-
229
- it 'doesn\'t raise RecordNotFound when no history records are found' do
230
- expect { foo.as_of(1.minute.ago) }.to_not raise_error
231
- expect(foo.as_of(1.minute.ago)).to be(nil)
232
- end
233
-
234
-
235
- it 'raises ActiveRecord::RecordNotFound in the bang variant' do
236
- expect { foo.as_of!(1.minute.ago) }.to raise_error(ActiveRecord::RecordNotFound)
237
- end
238
-
239
- describe 'it honors default_scopes' do
240
- active = ts_eval { Defoo.create! :name => 'active 1', :active => true }
241
- ts_eval(active) { update_attributes! :name => 'active 2' }
242
-
243
- hidden = ts_eval { Defoo.create! :name => 'hidden 1', :active => false }
244
- ts_eval(hidden) { update_attributes! :name => 'hidden 2' }
245
-
246
- it { expect(Defoo.as_of(active.ts[0]).map(&:name)).to eq ['active 1'] }
247
- it { expect(Defoo.as_of(active.ts[1]).map(&:name)).to eq ['active 2'] }
248
- it { expect(Defoo.as_of(hidden.ts[0]).map(&:name)).to eq ['active 2'] }
249
- it { expect(Defoo.as_of(hidden.ts[1]).map(&:name)).to eq ['active 2'] }
250
-
251
- it { expect(Defoo.unscoped.as_of(active.ts[0]).map(&:name)).to eq ['active 1'] }
252
- it { expect(Defoo.unscoped.as_of(active.ts[1]).map(&:name)).to eq ['active 2'] }
253
- it { expect(Defoo.unscoped.as_of(hidden.ts[0]).map(&:name)).to eq ['active 2', 'hidden 1'] }
254
- it { expect(Defoo.unscoped.as_of(hidden.ts[1]).map(&:name)).to eq ['active 2', 'hidden 2'] }
255
- end
256
-
257
- describe 'proxies from non-temporal models to temporal ones' do
258
- it { expect(baz.as_of(bar.ts[0]).name).to eq 'baz' }
259
- it { expect(baz.as_of(bar.ts[1]).name).to eq 'baz' }
260
- it { expect(baz.as_of(bar.ts[2]).name).to eq 'baz' }
261
- it { expect(baz.as_of(bar.ts[3]).name).to eq 'baz' }
262
-
263
- it { expect(baz.as_of(bar.ts[0]).bar.name).to eq 'bar' }
264
- it { expect(baz.as_of(bar.ts[1]).bar.name).to eq 'foo bar' }
265
- it { expect(baz.as_of(bar.ts[2]).bar.name).to eq 'bar bar' }
266
- it { expect(baz.as_of(bar.ts[3]).bar.name).to eq 'new bar' }
267
-
268
- it { expect(baz.as_of(bar.ts[0]).bar.foo.name).to eq 'foo bar' }
269
- it { expect(baz.as_of(bar.ts[1]).bar.foo.name).to eq 'foo bar' }
270
- it { expect(baz.as_of(bar.ts[2]).bar.foo.name).to eq 'new foo' }
271
- it { expect(baz.as_of(bar.ts[3]).bar.foo.name).to eq 'new foo' }
272
- end
273
- end
274
-
275
- describe '#history' do
276
- describe 'returns historical instances' do
277
- it { expect(foo.history.size).to eq(3) }
278
- it { expect(foo.history.map(&:name)).to eq ['foo', 'foo bar', 'new foo'] }
279
-
280
- it { expect(bar.history.size).to eq(4) }
281
- it { expect(bar.history.map(&:name)).to eq ['bar', 'foo bar', 'bar bar', 'new bar'] }
282
- end
283
-
284
- describe 'does not return read only records' do
285
- it { expect(foo.history.all?(&:readonly?)).to be(false) }
286
- it { expect(bar.history.all?(&:readonly?)).to be(false) }
287
- end
288
-
289
- describe 'takes care of associated records' do
290
- subject { foo.history.map {|f| f.bars.first.try(:name)} }
291
- it { is_expected.to eq [nil, 'foo bar', 'new bar'] }
292
- end
293
-
294
- describe 'does not return read only associated records' do
295
- it { expect(foo.history[2].bars.all?(&:readonly?)).to_not be(true) }
296
- it { expect(bar.history.all? {|b| b.foo.readonly?}).to_not be(true) }
297
- end
298
-
299
- describe 'allows a custom select list' do
300
- it { expect(foo.history.select(:id).first.attributes.keys).to eq %w( id ) }
301
- end
302
-
303
- describe 'does not add as_of_time when there are aggregates' do
304
- it { expect(foo.history.select('max(id)').to_sql).to_not match(/as_of_time/) }
305
-
306
- it { expect(foo.history.except(:order).select('max(id) as foo, min(id) as bar').group('id').first.attributes.keys).to eq %w( id foo bar ) }
307
- end
308
-
309
- context 'with STI models' do
310
- pub = ts_eval { Publication.create! :title => 'wrong title' }
311
- ts_eval(pub) { update_attributes! :title => 'correct title' }
312
-
313
- it { expect(pub.history.map(&:title)).to eq ['wrong title', 'correct title'] }
314
- end
315
-
316
- context '.sorted' do
317
- describe 'orders by recorded_at, hid' do
318
- it { expect(foo.history.sorted.to_sql).to match(/order by .+"recorded_at" ASC, .+"hid" ASC/i) }
319
- end
320
- end
321
- end
322
-
323
- describe '#pred' do
324
- context 'on the first history entry' do
325
- subject { foo.history.first.pred }
326
- it { is_expected.to be(nil) }
327
- end
328
-
329
- context 'on the second history entry' do
330
- subject { foo.history.second.pred }
331
- it { is_expected.to eq foo.history.first }
332
- end
333
-
334
- context 'on the last history entry' do
335
- subject { foo.history.last.pred }
336
- it { is_expected.to eq foo.history[foo.history.size - 2] }
337
- end
338
- end
339
-
340
- describe '#succ' do
341
- context 'on the first history entry' do
342
- subject { foo.history.first.succ }
343
- it { is_expected.to eq foo.history.second }
344
- end
345
-
346
- context 'on the second history entry' do
347
- subject { foo.history.second.succ }
348
- it { is_expected.to eq foo.history.third }
349
- end
350
-
351
- context 'on the last history entry' do
352
- subject { foo.history.last.succ }
353
- it { is_expected.to be(nil) }
354
- end
355
- end
356
-
357
- describe '#first' do
358
- subject { foo.history.sample.first }
359
- it { is_expected.to eq foo.history.first }
360
- end
361
-
362
- describe '#last' do
363
- subject { foo.history.sample.last }
364
- it { is_expected.to eq foo.history.last }
365
- end
366
-
367
- describe '#current_version' do
368
- describe 'on plain records' do
369
- subject { foo.current_version }
370
- it { is_expected.to eq foo }
371
- end
372
-
373
- describe 'from #as_of' do
374
- subject { foo.as_of(Time.now) }
375
- it { is_expected.to eq foo }
376
- end
377
-
378
- describe 'on historical records' do
379
- subject { foo.history.sample.current_version }
380
- it { is_expected.to eq foo }
381
- end
382
- end
383
-
384
- describe '#historical?' do
385
- subject { record.historical? }
386
-
387
- describe 'on plain records' do
388
- let(:record) { foo }
389
- it { is_expected.to be(false) }
390
- end
391
-
392
- describe 'on historical records' do
393
- describe 'from #history' do
394
- let(:record) { foo.history.first }
395
- it { is_expected.to be(true) }
396
- end
397
-
398
- describe 'from #as_of' do
399
- let(:record) { foo.as_of(Time.now) }
400
- it { is_expected.to be(true) }
401
- end
402
- end
403
- end
404
-
405
- describe '#destroy' do
406
- describe 'on historical records' do
407
- subject { foo.history.first.destroy }
408
- it { expect { subject }.to raise_error(ActiveRecord::ReadOnlyRecord) }
409
- end
410
-
411
- describe 'on current records' do
412
- rec = nil
413
- before(:all) do
414
- rec = ts_eval { Foo.create!(:name => 'alive foo', :fooity => 42) }
415
- ts_eval(rec) { update_attributes!(:name => 'dying foo') }
416
- end
417
- after(:all) do
418
- rec.history.delete_all
419
- end
420
-
421
- subject { rec.destroy }
422
-
423
- it { expect { subject }.to_not raise_error }
424
- it { expect { rec.reload }.to raise_error(ActiveRecord::RecordNotFound) }
425
-
426
- describe 'does not delete its history' do
427
- subject { record.name }
428
-
429
- context do
430
- let(:record) { rec.as_of(rec.ts.first) }
431
- it { is_expected.to eq 'alive foo' }
432
- end
433
-
434
- context do
435
- let(:record) { rec.as_of(rec.ts.last) }
436
- it { is_expected.to eq 'dying foo' }
437
- end
438
-
439
- context do
440
- let(:record) { Foo.as_of(rec.ts.first).where(:fooity => 42).first }
441
- it { is_expected.to eq 'alive foo' }
442
- end
443
-
444
- context do
445
- subject { Foo.history.where(:fooity => 42).map(&:name) }
446
- it { is_expected.to eq ['alive foo', 'dying foo'] }
447
- end
448
- end
449
- end
450
- end
451
-
452
- describe '#timeline' do
453
- split = lambda {|ts| ts.map!{|t| [t.to_i, t.usec]} }
454
-
455
- timestamps_from = lambda {|*records|
456
- records.map(&:history).flatten!.inject([]) {|ret, rec|
457
- ret.push [rec.valid_from.to_i, rec.valid_from.usec] if rec.try(:valid_from)
458
- ret.push [rec.valid_to .to_i, rec.valid_to .usec] if rec.try(:valid_to)
459
- ret
460
- }.sort.uniq
461
- }
462
-
463
- describe 'on records having an :has_many relationship' do
464
- describe 'by default returns timestamps of the record only' do
465
- subject { split.call(foo.timeline) }
466
- it { expect(subject.size).to eq foo.ts.size }
467
- it { is_expected.to eq timestamps_from.call(foo) }
468
- end
469
-
470
- describe 'when asked, returns timestamps including the related objects' do
471
- subject { split.call(foo.timeline(:with => :bars)) }
472
- it { expect(subject.size).to eq(foo.ts.size + bar.ts.size) }
473
- it { is_expected.to eq(timestamps_from.call(foo, *foo.bars)) }
474
- end
475
- end
476
-
477
- describe 'on records using has_timeline :with' do
478
- subject { split.call(bar.timeline) }
479
-
480
- describe 'returns timestamps of the record and its associations' do
481
-
482
- let!(:expected) do
483
- creat = bar.history.first.valid_from
484
- c_sec, c_usec = creat.to_i, creat.usec
485
-
486
- timestamps_from.call(foo, bar).reject {|sec, usec|
487
- sec < c_sec || ( sec == c_sec && usec < c_usec )
488
- }
489
- end
490
-
491
- it { expect(subject.size).to eq expected.size }
492
- it { is_expected.to eq expected }
493
- end
494
- end
495
-
496
- describe 'on non-temporal records using has_timeline :with' do
497
- subject { split.call(baz.timeline) }
498
-
499
- describe 'returns timestamps of its temporal associations' do
500
- it { expect(subject.size).to eq bar.ts.size }
501
- it { is_expected.to eq timestamps_from.call(bar) }
502
- end
503
- end
504
- end
505
-
506
- describe '#last_changes' do
507
- context 'on plain records' do
508
- context 'having history' do
509
- subject { bar.last_changes }
510
- it { is_expected.to eq('name' => ['bar bar', 'new bar']) }
511
- end
512
-
513
- context 'without history' do
514
- let(:record) { Bar.create!(:name => 'foreveralone') }
515
- subject { record.last_changes }
516
- it { is_expected.to be_nil }
517
- after { record.destroy.history.delete_all } # UGLY
518
- end
519
- end
520
-
521
- context 'on history records' do
522
- context 'at the beginning of the timeline' do
523
- subject { bar.history.first.last_changes }
524
- it { is_expected.to be_nil }
525
- end
526
-
527
- context 'in the middle of the timeline' do
528
- subject { bar.history.second.last_changes }
529
- it { is_expected.to eq('name' => ['bar', 'foo bar']) }
530
- end
531
- end
532
- end
533
-
534
- describe '#changes_against' do
535
- context 'can compare records against history' do
536
- it { expect(bar.changes_against(bar.history.first)).to eq('name' => ['bar', 'new bar']) }
537
- it { expect(bar.changes_against(bar.history.second)).to eq('name' => ['foo bar', 'new bar']) }
538
- it { expect(bar.changes_against(bar.history.third)).to eq('name' => ['bar bar', 'new bar']) }
539
- it { expect(bar.changes_against(bar.history.last)).to eq({}) }
540
- end
541
-
542
- context 'can compare history against history' do
543
- it { expect(bar.history.first.changes_against(bar.history.third)).to eq('name' => ['bar bar', 'bar']) }
544
- it { expect(bar.history.second.changes_against(bar.history.third)).to eq('name' => ['bar bar', 'foo bar']) }
545
- it { expect(bar.history.third.changes_against(bar.history.third)).to eq({}) }
546
- end
547
- end
548
-
549
- describe '#pred' do
550
- context 'on records having history' do
551
- subject { bar.pred }
552
- it { expect(subject.name).to eq 'bar bar' }
553
- end
554
-
555
- context 'when there is enough history' do
556
- subject { bar.pred.pred.pred.pred }
557
- it { expect(subject.name).to eq 'bar' }
558
- end
559
-
560
- context 'when no history is recorded' do
561
- let(:record) { Bar.create!(:name => 'quuuux') }
562
- subject { record.pred }
563
- it { is_expected.to be(nil) }
564
- after { record.destroy.history.delete_all }
565
- end
566
- end
567
-
568
- describe 'timestamp methods' do
569
- history_methods = %w( valid_from valid_to recorded_at )
570
- current_methods = %w( as_of_time )
571
-
572
- context 'on history records' do
573
- let(:record) { foo.history.first }
574
-
575
- (history_methods + current_methods).each do |attr|
576
- describe ['#', attr].join do
577
- subject { record.public_send(attr) }
578
-
579
- it { is_expected.to be_present }
580
- it { is_expected.to be_a(Time) }
581
- it { is_expected.to be_utc }
582
- end
583
- end
584
- end
585
-
586
- context 'on current records' do
587
- let(:record) { foo }
588
-
589
- history_methods.each do |attr|
590
- describe ['#', attr].join do
591
- subject { record.public_send(attr) }
592
-
593
- it { expect { subject }.to raise_error(NoMethodError) }
594
- end
595
- end
596
-
597
- current_methods.each do |attr|
598
- describe ['#', attr].join do
599
- subject { record.public_send(attr) }
600
-
601
- it { is_expected.to be(nil) }
602
- end
603
- end
604
- end
605
-
606
- end
607
-
608
- # Class methods
609
- context do
610
- describe '.as_of' do
611
- it { expect(Foo.as_of(1.month.ago)).to eq [] }
612
-
613
- it { expect(Foo.as_of(foos[0].ts[0])).to eq [foo, foos[0]] }
614
- it { expect(Foo.as_of(foos[1].ts[0])).to eq [foo, foos[0], foos[1]] }
615
- it { expect(Foo.as_of(Time.now )).to eq [foo, foos[0], foos[1]] }
616
-
617
- it { expect(Bar.as_of(foos[1].ts[0])).to eq [bar] }
618
-
619
- it { expect(Bar.as_of(bars[0].ts[0])).to eq [bar, bars[0]] }
620
- it { expect(Bar.as_of(bars[1].ts[0])).to eq [bar, bars[0], bars[1]] }
621
- it { expect(Bar.as_of(Time.now )).to eq [bar, bars[0], bars[1]] }
622
-
623
- # Associations
624
- context do
625
- subject { foos[0].id }
626
-
627
- it { expect(Foo.as_of(foos[0].ts[0]).find(subject).bars).to eq [] }
628
- it { expect(Foo.as_of(foos[1].ts[0]).find(subject).bars).to eq [] }
629
- it { expect(Foo.as_of(bars[0].ts[0]).find(subject).bars).to eq [bars[0]] }
630
- it { expect(Foo.as_of(bars[1].ts[0]).find(subject).bars).to eq [bars[0]] }
631
- it { expect(Foo.as_of(Time.now ).find(subject).bars).to eq [bars[0]] }
632
- end
633
-
634
- context do
635
- subject { foos[1].id }
636
-
637
- it { expect { Foo.as_of(foos[0].ts[0]).find(subject) }.to raise_error(ActiveRecord::RecordNotFound) }
638
- it { expect { Foo.as_of(foos[1].ts[0]).find(subject) }.to_not raise_error }
639
-
640
- it { expect(Foo.as_of(bars[0].ts[0]).find(subject).bars).to eq [] }
641
- it { expect(Foo.as_of(bars[1].ts[0]).find(subject).bars).to eq [bars[1]] }
642
- it { expect(Foo.as_of(Time.now ).find(subject).bars).to eq [bars[1]] }
643
- end
644
- end
645
-
646
- describe '.history' do
647
- let(:foo_history) {
648
- ['foo', 'foo bar', 'new foo', 'foo 0', 'foo 1']
649
- }
650
-
651
- let(:bar_history) {
652
- ['bar', 'foo bar', 'bar bar', 'new bar', 'bar 0', 'bar 1']
653
- }
654
-
655
- it { expect(Foo.history.all.map(&:name)).to eq foo_history }
656
- it { expect(Bar.history.all.map(&:name)).to eq bar_history }
657
- end
658
-
659
- describe '.time_query' do
660
- it { expect(Foo.history.time_query(:after, :now, inclusive: true ).count).to eq 3 }
661
- it { expect(Foo.history.time_query(:after, :now, inclusive: false).count).to eq 0 }
662
- it { expect(Foo.history.time_query(:before, :now, inclusive: true ).count).to eq 5 }
663
- it { expect(Foo.history.time_query(:before, :now, inclusive: false).count).to eq 2 }
664
-
665
- it { expect(Foo.history.past.size).to eq 2 }
666
- end
667
-
668
- end
669
-
670
- # Transactions
671
- context 'Within transactions' do
672
- context 'multiple updates to an existing record' do
673
- let!(:r1) do
674
- Foo.create!(:name => 'xact test').tap do |record|
675
- Foo.transaction do
676
- record.update_attribute 'name', 'lost into oblivion'
677
- record.update_attribute 'name', 'does work'
678
- end
679
- end
680
- end
681
-
682
- it "generate only a single history record" do
683
- expect(r1.history.size).to eq(2)
684
-
685
- expect(r1.history.first.name).to eq 'xact test'
686
- expect(r1.history.last.name).to eq 'does work'
687
- end
688
- end
689
-
690
- context 'insertion and subsequent update' do
691
- let!(:r2) do
692
- Foo.transaction do
693
- Foo.create!(:name => 'lost into oblivion').tap do |record|
694
- record.update_attribute 'name', 'I am Bar'
695
- record.update_attribute 'name', 'I am Foo'
696
- end
697
- end
698
- end
699
-
700
- it 'generates a single history record' do
701
- expect(r2.history.size).to eq(1)
702
- expect(r2.history.first.name).to eq 'I am Foo'
703
- end
704
- end
705
-
706
- context 'insertion and subsequent deletion' do
707
- let!(:r3) do
708
- Foo.transaction do
709
- Foo.create!(:name => 'it never happened').destroy
710
- end
711
- end
712
-
713
- it 'does not generate any history' do
714
- expect(Foo.history.where(:id => r3.id)).to be_empty
715
- end
716
- end
717
- end
718
-
719
- # This group is below here to not to disturb the flow of the above specs.
720
- #
721
- context 'history modification' do
722
- describe '#save' do
723
- subject { bar.history.first }
724
-
725
- before do
726
- subject.name = 'modified bar history'
727
- subject.save
728
- subject.reload
729
- end
730
-
731
- it { is_expected.to be_a(Bar::History) }
732
- it { expect(subject.name).to eq 'modified bar history' }
733
- end
734
-
735
- describe '#save!' do
736
- subject { bar.history.second }
737
-
738
- before do
739
- subject.name = 'another modified bar history'
740
- subject.save
741
- subject.reload
742
- end
743
-
744
- it { is_expected.to be_a(Bar::History) }
745
- it { expect(subject.name).to eq 'another modified bar history' }
746
- end
747
- end
748
-
749
- end