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
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'support/adapter/structure'
3
+
4
+ describe ChronoModel::Adapter do
5
+ include ChronoTest::Adapter::Helpers
6
+ include ChronoTest::Adapter::Structure
7
+
8
+ before :all do
9
+ adapter.create_table :meetings do |t|
10
+ t.string :name
11
+ t.tsrange :interval
12
+ end
13
+ end
14
+
15
+ after :all do
16
+ adapter.drop_table :meetings
17
+ end
18
+
19
+ describe '.add_temporal_indexes' do
20
+ before do
21
+ adapter.add_temporal_indexes :meetings, :interval
22
+ end
23
+
24
+ it { expect(adapter.indexes(:meetings).map(&:name)).to eq [
25
+ 'index_meetings_temporal_on_interval',
26
+ 'index_meetings_temporal_on_lower_interval',
27
+ 'index_meetings_temporal_on_upper_interval'
28
+ ] }
29
+
30
+ after do
31
+ adapter.remove_temporal_indexes :meetings, :interval
32
+ end
33
+ end
34
+
35
+ describe '.remove_temporal_indexes' do
36
+ before :all do
37
+ adapter.add_temporal_indexes :meetings, :interval
38
+ end
39
+
40
+ before do
41
+ adapter.remove_temporal_indexes :meetings, :interval
42
+ end
43
+
44
+ it { expect(adapter.indexes(:meetings)).to be_empty }
45
+ end
46
+
47
+ describe '.add_timeline_consistency_constraint' do
48
+ before do
49
+ adapter.add_timeline_consistency_constraint(:meetings, :interval)
50
+ end
51
+
52
+ it { expect(adapter.indexes(:meetings).map(&:name)).to eq [
53
+ 'meetings_timeline_consistency'
54
+ ] }
55
+
56
+ after do
57
+ adapter.remove_timeline_consistency_constraint(:meetings)
58
+ end
59
+ end
60
+
61
+ describe '.remove_timeline_consistency_constraint' do
62
+ before :all do
63
+ adapter.add_timeline_consistency_constraint :meetings, :interval
64
+ end
65
+
66
+ before do
67
+ adapter.remove_timeline_consistency_constraint(:meetings)
68
+ end
69
+
70
+ it { expect(adapter.indexes(:meetings)).to be_empty }
71
+ end
72
+ end
@@ -0,0 +1,312 @@
1
+ require 'spec_helper'
2
+ require 'support/adapter/structure'
3
+
4
+ shared_examples_for 'temporal table' do
5
+ it { expect(adapter.is_chrono?(subject)).to be(true) }
6
+
7
+ it { is_expected.to_not have_public_backing }
8
+
9
+ it { is_expected.to have_temporal_backing }
10
+ it { is_expected.to have_history_backing }
11
+ it { is_expected.to have_history_extra_columns }
12
+ it { is_expected.to have_history_functions }
13
+ it { is_expected.to have_public_interface }
14
+
15
+ it { is_expected.to have_columns(columns) }
16
+ it { is_expected.to have_temporal_columns(columns) }
17
+ it { is_expected.to have_history_columns(columns) }
18
+ end
19
+
20
+ shared_examples_for 'plain table' do
21
+ it { expect(adapter.is_chrono?(subject)).to be(false) }
22
+
23
+ it { is_expected.to have_public_backing }
24
+
25
+ it { is_expected.to_not have_temporal_backing }
26
+ it { is_expected.to_not have_history_backing }
27
+ it { is_expected.to_not have_history_functions }
28
+ it { is_expected.to_not have_public_interface }
29
+
30
+ it { is_expected.to have_columns(columns) }
31
+ end
32
+
33
+ describe ChronoModel::Adapter do
34
+ include ChronoTest::Adapter::Helpers
35
+ include ChronoTest::Adapter::Structure
36
+
37
+ describe '.create_table' do
38
+ with_temporal_table do
39
+ it_should_behave_like 'temporal table'
40
+ end
41
+
42
+ with_plain_table do
43
+ it_should_behave_like 'plain table'
44
+ end
45
+ end
46
+
47
+ describe '.rename_table' do
48
+ renamed = 'foo_table'
49
+ subject { renamed }
50
+
51
+ context 'temporal: true' do
52
+ before :all do
53
+ adapter.create_table table, :temporal => true, &columns
54
+ adapter.add_index table, :test
55
+ adapter.add_index table, [:foo, :bar]
56
+
57
+ adapter.rename_table table, renamed
58
+ end
59
+ after(:all) { adapter.drop_table(renamed) }
60
+
61
+ it_should_behave_like 'temporal table'
62
+
63
+ it 'renames indexes' do
64
+ new_index_names = adapter.indexes(renamed).map(&:name)
65
+ expected_index_names = [[:test], [:foo, :bar]].map do |idx_cols|
66
+ "index_#{renamed}_on_#{idx_cols.join('_and_')}"
67
+ end
68
+ expect(new_index_names.to_set).to eq expected_index_names.to_set
69
+ end
70
+ end
71
+
72
+ context 'temporal: false' do
73
+ before :all do
74
+ adapter.create_table table, :temporal => false, &columns
75
+
76
+ adapter.rename_table table, renamed
77
+ end
78
+ after(:all) { adapter.drop_table(renamed) }
79
+
80
+ it_should_behave_like 'plain table'
81
+ end
82
+ end
83
+
84
+ describe '.change_table' do
85
+ with_temporal_table do
86
+ before :all do
87
+ adapter.change_table table, :temporal => false
88
+ end
89
+
90
+ it_should_behave_like 'plain table'
91
+ end
92
+
93
+ with_plain_table do
94
+ before :all do
95
+ adapter.add_index table, :foo
96
+ adapter.add_index table, :bar, :unique => true
97
+
98
+ adapter.change_table table, :temporal => true
99
+ end
100
+
101
+ it_should_behave_like 'temporal table'
102
+
103
+ let(:history_indexes) do
104
+ adapter.on_schema(ChronoModel::Adapter::HISTORY_SCHEMA) do
105
+ adapter.indexes(table)
106
+ end
107
+ end
108
+
109
+ it "copies plain index to history" do
110
+ expect(history_indexes.find {|i| i.columns == ['foo']}).to be_present
111
+ end
112
+
113
+ it "copies unique index to history without uniqueness constraint" do
114
+ expect(history_indexes.find {|i| i.columns == ['bar'] && i.unique == false}).to be_present
115
+ end
116
+ end
117
+ end
118
+
119
+ describe '.drop_table' do
120
+ before :all do
121
+ adapter.create_table table, :temporal => true, &columns
122
+
123
+ adapter.drop_table table
124
+ end
125
+
126
+ it { is_expected.to_not have_public_backing }
127
+ it { is_expected.to_not have_temporal_backing }
128
+ it { is_expected.to_not have_history_backing }
129
+ it { is_expected.to_not have_history_functions }
130
+ it { is_expected.to_not have_public_interface }
131
+ end
132
+
133
+ describe '.add_index' do
134
+ with_temporal_table do
135
+ before :all do
136
+ adapter.add_index table, [:foo, :bar], :name => 'foobar_index'
137
+ adapter.add_index table, [:test], :name => 'test_index'
138
+ end
139
+
140
+ it { is_expected.to have_temporal_index 'foobar_index', %w( foo bar ) }
141
+ it { is_expected.to have_history_index 'foobar_index', %w( foo bar ) }
142
+ it { is_expected.to have_temporal_index 'test_index', %w( test ) }
143
+ it { is_expected.to have_history_index 'test_index', %w( test ) }
144
+
145
+ it { is_expected.to_not have_index 'foobar_index', %w( foo bar ) }
146
+ it { is_expected.to_not have_index 'test_index', %w( test ) }
147
+ end
148
+
149
+ with_plain_table do
150
+ before :all do
151
+ adapter.add_index table, [:foo, :bar], :name => 'foobar_index'
152
+ adapter.add_index table, [:test], :name => 'test_index'
153
+ end
154
+
155
+ it { is_expected.to_not have_temporal_index 'foobar_index', %w( foo bar ) }
156
+ it { is_expected.to_not have_history_index 'foobar_index', %w( foo bar ) }
157
+ it { is_expected.to_not have_temporal_index 'test_index', %w( test ) }
158
+ it { is_expected.to_not have_history_index 'test_index', %w( test ) }
159
+
160
+ it { is_expected.to have_index 'foobar_index', %w( foo bar ) }
161
+ it { is_expected.to have_index 'test_index', %w( test ) }
162
+ end
163
+ end
164
+
165
+ describe '.remove_index' do
166
+ with_temporal_table do
167
+ before :all do
168
+ adapter.add_index table, [:foo, :bar], :name => 'foobar_index'
169
+ adapter.add_index table, [:test], :name => 'test_index'
170
+
171
+ adapter.remove_index table, :name => 'test_index'
172
+ end
173
+
174
+ it { is_expected.to_not have_temporal_index 'test_index', %w( test ) }
175
+ it { is_expected.to_not have_history_index 'test_index', %w( test ) }
176
+ it { is_expected.to_not have_index 'test_index', %w( test ) }
177
+ end
178
+
179
+ with_plain_table do
180
+ before :all do
181
+ adapter.add_index table, [:foo, :bar], :name => 'foobar_index'
182
+ adapter.add_index table, [:test], :name => 'test_index'
183
+
184
+ adapter.remove_index table, :name => 'test_index'
185
+ end
186
+
187
+ it { is_expected.to_not have_temporal_index 'test_index', %w( test ) }
188
+ it { is_expected.to_not have_history_index 'test_index', %w( test ) }
189
+ it { is_expected.to_not have_index 'test_index', %w( test ) }
190
+ end
191
+ end
192
+
193
+ describe '.add_column' do
194
+ let(:extra_columns) { [['foobarbaz', 'integer']] }
195
+
196
+ with_temporal_table do
197
+ before :all do
198
+ adapter.add_column table, :foobarbaz, :integer
199
+ end
200
+
201
+ it { is_expected.to have_columns(extra_columns) }
202
+ it { is_expected.to have_temporal_columns(extra_columns) }
203
+ it { is_expected.to have_history_columns(extra_columns) }
204
+ end
205
+
206
+ with_plain_table do
207
+ before :all do
208
+ adapter.add_column table, :foobarbaz, :integer
209
+ end
210
+
211
+ it { is_expected.to have_columns(extra_columns) }
212
+ end
213
+ end
214
+
215
+ describe '.remove_column' do
216
+ let(:resulting_columns) { columns.reject {|c,_| c == 'foo'} }
217
+
218
+ with_temporal_table do
219
+ before :all do
220
+ adapter.remove_column table, :foo
221
+ end
222
+
223
+ it { is_expected.to have_columns(resulting_columns) }
224
+ it { is_expected.to have_temporal_columns(resulting_columns) }
225
+ it { is_expected.to have_history_columns(resulting_columns) }
226
+
227
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
228
+ it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
229
+ it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
230
+ end
231
+
232
+ with_plain_table do
233
+ before :all do
234
+ adapter.remove_column table, :foo
235
+ end
236
+
237
+ it { is_expected.to have_columns(resulting_columns) }
238
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
239
+ end
240
+ end
241
+
242
+ describe '.rename_column' do
243
+ with_temporal_table do
244
+ before :all do
245
+ adapter.rename_column table, :foo, :taratapiatapioca
246
+ end
247
+
248
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
249
+ it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
250
+ it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
251
+
252
+ it { is_expected.to have_columns([['taratapiatapioca', 'integer']]) }
253
+ it { is_expected.to have_temporal_columns([['taratapiatapioca', 'integer']]) }
254
+ it { is_expected.to have_history_columns([['taratapiatapioca', 'integer']]) }
255
+ end
256
+
257
+ with_plain_table do
258
+ before :all do
259
+ adapter.rename_column table, :foo, :taratapiatapioca
260
+ end
261
+
262
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
263
+ it { is_expected.to have_columns([['taratapiatapioca', 'integer']]) }
264
+ end
265
+ end
266
+
267
+ describe '.change_column' do
268
+ with_temporal_table do
269
+ before :all do
270
+ adapter.change_column table, :foo, :float
271
+ end
272
+
273
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
274
+ it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
275
+ it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
276
+
277
+ it { is_expected.to have_columns([['foo', 'double precision']]) }
278
+ it { is_expected.to have_temporal_columns([['foo', 'double precision']]) }
279
+ it { is_expected.to have_history_columns([['foo', 'double precision']]) }
280
+ end
281
+
282
+ with_plain_table do
283
+ before(:all) do
284
+ adapter.change_column table, :foo, :float
285
+ end
286
+
287
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
288
+ it { is_expected.to have_columns([['foo', 'double precision']]) }
289
+ end
290
+ end
291
+
292
+ describe '.remove_column' do
293
+ with_temporal_table do
294
+ before :all do
295
+ adapter.remove_column table, :foo
296
+ end
297
+
298
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
299
+ it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
300
+ it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
301
+ end
302
+
303
+ with_plain_table do
304
+ before :all do
305
+ adapter.remove_column table, :foo
306
+ end
307
+
308
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
309
+ end
310
+ end
311
+
312
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'support/time_machine/structure'
3
+
4
+ describe ChronoModel do
5
+ describe '.history_models' do
6
+ subject { ChronoModel.history_models }
7
+
8
+ it 'tracks recorded history models' do
9
+ expected = {}
10
+
11
+ # support/time_machine/structure
12
+ expected['foos'] = Foo::History if defined?(Foo::History)
13
+ expected['bars'] = Bar::History if defined?(Bar::History)
14
+ expected['sub_bars'] = SubBar::History if defined?(SubBar::History)
15
+
16
+ # default_scope_spec
17
+ expected['defoos'] = Defoo::History if defined?(Defoo::History)
18
+
19
+ # counter_cache_race_spec
20
+ expected['sections'] = Section::History if defined?(Section::History)
21
+ expected['articles'] = Article::History if defined?(Article::History)
22
+
23
+ # sti_spec
24
+ expected['animals'] = Animal::History if defined?(Animal::History)
25
+ expected['elements'] = Element::History if defined?(Element::History)
26
+
27
+ is_expected.to eq(expected)
28
+ end
29
+
30
+ it { expect(subject.size).to be > 0 }
31
+ end
32
+ end
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+ require 'support/time_machine/structure'
3
+
4
+ describe ChronoModel::TimeMachine do
5
+ include ChronoTest::TimeMachine::Helpers
6
+
7
+ describe '.as_of' do
8
+ it { expect(Foo.as_of(1.month.ago)).to eq [] }
9
+
10
+ it { expect(Foo.as_of($t.foos[0].ts[0])).to eq [$t.foo, $t.foos[0]] }
11
+ it { expect(Foo.as_of($t.foos[1].ts[0])).to eq [$t.foo, $t.foos[0], $t.foos[1]] }
12
+ it { expect(Foo.as_of(Time.now )).to eq [$t.foo, $t.foos[0], $t.foos[1]] }
13
+
14
+ it { expect(Bar.as_of($t.foos[1].ts[0])).to eq [$t.bar] }
15
+
16
+ it { expect(Bar.as_of($t.bars[0].ts[0])).to eq [$t.bar, $t.bars[0]] }
17
+ it { expect(Bar.as_of($t.bars[1].ts[0])).to eq [$t.bar, $t.bars[0], $t.bars[1]] }
18
+ it { expect(Bar.as_of(Time.now )).to eq [$t.bar, $t.bars[0], $t.bars[1]] }
19
+
20
+ it { expect(Foo.as_of($t.foos[0].ts[0]).first).to be_a(Foo) }
21
+ it { expect(Bar.as_of($t.foos[0].ts[0]).first).to be_a(Bar) }
22
+
23
+ # Associations
24
+ context do
25
+ subject { $t.foos[0].id }
26
+
27
+ it { expect(Foo.as_of($t.foos[0].ts[0]).find(subject).bars).to eq [] }
28
+ it { expect(Foo.as_of($t.foos[1].ts[0]).find(subject).bars).to eq [] }
29
+ it { expect(Foo.as_of($t.bars[0].ts[0]).find(subject).bars).to eq [$t.bars[0]] }
30
+ it { expect(Foo.as_of($t.bars[1].ts[0]).find(subject).bars).to eq [$t.bars[0]] }
31
+ it { expect(Foo.as_of(Time.now ).find(subject).bars).to eq [$t.bars[0]] }
32
+
33
+ it { expect(Foo.as_of($t.bars[0].ts[0]).find(subject).bars.first).to be_a(Bar) }
34
+ end
35
+
36
+ context do
37
+ subject { $t.foos[1].id }
38
+
39
+ it { expect { Foo.as_of($t.foos[0].ts[0]).find(subject) }.to raise_error(ActiveRecord::RecordNotFound) }
40
+ it { expect { Foo.as_of($t.foos[1].ts[0]).find(subject) }.to_not raise_error }
41
+
42
+ it { expect(Foo.as_of($t.bars[0].ts[0]).find(subject).bars).to eq [] }
43
+ it { expect(Foo.as_of($t.bars[1].ts[0]).find(subject).bars).to eq [$t.bars[1]] }
44
+ it { expect(Foo.as_of(Time.now ).find(subject).bars).to eq [$t.bars[1]] }
45
+ end
46
+ end
47
+
48
+
49
+ describe '#as_of' do
50
+ describe 'accepts a Time instance' do
51
+ it { expect($t.foo.as_of(Time.now).name).to eq 'new foo' }
52
+ it { expect($t.bar.as_of(Time.now).name).to eq 'new bar' }
53
+ end
54
+
55
+
56
+ describe 'ignores time zones' do
57
+ it { expect($t.foo.as_of(Time.now.in_time_zone('America/Havana')).name).to eq 'new foo' }
58
+ it { expect($t.bar.as_of(Time.now.in_time_zone('America/Havana')).name).to eq 'new bar' }
59
+ end
60
+
61
+
62
+ describe 'returns records as they were before' do
63
+ it { expect($t.foo.as_of($t.foo.ts[0]).name).to eq 'foo' }
64
+ it { expect($t.foo.as_of($t.foo.ts[1]).name).to eq 'foo bar' }
65
+ it { expect($t.foo.as_of($t.foo.ts[2]).name).to eq 'new foo' }
66
+
67
+ it { expect($t.bar.as_of($t.bar.ts[0]).name).to eq 'bar' }
68
+ it { expect($t.bar.as_of($t.bar.ts[1]).name).to eq 'foo bar' }
69
+ it { expect($t.bar.as_of($t.bar.ts[2]).name).to eq 'bar bar' }
70
+ it { expect($t.bar.as_of($t.bar.ts[3]).name).to eq 'new bar' }
71
+ end
72
+
73
+
74
+ describe 'takes care of associated records' do
75
+ it { expect($t.foo.as_of($t.foo.ts[0]).bars).to eq [] }
76
+ it { expect($t.foo.as_of($t.foo.ts[1]).bars).to eq [] }
77
+ it { expect($t.foo.as_of($t.foo.ts[2]).bars).to eq [$t.bar] }
78
+
79
+ it { expect($t.foo.as_of($t.foo.ts[2]).bars.first.name).to eq 'foo bar' }
80
+
81
+ it { expect($t.foo.as_of($t.bar.ts[0]).bars).to eq [$t.bar] }
82
+ it { expect($t.foo.as_of($t.bar.ts[1]).bars).to eq [$t.bar] }
83
+ it { expect($t.foo.as_of($t.bar.ts[2]).bars).to eq [$t.bar] }
84
+ it { expect($t.foo.as_of($t.bar.ts[3]).bars).to eq [$t.bar] }
85
+
86
+ it { expect($t.foo.as_of($t.bar.ts[0]).bars.first.name).to eq 'bar' }
87
+ it { expect($t.foo.as_of($t.bar.ts[1]).bars.first.name).to eq 'foo bar' }
88
+ it { expect($t.foo.as_of($t.bar.ts[2]).bars.first.name).to eq 'bar bar' }
89
+ it { expect($t.foo.as_of($t.bar.ts[3]).bars.first.name).to eq 'new bar' }
90
+
91
+
92
+ it { expect($t.bar.as_of($t.bar.ts[0]).foo).to eq $t.foo }
93
+ it { expect($t.bar.as_of($t.bar.ts[1]).foo).to eq $t.foo }
94
+ it { expect($t.bar.as_of($t.bar.ts[2]).foo).to eq $t.foo }
95
+ it { expect($t.bar.as_of($t.bar.ts[3]).foo).to eq $t.foo }
96
+
97
+ it { expect($t.bar.as_of($t.bar.ts[0]).foo.name).to eq 'foo bar' }
98
+ it { expect($t.bar.as_of($t.bar.ts[1]).foo.name).to eq 'foo bar' }
99
+ it { expect($t.bar.as_of($t.bar.ts[2]).foo.name).to eq 'new foo' }
100
+ it { expect($t.bar.as_of($t.bar.ts[3]).foo.name).to eq 'new foo' }
101
+ end
102
+
103
+
104
+ describe 'supports historical queries with includes()' do
105
+ it { expect(Foo.as_of($t.foo.ts[0]).includes(:bars).first.bars).to eq [] }
106
+ it { expect(Foo.as_of($t.foo.ts[1]).includes(:bars).first.bars).to eq [] }
107
+ it { expect(Foo.as_of($t.foo.ts[2]).includes(:bars).first.bars).to eq [$t.bar] }
108
+
109
+ it { expect(Foo.as_of($t.bar.ts[0]).includes(:bars).first.bars.first.name).to eq 'bar' }
110
+ it { expect(Foo.as_of($t.bar.ts[1]).includes(:bars).first.bars.first.name).to eq 'foo bar' }
111
+ it { expect(Foo.as_of($t.bar.ts[2]).includes(:bars).first.bars.first.name).to eq 'bar bar' }
112
+ it { expect(Foo.as_of($t.bar.ts[3]).includes(:bars).first.bars.first.name).to eq 'new bar' }
113
+
114
+
115
+ it { expect(Foo.as_of($t.foo.ts[0]).includes(bars: :sub_bars).first.bars).to eq [] }
116
+ it { expect(Foo.as_of($t.foo.ts[1]).includes(bars: :sub_bars).first.bars).to eq [] }
117
+ it { expect(Foo.as_of($t.foo.ts[2]).includes(bars: :sub_bars).first.bars).to eq [$t.bar] }
118
+
119
+ it { expect(Foo.as_of($t.bar.ts[0]).includes(bars: :sub_bars).first.bars.first.name).to eq 'bar' }
120
+ it { expect(Foo.as_of($t.bar.ts[1]).includes(bars: :sub_bars).first.bars.first.name).to eq 'foo bar' }
121
+ it { expect(Foo.as_of($t.bar.ts[2]).includes(bars: :sub_bars).first.bars.first.name).to eq 'bar bar' }
122
+ it { expect(Foo.as_of($t.bar.ts[3]).includes(bars: :sub_bars).first.bars.first.name).to eq 'new bar' }
123
+
124
+ it { expect(Bar.as_of($t.bar.ts[0]).includes(:foo).first.foo).to eq $t.foo }
125
+ it { expect(Bar.as_of($t.bar.ts[1]).includes(:foo).first.foo).to eq $t.foo }
126
+ it { expect(Bar.as_of($t.bar.ts[2]).includes(:foo).first.foo).to eq $t.foo }
127
+ it { expect(Bar.as_of($t.bar.ts[3]).includes(:foo).first.foo).to eq $t.foo }
128
+
129
+ it { expect(Bar.as_of($t.bar.ts[0]).includes(:foo).first.foo.name).to eq 'foo bar' }
130
+ it { expect(Bar.as_of($t.bar.ts[1]).includes(:foo).first.foo.name).to eq 'foo bar' }
131
+ it { expect(Bar.as_of($t.bar.ts[2]).includes(:foo).first.foo.name).to eq 'new foo' }
132
+ it { expect(Bar.as_of($t.bar.ts[3]).includes(:foo).first.foo.name).to eq 'new foo' }
133
+
134
+
135
+ it { expect(Bar.as_of($t.bar.ts[0]).includes(foo: :sub_bars).first.foo).to eq $t.foo }
136
+ it { expect(Bar.as_of($t.bar.ts[1]).includes(foo: :sub_bars).first.foo).to eq $t.foo }
137
+ it { expect(Bar.as_of($t.bar.ts[2]).includes(foo: :sub_bars).first.foo).to eq $t.foo }
138
+ it { expect(Bar.as_of($t.bar.ts[3]).includes(foo: :sub_bars).first.foo).to eq $t.foo }
139
+
140
+ it { expect(Bar.as_of($t.bar.ts[0]).includes(foo: :sub_bars).first.foo.name).to eq 'foo bar' }
141
+ it { expect(Bar.as_of($t.bar.ts[1]).includes(foo: :sub_bars).first.foo.name).to eq 'foo bar' }
142
+ it { expect(Bar.as_of($t.bar.ts[2]).includes(foo: :sub_bars).first.foo.name).to eq 'new foo' }
143
+ it { expect(Bar.as_of($t.bar.ts[3]).includes(foo: :sub_bars).first.foo.name).to eq 'new foo' }
144
+
145
+ it { expect(Foo.as_of($t.foo.ts[0]).includes(:bars, :sub_bars).first.sub_bars.count).to eq 0 }
146
+ it { expect(Foo.as_of($t.foo.ts[1]).includes(:bars, :sub_bars).first.sub_bars.count).to eq 0 }
147
+ it { expect(Foo.as_of($t.foo.ts[2]).includes(:bars, :sub_bars).first.sub_bars.count).to eq 1 }
148
+
149
+ it { expect(Foo.as_of($t.foo.ts[0]).includes(:bars, :sub_bars).first.sub_bars.first).to be nil }
150
+ it { expect(Foo.as_of($t.foo.ts[1]).includes(:bars, :sub_bars).first.sub_bars.first).to be nil }
151
+
152
+ it { expect(Foo.as_of($t.subbar.ts[0]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'sub-bar' }
153
+ it { expect(Foo.as_of($t.subbar.ts[1]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'bar sub-bar' }
154
+ it { expect(Foo.as_of($t.subbar.ts[2]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'sub-bar sub-bar' }
155
+ it { expect(Foo.as_of($t.subbar.ts[3]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'new sub-bar' }
156
+ end
157
+
158
+
159
+ it 'does not raise RecordNotFound when no history records are found' do
160
+ expect { $t.foo.as_of(1.minute.ago) }.to_not raise_error
161
+
162
+ expect($t.foo.as_of(1.minute.ago)).to be(nil)
163
+ end
164
+
165
+
166
+ it 'raises ActiveRecord::RecordNotFound in the bang variant' do
167
+ expect { $t.foo.as_of!(1.minute.ago) }.to raise_error(ActiveRecord::RecordNotFound)
168
+ end
169
+
170
+
171
+ describe 'proxies from non-temporal models to temporal ones' do
172
+ it { expect($t.baz.as_of($t.bar.ts[0]).name).to eq 'baz' }
173
+ it { expect($t.baz.as_of($t.bar.ts[1]).name).to eq 'baz' }
174
+ it { expect($t.baz.as_of($t.bar.ts[2]).name).to eq 'baz' }
175
+ it { expect($t.baz.as_of($t.bar.ts[3]).name).to eq 'baz' }
176
+
177
+ it { expect($t.baz.as_of($t.bar.ts[0]).bar.name).to eq 'bar' }
178
+ it { expect($t.baz.as_of($t.bar.ts[1]).bar.name).to eq 'foo bar' }
179
+ it { expect($t.baz.as_of($t.bar.ts[2]).bar.name).to eq 'bar bar' }
180
+ it { expect($t.baz.as_of($t.bar.ts[3]).bar.name).to eq 'new bar' }
181
+
182
+ it { expect($t.baz.as_of($t.bar.ts[0]).bar.foo.name).to eq 'foo bar' }
183
+ it { expect($t.baz.as_of($t.bar.ts[1]).bar.foo.name).to eq 'foo bar' }
184
+ it { expect($t.baz.as_of($t.bar.ts[2]).bar.foo.name).to eq 'new foo' }
185
+ it { expect($t.baz.as_of($t.bar.ts[3]).bar.foo.name).to eq 'new foo' }
186
+ end
187
+ end
188
+ end