chrono_model 0.8.2 → 0.9.0
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/.travis.yml +11 -10
- data/Gemfile +0 -19
- data/README.md +55 -26
- data/chrono_model.gemspec +11 -3
- data/lib/chrono_model.rb +10 -0
- data/lib/chrono_model/adapter.rb +46 -26
- data/lib/chrono_model/patches.rb +69 -6
- data/lib/chrono_model/schema_format.rake +23 -7
- data/lib/chrono_model/schema_format.rb +2 -1
- data/lib/chrono_model/time_gate.rb +3 -11
- data/lib/chrono_model/time_machine.rb +53 -102
- data/lib/chrono_model/version.rb +1 -1
- data/spec/adapter_spec.rb +115 -111
- data/spec/json_ops_spec.rb +5 -5
- data/spec/spec_helper.rb +3 -2
- data/spec/support/matchers/base.rb +5 -8
- data/spec/support/matchers/column.rb +22 -9
- data/spec/support/matchers/index.rb +8 -4
- data/spec/support/matchers/schema.rb +5 -1
- data/spec/support/matchers/table.rb +55 -24
- data/spec/time_machine_spec.rb +195 -176
- data/spec/time_query_spec.rb +39 -39
- metadata +116 -18
data/spec/json_ops_spec.rb
CHANGED
@@ -14,11 +14,11 @@ describe 'JSON equality operator' do
|
|
14
14
|
ChronoModel::Json.drop
|
15
15
|
end
|
16
16
|
|
17
|
-
it { adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":1}'::json ]).
|
18
|
-
it { adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a" : 1}'::json ]).
|
19
|
-
it { adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":2}'::json ]).
|
20
|
-
it { adapter.select_value(%[ SELECT '{"a":1,"b":2}'::json = '{"b":2,"a":1}'::json ]).
|
21
|
-
it { adapter.select_value(%[ SELECT '{"a":1,"b":2,"x":{"c":4,"d":5}}'::json = '{"b":2, "x": { "d": 5, "c": 4}, "a":1}'::json ]).
|
17
|
+
it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":1}'::json ])).to eq 't' }
|
18
|
+
it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a" : 1}'::json ])).to eq 't' }
|
19
|
+
it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":2}'::json ])).to eq 'f' }
|
20
|
+
it { expect(adapter.select_value(%[ SELECT '{"a":1,"b":2}'::json = '{"b":2,"a":1}'::json ])).to eq 't' }
|
21
|
+
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 't' }
|
22
22
|
|
23
23
|
context 'on a temporal table' do
|
24
24
|
before :all do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
2
2
|
#
|
3
|
+
require 'codeclimate-test-reporter'
|
4
|
+
CodeClimate::TestReporter.start
|
5
|
+
|
3
6
|
require 'chrono_model'
|
4
7
|
|
5
8
|
require 'support/connection'
|
@@ -9,8 +12,6 @@ require 'support/matchers/column'
|
|
9
12
|
require 'support/matchers/index'
|
10
13
|
|
11
14
|
RSpec.configure do |config|
|
12
|
-
config.treat_symbols_as_metadata_keys_with_true_values = true
|
13
|
-
|
14
15
|
config.include(ChronoTest::Matchers::Schema)
|
15
16
|
config.include(ChronoTest::Matchers::Table)
|
16
17
|
config.include(ChronoTest::Matchers::Column)
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require 'ostruct'
|
2
|
-
|
3
1
|
module ChronoTest::Matchers
|
4
2
|
class Base
|
3
|
+
include ActiveRecord::Sanitization::ClassMethods
|
4
|
+
|
5
5
|
attr_reader :table
|
6
6
|
|
7
7
|
def matches?(table)
|
@@ -15,6 +15,7 @@ module ChronoTest::Matchers
|
|
15
15
|
end
|
16
16
|
|
17
17
|
protected
|
18
|
+
|
18
19
|
def connection
|
19
20
|
ChronoTest.connection
|
20
21
|
end
|
@@ -46,13 +47,9 @@ module ChronoTest::Matchers
|
|
46
47
|
end
|
47
48
|
|
48
49
|
private
|
49
|
-
FooColumn = OpenStruct.new(:name => '')
|
50
|
-
|
51
50
|
def exec_query(sql, binds, name)
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
connection.exec_query(sql, name, binds)
|
51
|
+
sql = sanitize_sql_array([ sql, *Array.wrap(binds) ])
|
52
|
+
connection.exec_query(sql, name)
|
56
53
|
end
|
57
54
|
end
|
58
55
|
|
@@ -21,25 +21,38 @@ module ChronoTest::Matchers
|
|
21
21
|
@matches.values.all?
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
"expected #{@schema}.#{table} to have
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
def failure_message
|
25
|
+
message_matches("expected #{@schema}.#{table} to have")
|
26
|
+
end
|
27
|
+
|
28
|
+
def failure_message_when_negated
|
29
|
+
message_matches("expected #{@schema}.#{table} to not have")
|
30
30
|
end
|
31
31
|
|
32
32
|
protected
|
33
33
|
def has_column?(name, type)
|
34
|
+
column_type(name) == [name, type]
|
35
|
+
end
|
36
|
+
|
37
|
+
def column_type(name)
|
34
38
|
table = "#{@schema}.#{self.table}"
|
35
39
|
|
36
|
-
select_rows(<<-SQL, [table, name], 'Check column').first
|
40
|
+
select_rows(<<-SQL, [table, name], 'Check column').first
|
37
41
|
SELECT attname, FORMAT_TYPE(atttypid, atttypmod)
|
38
42
|
FROM pg_attribute
|
39
|
-
WHERE attrelid =
|
40
|
-
AND attname =
|
43
|
+
WHERE attrelid = ?::regclass::oid
|
44
|
+
AND attname = ?
|
41
45
|
SQL
|
42
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def message_matches(message)
|
50
|
+
(message << ' ').tap do |message|
|
51
|
+
message << @matches.map do |(name, type), match|
|
52
|
+
"a #{name}(#{type}) column" unless match
|
53
|
+
end.compact.to_sentence
|
54
|
+
end
|
55
|
+
end
|
43
56
|
end
|
44
57
|
|
45
58
|
def have_columns(*args)
|
@@ -24,18 +24,22 @@ module ChronoTest::Matchers
|
|
24
24
|
JOIN pg_class i ON i.oid = d.indexrelid
|
25
25
|
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(d.indkey)
|
26
26
|
WHERE i.relkind = 'i'
|
27
|
-
AND t.relname =
|
28
|
-
AND i.relname =
|
27
|
+
AND t.relname = ?
|
28
|
+
AND i.relname = ?
|
29
29
|
AND i.relnamespace = (
|
30
|
-
SELECT oid FROM pg_namespace WHERE nspname =
|
30
|
+
SELECT oid FROM pg_namespace WHERE nspname = ?
|
31
31
|
)
|
32
32
|
ORDER BY a.attname
|
33
33
|
SQL
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
36
|
+
def failure_message
|
37
37
|
"expected #{schema}.#{table} to have a #{name} index on #{columns}"
|
38
38
|
end
|
39
|
+
|
40
|
+
def failure_message_when_negated
|
41
|
+
"expected #{schema}.#{table} to not have a #{name} index on #{columns}"
|
42
|
+
end
|
39
43
|
end
|
40
44
|
|
41
45
|
def have_index(*args)
|
@@ -14,10 +14,14 @@ module ChronoTest::Matchers
|
|
14
14
|
'be in schema'
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
17
|
+
def failure_message
|
18
18
|
"expected to be in schema #@expected, but was in #@current"
|
19
19
|
end
|
20
20
|
|
21
|
+
def failure_message_when_negated
|
22
|
+
"expected to be in schema #@current, but was in #@expected"
|
23
|
+
end
|
24
|
+
|
21
25
|
def matches?(*)
|
22
26
|
@current = select_value(<<-SQL, [], 'Current schema')
|
23
27
|
SHOW search_path
|
@@ -18,8 +18,8 @@ module ChronoTest::Matchers
|
|
18
18
|
FROM pg_class c
|
19
19
|
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
20
20
|
WHERE c.relkind = '#{kind}'
|
21
|
-
AND c.relname =
|
22
|
-
AND n.nspname =
|
21
|
+
AND c.relname = ?
|
22
|
+
AND n.nspname = ?
|
23
23
|
)
|
24
24
|
SQL
|
25
25
|
end
|
@@ -39,9 +39,13 @@ module ChronoTest::Matchers
|
|
39
39
|
'be in the public schema'
|
40
40
|
end
|
41
41
|
|
42
|
-
def
|
42
|
+
def failure_message
|
43
43
|
"expected #{table} to exist in the #{public_schema} schema"
|
44
44
|
end
|
45
|
+
|
46
|
+
def failure_message_when_negated
|
47
|
+
"expected #{table} to not exist in the #{public_schema} schema"
|
48
|
+
end
|
45
49
|
end
|
46
50
|
|
47
51
|
def have_public_backing
|
@@ -63,9 +67,13 @@ module ChronoTest::Matchers
|
|
63
67
|
'be in the temporal schema'
|
64
68
|
end
|
65
69
|
|
66
|
-
def
|
70
|
+
def failure_message
|
67
71
|
"expected #{table} to exist in the #{temporal_schema} schema"
|
68
72
|
end
|
73
|
+
|
74
|
+
def failure_message_when_negated
|
75
|
+
"expected #{table} to not exist in the #{temporal_schema} schema"
|
76
|
+
end
|
69
77
|
end
|
70
78
|
|
71
79
|
def have_temporal_backing
|
@@ -91,7 +99,7 @@ module ChronoTest::Matchers
|
|
91
99
|
'be in history schema'
|
92
100
|
end
|
93
101
|
|
94
|
-
def
|
102
|
+
def failure_message
|
95
103
|
"expected #{table} ".tap do |message|
|
96
104
|
message << [
|
97
105
|
("to exist in the #{history_schema} schema" unless @existance),
|
@@ -102,6 +110,17 @@ module ChronoTest::Matchers
|
|
102
110
|
end
|
103
111
|
end
|
104
112
|
|
113
|
+
def failure_message_when_negated
|
114
|
+
"expected #{table} ".tap do |message|
|
115
|
+
message << [
|
116
|
+
("to not exist in the #{history_schema} schema" if @existance),
|
117
|
+
("to not inherit from #{temporal_schema}.#{table}" if @inheritance),
|
118
|
+
("to not have a timeline consistency constraint" if @constraint),
|
119
|
+
("to not have history indexes" if @indexes)
|
120
|
+
].compact.to_sentence
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
105
124
|
private
|
106
125
|
def table_exists?
|
107
126
|
@existance = relation_exists? :in => history_schema
|
@@ -113,8 +132,8 @@ module ChronoTest::Matchers
|
|
113
132
|
@inheritance = select_value(<<-SQL, binds, 'Check inheritance') == 't'
|
114
133
|
SELECT EXISTS (
|
115
134
|
SELECT 1 FROM pg_catalog.pg_inherits
|
116
|
-
WHERE inhrelid =
|
117
|
-
AND inhparent =
|
135
|
+
WHERE inhrelid = ?::regclass::oid
|
136
|
+
AND inhparent = ?::regclass::oid
|
118
137
|
)
|
119
138
|
SQL
|
120
139
|
end
|
@@ -124,8 +143,8 @@ module ChronoTest::Matchers
|
|
124
143
|
|
125
144
|
indexes = select_values(<<-SQL, binds, 'Check history indexes')
|
126
145
|
SELECT indexdef FROM pg_indexes
|
127
|
-
WHERE schemaname =
|
128
|
-
AND tablename =
|
146
|
+
WHERE schemaname = ?
|
147
|
+
AND tablename = ?
|
129
148
|
SQL
|
130
149
|
|
131
150
|
fqtn = [history_schema, table].join('.')
|
@@ -146,26 +165,26 @@ module ChronoTest::Matchers
|
|
146
165
|
end
|
147
166
|
|
148
167
|
def has_consistency_constraint?
|
149
|
-
binds =
|
150
|
-
connection.timeline_consistency_constraint_name(table),
|
151
|
-
history_schema,
|
152
|
-
[history_schema, table].join('.'),
|
153
|
-
connection.primary_key(table)
|
154
|
-
|
168
|
+
binds = {
|
169
|
+
conname: connection.timeline_consistency_constraint_name(table),
|
170
|
+
connamespace: history_schema,
|
171
|
+
conrelid: [history_schema, table].join('.'),
|
172
|
+
attname: connection.primary_key(table)
|
173
|
+
}
|
155
174
|
|
156
175
|
@constraint = select_value(<<-SQL, binds, 'Check Consistency Constraint') == 't'
|
157
176
|
SELECT EXISTS (
|
158
177
|
SELECT 1 FROM pg_catalog.pg_constraint
|
159
|
-
WHERE conname =
|
178
|
+
WHERE conname = :conname
|
160
179
|
AND contype = 'x'
|
161
|
-
AND conrelid =
|
180
|
+
AND conrelid = :conrelid::regclass
|
162
181
|
AND connamespace = (
|
163
|
-
SELECT oid FROM pg_catalog.pg_namespace WHERE nspname =
|
182
|
+
SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = :connamespace
|
164
183
|
)
|
165
184
|
AND conkey = (
|
166
185
|
SELECT array_agg(attnum) FROM pg_catalog.pg_attribute
|
167
|
-
WHERE attname IN (
|
168
|
-
AND attrelid =
|
186
|
+
WHERE attname IN (:attname, 'validity')
|
187
|
+
AND attrelid = :conrelid::regclass
|
169
188
|
)
|
170
189
|
)
|
171
190
|
SQL
|
@@ -192,7 +211,7 @@ module ChronoTest::Matchers
|
|
192
211
|
'be an updatable view'
|
193
212
|
end
|
194
213
|
|
195
|
-
def
|
214
|
+
def failure_message
|
196
215
|
"expected #{table} ".tap do |message|
|
197
216
|
message << [
|
198
217
|
("to exist in the #{public_schema} schema" unless @existance ),
|
@@ -204,6 +223,18 @@ module ChronoTest::Matchers
|
|
204
223
|
end
|
205
224
|
end
|
206
225
|
|
226
|
+
def failure_message_when_negated
|
227
|
+
"expected #{table} ".tap do |message|
|
228
|
+
message << [
|
229
|
+
("to not exist in the #{public_schema} schema" if @existance ),
|
230
|
+
('to not be an updatable view' if @updatable ),
|
231
|
+
('to not have an INSERT trigger' if @insert_trigger),
|
232
|
+
('to not have an UPDATE trigger' if @update_trigger),
|
233
|
+
('to not have a DELETE trigger' if @delete_trigger)
|
234
|
+
].compact.to_sentence
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
207
238
|
private
|
208
239
|
def view_exists?
|
209
240
|
@existance = relation_exists? :in => public_schema, :kind => :view
|
@@ -214,7 +245,7 @@ module ChronoTest::Matchers
|
|
214
245
|
|
215
246
|
@updatable = select_value(<<-SQL, binds, 'Check updatable') == 'YES'
|
216
247
|
SELECT is_updatable FROM information_schema.views
|
217
|
-
WHERE table_schema =
|
248
|
+
WHERE table_schema = ? AND table_name = ?
|
218
249
|
SQL
|
219
250
|
end
|
220
251
|
|
@@ -224,8 +255,8 @@ module ChronoTest::Matchers
|
|
224
255
|
FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c, pg_catalog.pg_namespace n
|
225
256
|
WHERE t.tgrelid = c.relfilenode
|
226
257
|
AND n.oid = c.relnamespace
|
227
|
-
AND n.nspname =
|
228
|
-
AND c.relname =
|
258
|
+
AND n.nspname = ?
|
259
|
+
AND c.relname = ?;
|
229
260
|
SQL
|
230
261
|
|
231
262
|
@insert_trigger = triggers.include? 'chronomodel_insert'
|
data/spec/time_machine_spec.rb
CHANGED
@@ -25,85 +25,118 @@ describe ChronoModel::TimeMachine do
|
|
25
25
|
baz = Baz.create :name => 'baz', :bar => bar
|
26
26
|
|
27
27
|
describe '.chrono?' do
|
28
|
+
subject { model.chrono? }
|
29
|
+
|
28
30
|
context 'on a temporal model' do
|
29
|
-
|
30
|
-
it {
|
31
|
+
let(:model) { Foo }
|
32
|
+
it { is_expected.to be(true) }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'on a plain model' do
|
36
|
+
let(:model) { Plain }
|
37
|
+
it { is_expected.to be(false) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '.history?' do
|
42
|
+
subject { model.history? }
|
43
|
+
|
44
|
+
context 'on a temporal parent model' do
|
45
|
+
let(:model) { Foo }
|
46
|
+
it { is_expected.to be(false) }
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'on a temporal history model' do
|
50
|
+
let(:model) { Foo::History }
|
51
|
+
it { is_expected.to be(true) }
|
31
52
|
end
|
32
53
|
|
33
54
|
context 'on a plain model' do
|
34
|
-
|
35
|
-
it {
|
55
|
+
let(:model) { Plain }
|
56
|
+
it { expect { subject }.to raise_error(NoMethodError) }
|
36
57
|
end
|
37
58
|
end
|
38
59
|
|
60
|
+
describe '.descendants' do
|
61
|
+
subject { Element.descendants }
|
62
|
+
it { is_expected.to_not include(Element::History) }
|
63
|
+
it { is_expected.to include(Publication) }
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '.descendants_with_history' do
|
67
|
+
subject { Element.descendants_with_history }
|
68
|
+
it { is_expected.to include(Element::History) }
|
69
|
+
it { is_expected.to include(Publication) }
|
70
|
+
end
|
71
|
+
|
39
72
|
# Specs start here
|
40
73
|
#
|
41
74
|
describe '.chrono_models' do
|
42
75
|
subject { ChronoModel::TimeMachine.chrono_models }
|
43
76
|
|
44
|
-
it {
|
77
|
+
it { is_expected.to eq(
|
45
78
|
'foos' => Foo::History,
|
46
79
|
'defoos' => Defoo::History,
|
47
80
|
'bars' => Bar::History,
|
48
81
|
'elements' => Element::History
|
49
|
-
|
82
|
+
) }
|
50
83
|
end
|
51
84
|
|
52
85
|
describe '#as_of' do
|
53
86
|
describe 'accepts a Time instance' do
|
54
|
-
it { foo.as_of(Time.now).name.
|
55
|
-
it { bar.as_of(Time.now).name.
|
87
|
+
it { expect(foo.as_of(Time.now).name).to eq 'new foo' }
|
88
|
+
it { expect(bar.as_of(Time.now).name).to eq 'new bar' }
|
56
89
|
end
|
57
90
|
|
58
91
|
describe 'ignores time zones' do
|
59
|
-
it { foo.as_of(Time.now.in_time_zone('America/Havana')).name.
|
60
|
-
it { bar.as_of(Time.now.in_time_zone('America/Havana')).name.
|
92
|
+
it { expect(foo.as_of(Time.now.in_time_zone('America/Havana')).name).to eq 'new foo' }
|
93
|
+
it { expect(bar.as_of(Time.now.in_time_zone('America/Havana')).name).to eq 'new bar' }
|
61
94
|
end
|
62
95
|
|
63
96
|
describe 'returns records as they were before' do
|
64
|
-
it { foo.as_of(foo.ts[0]).name.
|
65
|
-
it { foo.as_of(foo.ts[1]).name.
|
66
|
-
it { foo.as_of(foo.ts[2]).name.
|
97
|
+
it { expect(foo.as_of(foo.ts[0]).name).to eq 'foo' }
|
98
|
+
it { expect(foo.as_of(foo.ts[1]).name).to eq 'foo bar' }
|
99
|
+
it { expect(foo.as_of(foo.ts[2]).name).to eq 'new foo' }
|
67
100
|
|
68
|
-
it { bar.as_of(bar.ts[0]).name.
|
69
|
-
it { bar.as_of(bar.ts[1]).name.
|
70
|
-
it { bar.as_of(bar.ts[2]).name.
|
71
|
-
it { bar.as_of(bar.ts[3]).name.
|
101
|
+
it { expect(bar.as_of(bar.ts[0]).name).to eq 'bar' }
|
102
|
+
it { expect(bar.as_of(bar.ts[1]).name).to eq 'foo bar' }
|
103
|
+
it { expect(bar.as_of(bar.ts[2]).name).to eq 'bar bar' }
|
104
|
+
it { expect(bar.as_of(bar.ts[3]).name).to eq 'new bar' }
|
72
105
|
end
|
73
106
|
|
74
107
|
describe 'takes care of associated records' do
|
75
|
-
it { foo.as_of(foo.ts[0]).bars.
|
76
|
-
it { foo.as_of(foo.ts[1]).bars.
|
77
|
-
it { foo.as_of(foo.ts[2]).bars.
|
108
|
+
it { expect(foo.as_of(foo.ts[0]).bars).to eq [] }
|
109
|
+
it { expect(foo.as_of(foo.ts[1]).bars).to eq [] }
|
110
|
+
it { expect(foo.as_of(foo.ts[2]).bars).to eq [bar] }
|
78
111
|
|
79
|
-
it { foo.as_of(foo.ts[2]).bars.first.name.
|
112
|
+
it { expect(foo.as_of(foo.ts[2]).bars.first.name).to eq 'foo bar' }
|
80
113
|
|
81
114
|
|
82
|
-
it { foo.as_of(bar.ts[0]).bars.
|
83
|
-
it { foo.as_of(bar.ts[1]).bars.
|
84
|
-
it { foo.as_of(bar.ts[2]).bars.
|
85
|
-
it { foo.as_of(bar.ts[3]).bars.
|
115
|
+
it { expect(foo.as_of(bar.ts[0]).bars).to eq [bar] }
|
116
|
+
it { expect(foo.as_of(bar.ts[1]).bars).to eq [bar] }
|
117
|
+
it { expect(foo.as_of(bar.ts[2]).bars).to eq [bar] }
|
118
|
+
it { expect(foo.as_of(bar.ts[3]).bars).to eq [bar] }
|
86
119
|
|
87
|
-
it { foo.as_of(bar.ts[0]).bars.first.name.
|
88
|
-
it { foo.as_of(bar.ts[1]).bars.first.name.
|
89
|
-
it { foo.as_of(bar.ts[2]).bars.first.name.
|
90
|
-
it { foo.as_of(bar.ts[3]).bars.first.name.
|
120
|
+
it { expect(foo.as_of(bar.ts[0]).bars.first.name).to eq 'bar' }
|
121
|
+
it { expect(foo.as_of(bar.ts[1]).bars.first.name).to eq 'foo bar' }
|
122
|
+
it { expect(foo.as_of(bar.ts[2]).bars.first.name).to eq 'bar bar' }
|
123
|
+
it { expect(foo.as_of(bar.ts[3]).bars.first.name).to eq 'new bar' }
|
91
124
|
|
92
125
|
|
93
|
-
it { bar.as_of(bar.ts[0]).foo.
|
94
|
-
it { bar.as_of(bar.ts[1]).foo.
|
95
|
-
it { bar.as_of(bar.ts[2]).foo.
|
96
|
-
it { bar.as_of(bar.ts[3]).foo.
|
126
|
+
it { expect(bar.as_of(bar.ts[0]).foo).to eq foo }
|
127
|
+
it { expect(bar.as_of(bar.ts[1]).foo).to eq foo }
|
128
|
+
it { expect(bar.as_of(bar.ts[2]).foo).to eq foo }
|
129
|
+
it { expect(bar.as_of(bar.ts[3]).foo).to eq foo }
|
97
130
|
|
98
|
-
it { bar.as_of(bar.ts[0]).foo.name.
|
99
|
-
it { bar.as_of(bar.ts[1]).foo.name.
|
100
|
-
it { bar.as_of(bar.ts[2]).foo.name.
|
101
|
-
it { bar.as_of(bar.ts[3]).foo.name.
|
131
|
+
it { expect(bar.as_of(bar.ts[0]).foo.name).to eq 'foo bar' }
|
132
|
+
it { expect(bar.as_of(bar.ts[1]).foo.name).to eq 'foo bar' }
|
133
|
+
it { expect(bar.as_of(bar.ts[2]).foo.name).to eq 'new foo' }
|
134
|
+
it { expect(bar.as_of(bar.ts[3]).foo.name).to eq 'new foo' }
|
102
135
|
end
|
103
136
|
|
104
137
|
it 'doesn\'t raise RecordNotFound when no history records are found' do
|
105
138
|
expect { foo.as_of(1.minute.ago) }.to_not raise_error
|
106
|
-
foo.as_of(1.minute.ago).
|
139
|
+
expect(foo.as_of(1.minute.ago)).to be(nil)
|
107
140
|
end
|
108
141
|
|
109
142
|
|
@@ -118,164 +151,161 @@ describe ChronoModel::TimeMachine do
|
|
118
151
|
hidden = ts_eval { Defoo.create! :name => 'hidden 1', :active => false }
|
119
152
|
ts_eval(hidden) { update_attributes! :name => 'hidden 2' }
|
120
153
|
|
121
|
-
it { Defoo.as_of(active.ts[0]).map(&:name).
|
122
|
-
it { Defoo.as_of(active.ts[1]).map(&:name).
|
123
|
-
it { Defoo.as_of(hidden.ts[0]).map(&:name).
|
124
|
-
it { Defoo.as_of(hidden.ts[1]).map(&:name).
|
154
|
+
it { expect(Defoo.as_of(active.ts[0]).map(&:name)).to eq ['active 1'] }
|
155
|
+
it { expect(Defoo.as_of(active.ts[1]).map(&:name)).to eq ['active 2'] }
|
156
|
+
it { expect(Defoo.as_of(hidden.ts[0]).map(&:name)).to eq ['active 2'] }
|
157
|
+
it { expect(Defoo.as_of(hidden.ts[1]).map(&:name)).to eq ['active 2'] }
|
125
158
|
|
126
|
-
it { Defoo.unscoped.as_of(active.ts[0]).map(&:name).
|
127
|
-
it { Defoo.unscoped.as_of(active.ts[1]).map(&:name).
|
128
|
-
it { Defoo.unscoped.as_of(hidden.ts[0]).map(&:name).
|
129
|
-
it { Defoo.unscoped.as_of(hidden.ts[1]).map(&:name).
|
159
|
+
it { expect(Defoo.unscoped.as_of(active.ts[0]).map(&:name)).to eq ['active 1'] }
|
160
|
+
it { expect(Defoo.unscoped.as_of(active.ts[1]).map(&:name)).to eq ['active 2'] }
|
161
|
+
it { expect(Defoo.unscoped.as_of(hidden.ts[0]).map(&:name)).to eq ['active 2', 'hidden 1'] }
|
162
|
+
it { expect(Defoo.unscoped.as_of(hidden.ts[1]).map(&:name)).to eq ['active 2', 'hidden 2'] }
|
130
163
|
end
|
131
164
|
|
132
165
|
describe 'proxies from non-temporal models to temporal ones' do
|
133
|
-
it { baz.as_of(bar.ts[0]).name.
|
134
|
-
it { baz.as_of(bar.ts[1]).name.
|
135
|
-
it { baz.as_of(bar.ts[2]).name.
|
136
|
-
it { baz.as_of(bar.ts[3]).name.
|
166
|
+
it { expect(baz.as_of(bar.ts[0]).name).to eq 'baz' }
|
167
|
+
it { expect(baz.as_of(bar.ts[1]).name).to eq 'baz' }
|
168
|
+
it { expect(baz.as_of(bar.ts[2]).name).to eq 'baz' }
|
169
|
+
it { expect(baz.as_of(bar.ts[3]).name).to eq 'baz' }
|
137
170
|
|
138
|
-
it { baz.as_of(bar.ts[0]).bar.name.
|
139
|
-
it { baz.as_of(bar.ts[1]).bar.name.
|
140
|
-
it { baz.as_of(bar.ts[2]).bar.name.
|
141
|
-
it { baz.as_of(bar.ts[3]).bar.name.
|
171
|
+
it { expect(baz.as_of(bar.ts[0]).bar.name).to eq 'bar' }
|
172
|
+
it { expect(baz.as_of(bar.ts[1]).bar.name).to eq 'foo bar' }
|
173
|
+
it { expect(baz.as_of(bar.ts[2]).bar.name).to eq 'bar bar' }
|
174
|
+
it { expect(baz.as_of(bar.ts[3]).bar.name).to eq 'new bar' }
|
142
175
|
|
143
|
-
it { baz.as_of(bar.ts[0]).bar.foo.name.
|
144
|
-
it { baz.as_of(bar.ts[1]).bar.foo.name.
|
145
|
-
it { baz.as_of(bar.ts[2]).bar.foo.name.
|
146
|
-
it { baz.as_of(bar.ts[3]).bar.foo.name.
|
176
|
+
it { expect(baz.as_of(bar.ts[0]).bar.foo.name).to eq 'foo bar' }
|
177
|
+
it { expect(baz.as_of(bar.ts[1]).bar.foo.name).to eq 'foo bar' }
|
178
|
+
it { expect(baz.as_of(bar.ts[2]).bar.foo.name).to eq 'new foo' }
|
179
|
+
it { expect(baz.as_of(bar.ts[3]).bar.foo.name).to eq 'new foo' }
|
147
180
|
end
|
148
181
|
end
|
149
182
|
|
150
183
|
describe '#history' do
|
151
184
|
describe 'returns historical instances' do
|
152
|
-
it { foo.history.
|
153
|
-
it { foo.history.map(&:name).
|
185
|
+
it { expect(foo.history.size).to eq(3) }
|
186
|
+
it { expect(foo.history.map(&:name)).to eq ['foo', 'foo bar', 'new foo'] }
|
154
187
|
|
155
|
-
it { bar.history.
|
156
|
-
it { bar.history.map(&:name).
|
188
|
+
it { expect(bar.history.size).to eq(4) }
|
189
|
+
it { expect(bar.history.map(&:name)).to eq ['bar', 'foo bar', 'bar bar', 'new bar'] }
|
157
190
|
end
|
158
191
|
|
159
192
|
describe 'does not return read only records' do
|
160
|
-
it { foo.history.all?(&:readonly?).
|
161
|
-
it { bar.history.all?(&:readonly?).
|
193
|
+
it { expect(foo.history.all?(&:readonly?)).to be(false) }
|
194
|
+
it { expect(bar.history.all?(&:readonly?)).to be(false) }
|
162
195
|
end
|
163
196
|
|
164
197
|
describe 'takes care of associated records' do
|
165
198
|
subject { foo.history.map {|f| f.bars.first.try(:name)} }
|
166
|
-
it {
|
199
|
+
it { is_expected.to eq [nil, 'foo bar', 'new bar'] }
|
167
200
|
end
|
168
201
|
|
169
202
|
describe 'does not return read only associated records' do
|
170
|
-
it { foo.history[2].bars.all?(&:readonly?).
|
171
|
-
it { bar.history.all? {|b| b.foo.readonly?}.
|
203
|
+
it { expect(foo.history[2].bars.all?(&:readonly?)).to_not be(true) }
|
204
|
+
it { expect(bar.history.all? {|b| b.foo.readonly?}).to_not be(true) }
|
172
205
|
end
|
173
206
|
|
174
207
|
describe 'allows a custom select list' do
|
175
|
-
it { foo.history.select(:id).first.attributes.keys.
|
208
|
+
it { expect(foo.history.select(:id).first.attributes.keys).to eq %w( id ) }
|
176
209
|
end
|
177
210
|
|
178
211
|
describe 'does not add as_of_time when there are aggregates' do
|
179
|
-
it { foo.history.select('max
|
180
|
-
|
212
|
+
it { expect(foo.history.select('max(id)').to_sql).to_not match /as_of_time/ }
|
213
|
+
|
214
|
+
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 ) }
|
181
215
|
end
|
182
216
|
|
183
217
|
context 'with STI models' do
|
184
218
|
pub = ts_eval { Publication.create! :title => 'wrong title' }
|
185
219
|
ts_eval(pub) { update_attributes! :title => 'correct title' }
|
186
220
|
|
187
|
-
it { pub.history.map(&:title).
|
188
|
-
end
|
189
|
-
|
190
|
-
describe 'allows a custom order list' do
|
191
|
-
it { expect { foo.history.order('id') }.to_not raise_error }
|
192
|
-
it { foo.history.order('id').to_sql.should =~ /order by id/i }
|
221
|
+
it { expect(pub.history.map(&:title)).to eq ['wrong title', 'correct title'] }
|
193
222
|
end
|
194
223
|
|
195
224
|
context '.sorted' do
|
196
225
|
describe 'orders by recorded_at, hid' do
|
197
|
-
it { foo.history.sorted.to_sql.
|
226
|
+
it { expect(foo.history.sorted.to_sql).to match /order by .+"recorded_at", .+"hid"/i }
|
198
227
|
end
|
199
228
|
end
|
200
|
-
|
201
229
|
end
|
202
230
|
|
203
231
|
describe '#pred' do
|
204
232
|
context 'on the first history entry' do
|
205
233
|
subject { foo.history.first.pred }
|
206
|
-
it {
|
234
|
+
it { is_expected.to be(nil) }
|
207
235
|
end
|
208
236
|
|
209
237
|
context 'on the second history entry' do
|
210
238
|
subject { foo.history.second.pred }
|
211
|
-
it {
|
239
|
+
it { is_expected.to eq foo.history.first }
|
212
240
|
end
|
213
241
|
|
214
242
|
context 'on the last history entry' do
|
215
243
|
subject { foo.history.last.pred }
|
216
|
-
it {
|
244
|
+
it { is_expected.to eq foo.history[foo.history.size - 2] }
|
217
245
|
end
|
218
246
|
end
|
219
247
|
|
220
248
|
describe '#succ' do
|
221
249
|
context 'on the first history entry' do
|
222
250
|
subject { foo.history.first.succ }
|
223
|
-
it {
|
251
|
+
it { is_expected.to eq foo.history.second }
|
224
252
|
end
|
225
253
|
|
226
254
|
context 'on the second history entry' do
|
227
255
|
subject { foo.history.second.succ }
|
228
|
-
it {
|
256
|
+
it { is_expected.to eq foo.history.third }
|
229
257
|
end
|
230
258
|
|
231
259
|
context 'on the last history entry' do
|
232
260
|
subject { foo.history.last.succ }
|
233
|
-
it {
|
261
|
+
it { is_expected.to be(nil) }
|
234
262
|
end
|
235
263
|
end
|
236
264
|
|
237
265
|
describe '#first' do
|
238
266
|
subject { foo.history.sample.first }
|
239
|
-
it {
|
267
|
+
it { is_expected.to eq foo.history.first }
|
240
268
|
end
|
241
269
|
|
242
270
|
describe '#last' do
|
243
271
|
subject { foo.history.sample.last }
|
244
|
-
it {
|
272
|
+
it { is_expected.to eq foo.history.last }
|
245
273
|
end
|
246
274
|
|
247
275
|
describe '#current_version' do
|
248
276
|
describe 'on plain records' do
|
249
277
|
subject { foo.current_version }
|
250
|
-
it {
|
278
|
+
it { is_expected.to eq foo }
|
251
279
|
end
|
252
280
|
|
253
281
|
describe 'from #as_of' do
|
254
282
|
subject { foo.as_of(Time.now) }
|
255
|
-
it {
|
283
|
+
it { is_expected.to eq foo }
|
256
284
|
end
|
257
285
|
|
258
286
|
describe 'on historical records' do
|
259
287
|
subject { foo.history.sample.current_version }
|
260
|
-
it {
|
288
|
+
it { is_expected.to eq foo }
|
261
289
|
end
|
262
290
|
end
|
263
291
|
|
264
292
|
describe '#historical?' do
|
293
|
+
subject { record.historical? }
|
294
|
+
|
265
295
|
describe 'on plain records' do
|
266
|
-
|
267
|
-
it {
|
296
|
+
let(:record) { foo }
|
297
|
+
it { is_expected.to be(false) }
|
268
298
|
end
|
269
299
|
|
270
300
|
describe 'on historical records' do
|
271
301
|
describe 'from #history' do
|
272
|
-
|
273
|
-
it {
|
302
|
+
let(:record) { foo.history.first }
|
303
|
+
it { is_expected.to be(true) }
|
274
304
|
end
|
275
305
|
|
276
306
|
describe 'from #as_of' do
|
277
|
-
|
278
|
-
it {
|
307
|
+
let(:record) { foo.as_of(Time.now) }
|
308
|
+
it { is_expected.to be(true) }
|
279
309
|
end
|
280
310
|
end
|
281
311
|
end
|
@@ -302,24 +332,26 @@ describe ChronoModel::TimeMachine do
|
|
302
332
|
it { expect { rec.reload }.to raise_error(ActiveRecord::RecordNotFound) }
|
303
333
|
|
304
334
|
describe 'does not delete its history' do
|
335
|
+
subject { record.name }
|
336
|
+
|
305
337
|
context do
|
306
|
-
|
307
|
-
|
338
|
+
let(:record) { rec.as_of(rec.ts.first) }
|
339
|
+
it { is_expected.to eq 'alive foo' }
|
308
340
|
end
|
309
341
|
|
310
342
|
context do
|
311
|
-
|
312
|
-
|
343
|
+
let(:record) { rec.as_of(rec.ts.last) }
|
344
|
+
it { is_expected.to eq 'dying foo' }
|
313
345
|
end
|
314
346
|
|
315
347
|
context do
|
316
|
-
|
317
|
-
|
348
|
+
let(:record) { Foo.as_of(rec.ts.first).where(:fooity => 42).first }
|
349
|
+
it { is_expected.to eq 'alive foo' }
|
318
350
|
end
|
319
351
|
|
320
352
|
context do
|
321
353
|
subject { Foo.history.where(:fooity => 42).map(&:name) }
|
322
|
-
it {
|
354
|
+
it { is_expected.to eq ['alive foo', 'dying foo'] }
|
323
355
|
end
|
324
356
|
end
|
325
357
|
end
|
@@ -339,14 +371,14 @@ describe ChronoModel::TimeMachine do
|
|
339
371
|
describe 'on records having an :has_many relationship' do
|
340
372
|
describe 'by default returns timestamps of the record only' do
|
341
373
|
subject { split.call(foo.timeline) }
|
342
|
-
|
343
|
-
it {
|
374
|
+
it { expect(subject.size).to eq foo.ts.size }
|
375
|
+
it { is_expected.to eq timestamps_from.call(foo) }
|
344
376
|
end
|
345
377
|
|
346
378
|
describe 'when asked, returns timestamps including the related objects' do
|
347
379
|
subject { split.call(foo.timeline(:with => :bars)) }
|
348
|
-
|
349
|
-
it {
|
380
|
+
it { expect(subject.size).to eq(foo.ts.size + bar.ts.size) }
|
381
|
+
it { is_expected.to eq(timestamps_from.call(foo, *foo.bars)) }
|
350
382
|
end
|
351
383
|
end
|
352
384
|
|
@@ -364,8 +396,8 @@ describe ChronoModel::TimeMachine do
|
|
364
396
|
}
|
365
397
|
end
|
366
398
|
|
367
|
-
|
368
|
-
it {
|
399
|
+
it { expect(subject.size).to eq expected.size }
|
400
|
+
it { is_expected.to eq expected }
|
369
401
|
end
|
370
402
|
end
|
371
403
|
|
@@ -373,8 +405,8 @@ describe ChronoModel::TimeMachine do
|
|
373
405
|
subject { split.call(baz.timeline) }
|
374
406
|
|
375
407
|
describe 'returns timestamps of its temporal associations' do
|
376
|
-
|
377
|
-
it {
|
408
|
+
it { expect(subject.size).to eq bar.ts.size }
|
409
|
+
it { is_expected.to eq timestamps_from.call(bar) }
|
378
410
|
end
|
379
411
|
end
|
380
412
|
end
|
@@ -383,13 +415,13 @@ describe ChronoModel::TimeMachine do
|
|
383
415
|
context 'on plain records' do
|
384
416
|
context 'having history' do
|
385
417
|
subject { bar.last_changes }
|
386
|
-
it {
|
418
|
+
it { is_expected.to eq('name' => ['bar bar', 'new bar']) }
|
387
419
|
end
|
388
420
|
|
389
421
|
context 'without history' do
|
390
422
|
let(:record) { Bar.create!(:name => 'foreveralone') }
|
391
423
|
subject { record.last_changes }
|
392
|
-
it {
|
424
|
+
it { is_expected.to be_nil }
|
393
425
|
after { record.destroy.history.delete_all } # UGLY
|
394
426
|
end
|
395
427
|
end
|
@@ -397,56 +429,46 @@ describe ChronoModel::TimeMachine do
|
|
397
429
|
context 'on history records' do
|
398
430
|
context 'at the beginning of the timeline' do
|
399
431
|
subject { bar.history.first.last_changes }
|
400
|
-
it {
|
432
|
+
it { is_expected.to be_nil }
|
401
433
|
end
|
402
434
|
|
403
435
|
context 'in the middle of the timeline' do
|
404
436
|
subject { bar.history.second.last_changes }
|
405
|
-
it {
|
437
|
+
it { is_expected.to eq('name' => ['bar', 'foo bar']) }
|
406
438
|
end
|
407
439
|
end
|
408
440
|
end
|
409
441
|
|
410
442
|
describe '#changes_against' do
|
411
443
|
context 'can compare records against history' do
|
412
|
-
it { bar.changes_against(bar.history.first).
|
413
|
-
|
414
|
-
|
415
|
-
it { bar.changes_against(bar.history.
|
416
|
-
{'name' => ['foo bar', 'new bar']} }
|
417
|
-
|
418
|
-
it { bar.changes_against(bar.history.third).should ==
|
419
|
-
{'name' => ['bar bar', 'new bar']} }
|
420
|
-
|
421
|
-
it { bar.changes_against(bar.history.last).should == {} }
|
444
|
+
it { expect(bar.changes_against(bar.history.first)).to eq('name' => ['bar', 'new bar']) }
|
445
|
+
it { expect(bar.changes_against(bar.history.second)).to eq('name' => ['foo bar', 'new bar']) }
|
446
|
+
it { expect(bar.changes_against(bar.history.third)).to eq('name' => ['bar bar', 'new bar']) }
|
447
|
+
it { expect(bar.changes_against(bar.history.last)).to eq({}) }
|
422
448
|
end
|
423
449
|
|
424
450
|
context 'can compare history against history' do
|
425
|
-
it { bar.history.first.changes_against(bar.history.third).
|
426
|
-
|
427
|
-
|
428
|
-
it { bar.history.second.changes_against(bar.history.third).should ==
|
429
|
-
{'name' => ['bar bar', 'foo bar']} }
|
430
|
-
|
431
|
-
it { bar.history.third.changes_against(bar.history.third).should == {} }
|
451
|
+
it { expect(bar.history.first.changes_against(bar.history.third)).to eq('name' => ['bar bar', 'bar']) }
|
452
|
+
it { expect(bar.history.second.changes_against(bar.history.third)).to eq('name' => ['bar bar', 'foo bar']) }
|
453
|
+
it { expect(bar.history.third.changes_against(bar.history.third)).to eq({}) }
|
432
454
|
end
|
433
455
|
end
|
434
456
|
|
435
457
|
describe '#pred' do
|
436
458
|
context 'on records having history' do
|
437
459
|
subject { bar.pred }
|
438
|
-
|
460
|
+
it { expect(subject.name).to eq 'bar bar' }
|
439
461
|
end
|
440
462
|
|
441
463
|
context 'when there is enough history' do
|
442
464
|
subject { bar.pred.pred.pred.pred }
|
443
|
-
|
465
|
+
it { expect(subject.name).to eq 'bar' }
|
444
466
|
end
|
445
467
|
|
446
468
|
context 'when no history is recorded' do
|
447
469
|
let(:record) { Bar.create!(:name => 'quuuux') }
|
448
470
|
subject { record.pred }
|
449
|
-
it {
|
471
|
+
it { is_expected.to be(nil) }
|
450
472
|
after { record.destroy.history.delete_all }
|
451
473
|
end
|
452
474
|
end
|
@@ -462,9 +484,9 @@ describe ChronoModel::TimeMachine do
|
|
462
484
|
describe ['#', attr].join do
|
463
485
|
subject { record.public_send(attr) }
|
464
486
|
|
465
|
-
it {
|
466
|
-
it {
|
467
|
-
it {
|
487
|
+
it { is_expected.to be_present }
|
488
|
+
it { is_expected.to be_a(Time) }
|
489
|
+
it { is_expected.to be_utc }
|
468
490
|
end
|
469
491
|
end
|
470
492
|
end
|
@@ -484,7 +506,7 @@ describe ChronoModel::TimeMachine do
|
|
484
506
|
describe ['#', attr].join do
|
485
507
|
subject { record.public_send(attr) }
|
486
508
|
|
487
|
-
it {
|
509
|
+
it { is_expected.to be(nil) }
|
488
510
|
end
|
489
511
|
end
|
490
512
|
end
|
@@ -499,38 +521,38 @@ describe ChronoModel::TimeMachine do
|
|
499
521
|
after(:all) { foos.each(&:destroy); bars.each(&:destroy) }
|
500
522
|
|
501
523
|
describe '.as_of' do
|
502
|
-
it { Foo.as_of(1.month.ago).
|
524
|
+
it { expect(Foo.as_of(1.month.ago)).to eq [] }
|
503
525
|
|
504
|
-
it { Foo.as_of(foos[0].ts[0]).
|
505
|
-
it { Foo.as_of(foos[1].ts[0]).
|
506
|
-
it { Foo.as_of(Time.now ).
|
526
|
+
it { expect(Foo.as_of(foos[0].ts[0])).to eq [foo, foos[0]] }
|
527
|
+
it { expect(Foo.as_of(foos[1].ts[0])).to eq [foo, foos[0], foos[1]] }
|
528
|
+
it { expect(Foo.as_of(Time.now )).to eq [foo, foos[0], foos[1]] }
|
507
529
|
|
508
|
-
it { Bar.as_of(foos[1].ts[0]).
|
530
|
+
it { expect(Bar.as_of(foos[1].ts[0])).to eq [bar] }
|
509
531
|
|
510
|
-
it { Bar.as_of(bars[0].ts[0]).
|
511
|
-
it { Bar.as_of(bars[1].ts[0]).
|
512
|
-
it { Bar.as_of(Time.now ).
|
532
|
+
it { expect(Bar.as_of(bars[0].ts[0])).to eq [bar, bars[0]] }
|
533
|
+
it { expect(Bar.as_of(bars[1].ts[0])).to eq [bar, bars[0], bars[1]] }
|
534
|
+
it { expect(Bar.as_of(Time.now )).to eq [bar, bars[0], bars[1]] }
|
513
535
|
|
514
536
|
# Associations
|
515
537
|
context do
|
516
|
-
subject { foos[0] }
|
538
|
+
subject { foos[0].id }
|
517
539
|
|
518
|
-
it { Foo.as_of(foos[0].ts[0]).find(subject).bars.
|
519
|
-
it { Foo.as_of(foos[1].ts[0]).find(subject).bars.
|
520
|
-
it { Foo.as_of(bars[0].ts[0]).find(subject).bars.
|
521
|
-
it { Foo.as_of(bars[1].ts[0]).find(subject).bars.
|
522
|
-
it { Foo.as_of(Time.now ).find(subject).bars.
|
540
|
+
it { expect(Foo.as_of(foos[0].ts[0]).find(subject).bars).to eq [] }
|
541
|
+
it { expect(Foo.as_of(foos[1].ts[0]).find(subject).bars).to eq [] }
|
542
|
+
it { expect(Foo.as_of(bars[0].ts[0]).find(subject).bars).to eq [bars[0]] }
|
543
|
+
it { expect(Foo.as_of(bars[1].ts[0]).find(subject).bars).to eq [bars[0]] }
|
544
|
+
it { expect(Foo.as_of(Time.now ).find(subject).bars).to eq [bars[0]] }
|
523
545
|
end
|
524
546
|
|
525
547
|
context do
|
526
|
-
subject { foos[1] }
|
548
|
+
subject { foos[1].id }
|
527
549
|
|
528
550
|
it { expect { Foo.as_of(foos[0].ts[0]).find(subject) }.to raise_error(ActiveRecord::RecordNotFound) }
|
529
551
|
it { expect { Foo.as_of(foos[1].ts[0]).find(subject) }.to_not raise_error }
|
530
552
|
|
531
|
-
it { Foo.as_of(bars[0].ts[0]).find(subject).bars.
|
532
|
-
it { Foo.as_of(bars[1].ts[0]).find(subject).bars.
|
533
|
-
it { Foo.as_of(Time.now ).find(subject).bars.
|
553
|
+
it { expect(Foo.as_of(bars[0].ts[0]).find(subject).bars).to eq [] }
|
554
|
+
it { expect(Foo.as_of(bars[1].ts[0]).find(subject).bars).to eq [bars[1]] }
|
555
|
+
it { expect(Foo.as_of(Time.now ).find(subject).bars).to eq [bars[1]] }
|
534
556
|
end
|
535
557
|
end
|
536
558
|
|
@@ -543,17 +565,17 @@ describe ChronoModel::TimeMachine do
|
|
543
565
|
['bar', 'foo bar', 'bar bar', 'new bar', 'bar 0', 'bar 1']
|
544
566
|
}
|
545
567
|
|
546
|
-
it { Foo.history.all.map(&:name).
|
547
|
-
it { Bar.history.all.map(&:name).
|
568
|
+
it { expect(Foo.history.all.map(&:name)).to eq foo_history }
|
569
|
+
it { expect(Bar.history.all.map(&:name)).to eq bar_history }
|
548
570
|
end
|
549
571
|
|
550
572
|
describe '.time_query' do
|
551
|
-
it { Foo.history.time_query(:after, :now, inclusive: true ).count.
|
552
|
-
it { Foo.history.time_query(:after, :now, inclusive: false).count.
|
553
|
-
it { Foo.history.time_query(:before, :now, inclusive: true ).count.
|
554
|
-
it { Foo.history.time_query(:before, :now, inclusive: false).count.
|
573
|
+
it { expect(Foo.history.time_query(:after, :now, inclusive: true ).count).to eq 3 }
|
574
|
+
it { expect(Foo.history.time_query(:after, :now, inclusive: false).count).to eq 0 }
|
575
|
+
it { expect(Foo.history.time_query(:before, :now, inclusive: true ).count).to eq 5 }
|
576
|
+
it { expect(Foo.history.time_query(:before, :now, inclusive: false).count).to eq 2 }
|
555
577
|
|
556
|
-
it { Foo.history.past.size.
|
578
|
+
it { expect(Foo.history.past.size).to eq 2 }
|
557
579
|
end
|
558
580
|
|
559
581
|
end
|
@@ -571,10 +593,10 @@ describe ChronoModel::TimeMachine do
|
|
571
593
|
end
|
572
594
|
|
573
595
|
it "generate only a single history record" do
|
574
|
-
r1.history.
|
596
|
+
expect(r1.history.size).to eq(2)
|
575
597
|
|
576
|
-
r1.history.first.name.
|
577
|
-
r1.history.last.name.
|
598
|
+
expect(r1.history.first.name).to eq 'xact test'
|
599
|
+
expect(r1.history.last.name).to eq 'does work'
|
578
600
|
end
|
579
601
|
end
|
580
602
|
|
@@ -589,9 +611,8 @@ describe ChronoModel::TimeMachine do
|
|
589
611
|
end
|
590
612
|
|
591
613
|
it 'generates a single history record' do
|
592
|
-
r2.history.
|
593
|
-
|
594
|
-
r2.history.first.name.should == 'I am Foo'
|
614
|
+
expect(r2.history.size).to eq(1)
|
615
|
+
expect(r2.history.first.name).to eq 'I am Foo'
|
595
616
|
end
|
596
617
|
end
|
597
618
|
|
@@ -603,7 +624,7 @@ describe ChronoModel::TimeMachine do
|
|
603
624
|
end
|
604
625
|
|
605
626
|
it 'does not generate any history' do
|
606
|
-
Foo.history.where(:id => r3.id).
|
627
|
+
expect(Foo.history.where(:id => r3.id)).to be_empty
|
607
628
|
end
|
608
629
|
end
|
609
630
|
end
|
@@ -620,9 +641,8 @@ describe ChronoModel::TimeMachine do
|
|
620
641
|
subject.reload
|
621
642
|
end
|
622
643
|
|
623
|
-
it {
|
624
|
-
it {
|
625
|
-
its(:name) { should == 'modified bar history' }
|
644
|
+
it { is_expected.to be_a(Bar::History) }
|
645
|
+
it { expect(subject.name).to eq 'modified bar history' }
|
626
646
|
end
|
627
647
|
|
628
648
|
describe '#save!' do
|
@@ -634,9 +654,8 @@ describe ChronoModel::TimeMachine do
|
|
634
654
|
subject.reload
|
635
655
|
end
|
636
656
|
|
637
|
-
it {
|
638
|
-
it {
|
639
|
-
its(:name) { should == 'another modified bar history' }
|
657
|
+
it { is_expected.to be_a(Bar::History) }
|
658
|
+
it { expect(subject.name).to eq 'another modified bar history' }
|
640
659
|
end
|
641
660
|
end
|
642
661
|
|