chrono_model 1.2.0 → 1.2.1

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.
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