chrono_model 1.2.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +19 -20
  3. data/README.md +62 -40
  4. data/lib/active_record/connection_adapters/chronomodel_adapter.rb +17 -11
  5. data/lib/active_record/tasks/chronomodel_database_tasks.rb +64 -23
  6. data/lib/chrono_model/adapter/ddl.rb +168 -153
  7. data/lib/chrono_model/adapter/indexes.rb +99 -94
  8. data/lib/chrono_model/adapter/migrations.rb +81 -104
  9. data/lib/chrono_model/adapter/migrations_modules/legacy.rb +41 -0
  10. data/lib/chrono_model/adapter/migrations_modules/stable.rb +41 -0
  11. data/lib/chrono_model/adapter/tsrange.rb +20 -5
  12. data/lib/chrono_model/adapter/upgrade.rb +89 -91
  13. data/lib/chrono_model/adapter.rb +64 -31
  14. data/lib/chrono_model/chrono.rb +17 -0
  15. data/lib/chrono_model/conversions.rb +15 -9
  16. data/lib/chrono_model/db_console.rb +9 -0
  17. data/lib/chrono_model/json.rb +9 -6
  18. data/lib/chrono_model/patches/as_of_time_holder.rb +2 -2
  19. data/lib/chrono_model/patches/as_of_time_relation.rb +2 -2
  20. data/lib/chrono_model/patches/association.rb +15 -12
  21. data/lib/chrono_model/patches/batches.rb +17 -0
  22. data/lib/chrono_model/patches/db_console.rb +20 -4
  23. data/lib/chrono_model/patches/join_node.rb +4 -4
  24. data/lib/chrono_model/patches/preloader.rb +41 -11
  25. data/lib/chrono_model/patches/relation.rb +53 -8
  26. data/lib/chrono_model/patches.rb +3 -1
  27. data/lib/chrono_model/railtie.rb +29 -24
  28. data/lib/chrono_model/time_gate.rb +3 -3
  29. data/lib/chrono_model/time_machine/history_model.rb +65 -31
  30. data/lib/chrono_model/time_machine/time_query.rb +65 -49
  31. data/lib/chrono_model/time_machine/timeline.rb +52 -28
  32. data/lib/chrono_model/time_machine.rb +66 -25
  33. data/lib/chrono_model/utilities.rb +3 -3
  34. data/lib/chrono_model/version.rb +3 -1
  35. data/lib/chrono_model.rb +31 -36
  36. metadata +39 -136
  37. data/.gitignore +0 -21
  38. data/.rspec +0 -2
  39. data/.travis.yml +0 -41
  40. data/Gemfile +0 -4
  41. data/README.sql +0 -161
  42. data/Rakefile +0 -25
  43. data/chrono_model.gemspec +0 -33
  44. data/gemfiles/rails_5.0.gemfile +0 -6
  45. data/gemfiles/rails_5.1.gemfile +0 -6
  46. data/gemfiles/rails_5.2.gemfile +0 -6
  47. data/spec/aruba/dbconsole_spec.rb +0 -25
  48. data/spec/aruba/fixtures/database_with_default_username_and_password.yml +0 -14
  49. data/spec/aruba/fixtures/database_without_username_and_password.yml +0 -11
  50. data/spec/aruba/fixtures/empty_structure.sql +0 -27
  51. data/spec/aruba/fixtures/migrations/56/20160812190335_create_impressions.rb +0 -10
  52. data/spec/aruba/fixtures/migrations/56/20171115195229_add_temporal_extension_to_impressions.rb +0 -10
  53. data/spec/aruba/fixtures/railsapp/config/application.rb +0 -17
  54. data/spec/aruba/fixtures/railsapp/config/boot.rb +0 -5
  55. data/spec/aruba/fixtures/railsapp/config/environments/development.rb +0 -38
  56. data/spec/aruba/migrations_spec.rb +0 -48
  57. data/spec/aruba/rake_task_spec.rb +0 -71
  58. data/spec/chrono_model/adapter/base_spec.rb +0 -157
  59. data/spec/chrono_model/adapter/ddl_spec.rb +0 -243
  60. data/spec/chrono_model/adapter/indexes_spec.rb +0 -72
  61. data/spec/chrono_model/adapter/migrations_spec.rb +0 -312
  62. data/spec/chrono_model/conversions_spec.rb +0 -43
  63. data/spec/chrono_model/history_models_spec.rb +0 -32
  64. data/spec/chrono_model/json_ops_spec.rb +0 -59
  65. data/spec/chrono_model/time_machine/as_of_spec.rb +0 -188
  66. data/spec/chrono_model/time_machine/changes_spec.rb +0 -50
  67. data/spec/chrono_model/time_machine/counter_cache_race_spec.rb +0 -46
  68. data/spec/chrono_model/time_machine/default_scope_spec.rb +0 -37
  69. data/spec/chrono_model/time_machine/history_spec.rb +0 -104
  70. data/spec/chrono_model/time_machine/keep_cool_spec.rb +0 -27
  71. data/spec/chrono_model/time_machine/manipulations_spec.rb +0 -84
  72. data/spec/chrono_model/time_machine/model_identification_spec.rb +0 -46
  73. data/spec/chrono_model/time_machine/sequence_spec.rb +0 -74
  74. data/spec/chrono_model/time_machine/sti_spec.rb +0 -100
  75. data/spec/chrono_model/time_machine/time_query_spec.rb +0 -261
  76. data/spec/chrono_model/time_machine/timeline_spec.rb +0 -63
  77. data/spec/chrono_model/time_machine/timestamps_spec.rb +0 -43
  78. data/spec/chrono_model/time_machine/transactions_spec.rb +0 -69
  79. data/spec/config.travis.yml +0 -5
  80. data/spec/config.yml.example +0 -9
  81. data/spec/spec_helper.rb +0 -33
  82. data/spec/support/adapter/helpers.rb +0 -53
  83. data/spec/support/adapter/structure.rb +0 -44
  84. data/spec/support/aruba.rb +0 -44
  85. data/spec/support/connection.rb +0 -70
  86. data/spec/support/matchers/base.rb +0 -56
  87. data/spec/support/matchers/column.rb +0 -99
  88. data/spec/support/matchers/function.rb +0 -79
  89. data/spec/support/matchers/index.rb +0 -69
  90. data/spec/support/matchers/schema.rb +0 -39
  91. data/spec/support/matchers/table.rb +0 -275
  92. data/spec/support/time_machine/helpers.rb +0 -47
  93. data/spec/support/time_machine/structure.rb +0 -111
  94. data/sql/json_ops.sql +0 -56
  95. data/sql/uninstall-json_ops.sql +0 -24
