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.
- checksums.yaml +4 -4
- data/Rakefile +2 -1
- data/lib/chrono_model/time_machine.rb +1 -1
- data/lib/chrono_model/time_machine/history_model.rb +4 -0
- data/lib/chrono_model/version.rb +1 -1
- data/spec/chrono_model/adapter/base_spec.rb +157 -0
- data/spec/chrono_model/adapter/ddl_spec.rb +243 -0
- data/spec/chrono_model/adapter/indexes_spec.rb +72 -0
- data/spec/chrono_model/adapter/migrations_spec.rb +312 -0
- data/spec/chrono_model/history_models_spec.rb +32 -0
- data/spec/chrono_model/time_machine/as_of_spec.rb +188 -0
- data/spec/chrono_model/time_machine/changes_spec.rb +50 -0
- data/spec/chrono_model/{adapter → time_machine}/counter_cache_race_spec.rb +2 -2
- data/spec/chrono_model/time_machine/default_scope_spec.rb +37 -0
- data/spec/chrono_model/time_machine/history_spec.rb +104 -0
- data/spec/chrono_model/time_machine/keep_cool_spec.rb +27 -0
- data/spec/chrono_model/time_machine/manipulations_spec.rb +84 -0
- data/spec/chrono_model/time_machine/model_identification_spec.rb +46 -0
- data/spec/chrono_model/time_machine/sequence_spec.rb +74 -0
- data/spec/chrono_model/time_machine/sti_spec.rb +100 -0
- data/spec/chrono_model/{time_query_spec.rb → time_machine/time_query_spec.rb} +22 -5
- data/spec/chrono_model/time_machine/timeline_spec.rb +63 -0
- data/spec/chrono_model/time_machine/timestamps_spec.rb +43 -0
- data/spec/chrono_model/time_machine/transactions_spec.rb +69 -0
- data/spec/support/adapter/helpers.rb +53 -0
- data/spec/support/adapter/structure.rb +44 -0
- data/spec/support/time_machine/helpers.rb +47 -0
- data/spec/support/time_machine/structure.rb +111 -0
- metadata +48 -14
- data/spec/chrono_model/adapter/sti_bug_spec.rb +0 -49
- data/spec/chrono_model/adapter_spec.rb +0 -788
- data/spec/chrono_model/time_machine_spec.rb +0 -749
- 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
|