@@ -1,312 +0,0 @@
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
@@ -1,43 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ChronoModel::Conversions do
4
-
5
- describe 'string_to_utc_time' do
6
- subject { described_class.string_to_utc_time(string) }
7
-
8
- context 'given a valid UTC time string' do
9
- let(:string) { '2017-02-06 09:46:31.129626' }
10
-
11
- it { is_expected.to be_a(Time) }
12
- it { expect(subject.year).to eq 2017 }
13
- it { expect(subject.month).to eq 2 }
14
- it { expect(subject.day).to eq 6 }
15
- it { expect(subject.hour).to eq 9 }
16
- it { expect(subject.min).to eq 46 }
17
- it { expect(subject.sec).to eq 31 }
18
- it { expect(subject.usec).to eq 129626 } # Ref Issue #32
19
- end
20
-
21
- context 'given a valid UTC string without least significant zeros' do
22
- let(:string) { '2017-02-06 09:46:31.129' }
23
-
24
- it { is_expected.to be_a(Time) }
25
- it { expect(subject.usec).to eq 129000 } # Ref Issue #32
26
- end
27
-
28
- context 'given an invalid UTC time string' do
29
- let(:string) { 'foobar' }
30
-
31
- it { is_expected.to be(nil) }
32
- end
33
- end
34
-
35
- describe 'time_to_utc_string' do
36
- subject { described_class.time_to_utc_string(time) }
37
-
38
- let(:time) { Time.utc(1981, 4, 11, 2, 42, 10, 123456) }
39
-
40
- it { is_expected.to eq '1981-04-11 02:42:10.123456' }
41
- end
42
-
43
- end
@@ -1,32 +0,0 @@
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
@@ -1,59 +0,0 @@
1
- ##########################################################
2
- ### DEPRECATED: JSON operators are an hack and there is no
3
- ### reason not to use jsonb other than migrating your data
4
- ##########################################################
5
- if ENV['HAVE_PLPYTHON'] == '1'
6
-
7
- require 'spec_helper'
8
- require 'support/helpers'
9
-
10
- require 'chrono_model/json'
11
-
12
- describe 'JSON equality operator' do
13
- include ChronoTest::Helpers::Adapter
14
-
15
- table 'json_test'
16
-
17
- before :all do
18
- ChronoModel::Json.create
19
- end
20
-
21
- after :all do
22
- ChronoModel::Json.drop
23
- end
24
-
25
- it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":1}'::json ])).to eq true }
26
- it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a" : 1}'::json ])).to eq true }
27
- it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":2}'::json ])).to eq false }
28
- it { expect(adapter.select_value(%[ SELECT '{"a":1,"b":2}'::json = '{"b":2,"a":1}'::json ])).to eq true }
29
- it { expect(adapter.select_value(%[ SELECT '{"a":1,"b":2,"x":{"c":4,"d":5}}'::json = '{"b":2, "x": { "d": 5, "c": 4}, "a":1}'::json ])).to eq true }
30
-
31
- context 'on a temporal table' do
32
- before :all do
33
- adapter.create_table table, :temporal => true do |t|
34
- t.json 'data'
35
- end
36
-
37
- adapter.execute %[
38
- INSERT INTO #{table} ( data ) VALUES ( '{"a":1,"b":2}' )
39
- ]
40
- end
41
-
42
- after :all do
43
- adapter.drop_table table
44
- end
45
-
46
- it { expect {
47
- adapter.execute "UPDATE #{table} SET data = NULL"
48
- }.to_not raise_error }
49
-
50
- it { expect {
51
- adapter.execute %[UPDATE #{table} SET data = '{"x":1,"y":2}']
52
-
53
- }.to_not raise_error }
54
- end
55
-
56
- end
57
-
58
-
59
- end
@@ -1,188 +0,0 @@
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
@@ -1,50 +0,0 @@
1
- require 'spec_helper'
2
- require 'support/time_machine/structure'
3
-
4
- describe ChronoModel::TimeMachine do
5
- include ChronoTest::TimeMachine::Helpers
6
-
7
- describe '#last_changes' do
8
- context 'on plain records' do
9
- context 'having history' do
10
- subject { $t.bar.last_changes }
11
- it { is_expected.to eq('name' => ['bar bar', 'new bar']) }
12
- end
13
-
14
- context 'without history' do
15
- let(:record) { Bar.create!(:name => 'foreveralone') }
16
- subject { record.last_changes }
17
- it { is_expected.to be_nil }
18
- after { record.destroy.history.delete_all } # UGLY
19
- end
20
- end
21
-
22
- context 'on history records' do
23
- context 'at the beginning of the timeline' do
24
- subject { $t.bar.history.first.last_changes }
25
- it { is_expected.to be_nil }
26
- end
27
-
28
- context 'in the middle of the timeline' do
29
- subject { $t.bar.history.second.last_changes }
30
- it { is_expected.to eq('name' => ['bar', 'foo bar']) }
31
- end
32
- end
33
- end
34
-
35
- describe '#changes_against' do
36
- context 'can compare records against history' do
37
- it { expect($t.bar.changes_against($t.bar.history.first)).to eq('name' => ['bar', 'new bar']) }
38
- it { expect($t.bar.changes_against($t.bar.history.second)).to eq('name' => ['foo bar', 'new bar']) }
39
- it { expect($t.bar.changes_against($t.bar.history.third)).to eq('name' => ['bar bar', 'new bar']) }
40
- it { expect($t.bar.changes_against($t.bar.history.last)).to eq({}) }
41
- end
42
-
43
- context 'can compare history against history' do
44
- it { expect($t.bar.history.first. changes_against($t.bar.history.third)).to eq('name' => ['bar bar', 'bar']) }
45
- it { expect($t.bar.history.second.changes_against($t.bar.history.third)).to eq('name' => ['bar bar', 'foo bar']) }
46
- it { expect($t.bar.history.third. changes_against($t.bar.history.third)).to eq({}) }
47
- end
48
- end
49
-
50
- end