chrono_model 0.4.0 → 0.5.0.beta
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.
- data/Gemfile.lock +1 -1
- data/README.md +64 -35
- data/README.sql +73 -27
- data/lib/chrono_model/adapter.rb +235 -44
- data/lib/chrono_model/patches.rb +28 -75
- data/lib/chrono_model/railtie.rb +0 -25
- data/lib/chrono_model/time_gate.rb +36 -0
- data/lib/chrono_model/time_machine.rb +345 -122
- data/lib/chrono_model/utils.rb +89 -0
- data/lib/chrono_model/version.rb +1 -1
- data/lib/chrono_model.rb +2 -7
- data/spec/adapter_spec.rb +74 -7
- data/spec/support/connection.rb +1 -1
- data/spec/support/helpers.rb +20 -1
- data/spec/time_machine_spec.rb +216 -12
- metadata +11 -9
data/lib/chrono_model.rb
CHANGED
@@ -3,6 +3,8 @@ require 'chrono_model/adapter'
|
|
3
3
|
require 'chrono_model/compatibility'
|
4
4
|
require 'chrono_model/patches'
|
5
5
|
require 'chrono_model/time_machine'
|
6
|
+
require 'chrono_model/time_gate'
|
7
|
+
require 'chrono_model/utils'
|
6
8
|
|
7
9
|
module ChronoModel
|
8
10
|
class Error < ActiveRecord::ActiveRecordError #:nodoc:
|
@@ -24,11 +26,4 @@ silence_warnings do
|
|
24
26
|
# We need to override the "scoped" method on AR::Association for temporal
|
25
27
|
# associations to work as well
|
26
28
|
ActiveRecord::Associations::Association = ChronoModel::Patches::Association
|
27
|
-
|
28
|
-
# This implements correct WITH syntax on PostgreSQL
|
29
|
-
Arel::Visitors::PostgreSQL = ChronoModel::Patches::Visitor
|
30
|
-
|
31
|
-
# This adds .with support to ActiveRecord::Relation
|
32
|
-
ActiveRecord::Relation.instance_eval { include ChronoModel::Patches::QueryMethods }
|
33
|
-
ActiveRecord::Base.extend ChronoModel::Patches::Querying
|
34
29
|
end
|
data/spec/adapter_spec.rb
CHANGED
@@ -59,7 +59,7 @@ describe ChronoModel::Adapter do
|
|
59
59
|
|
60
60
|
def native.to_proc
|
61
61
|
proc {|t|
|
62
|
-
t.string :test
|
62
|
+
t.string :test, :null => false
|
63
63
|
t.integer :foo
|
64
64
|
t.float :bar
|
65
65
|
t.text :baz
|
@@ -325,8 +325,8 @@ describe ChronoModel::Adapter do
|
|
325
325
|
it { should include(['id', 'integer']) }
|
326
326
|
end
|
327
327
|
|
328
|
-
with_temporal_table
|
329
|
-
with_plain_table
|
328
|
+
with_temporal_table(&assert)
|
329
|
+
with_plain_table( &assert)
|
330
330
|
end
|
331
331
|
|
332
332
|
describe '.primary_key' do
|
@@ -336,8 +336,8 @@ describe ChronoModel::Adapter do
|
|
336
336
|
it { should == 'id' }
|
337
337
|
end
|
338
338
|
|
339
|
-
with_temporal_table
|
340
|
-
with_plain_table
|
339
|
+
with_temporal_table(&assert)
|
340
|
+
with_plain_table( &assert)
|
341
341
|
end
|
342
342
|
|
343
343
|
describe '.indexes' do
|
@@ -353,8 +353,8 @@ describe ChronoModel::Adapter do
|
|
353
353
|
it { subject.map(&:columns).should =~ [['foo'], ['bar', 'baz']] }
|
354
354
|
end
|
355
355
|
|
356
|
-
with_temporal_table
|
357
|
-
with_plain_table
|
356
|
+
with_temporal_table(&assert)
|
357
|
+
with_plain_table( &assert)
|
358
358
|
end
|
359
359
|
|
360
360
|
describe '.on_schema' do
|
@@ -395,4 +395,71 @@ describe ChronoModel::Adapter do
|
|
395
395
|
end
|
396
396
|
end
|
397
397
|
|
398
|
+
|
399
|
+
context 'INSERT multiple values' do
|
400
|
+
before :all do
|
401
|
+
adapter.create_table table, :temporal => true, &columns
|
402
|
+
end
|
403
|
+
|
404
|
+
after :all do
|
405
|
+
adapter.drop_table table
|
406
|
+
end
|
407
|
+
|
408
|
+
let(:current) { [ChronoModel::Adapter::TEMPORAL_SCHEMA, table].join('.') }
|
409
|
+
let(:history) { [ChronoModel::Adapter::HISTORY_SCHEMA, table].join('.') }
|
410
|
+
|
411
|
+
def count(table)
|
412
|
+
adapter.select_value("SELECT COUNT(*) FROM ONLY #{table}").to_i
|
413
|
+
end
|
414
|
+
|
415
|
+
def ids(table)
|
416
|
+
adapter.select_values("SELECT id FROM ONLY #{table} ORDER BY id")
|
417
|
+
end
|
418
|
+
|
419
|
+
context 'when succeeding' do
|
420
|
+
def insert
|
421
|
+
adapter.execute <<-SQL
|
422
|
+
INSERT INTO #{table} (test, foo) VALUES
|
423
|
+
('test1', 1),
|
424
|
+
('test2', 2);
|
425
|
+
SQL
|
426
|
+
end
|
427
|
+
|
428
|
+
it { expect { insert }.to_not raise_error }
|
429
|
+
it { count(current).should == 2 }
|
430
|
+
it { count(history).should == 2 }
|
431
|
+
end
|
432
|
+
|
433
|
+
context 'when failing' do
|
434
|
+
def insert
|
435
|
+
adapter.execute <<-SQL
|
436
|
+
INSERT INTO #{table} (test, foo) VALUES
|
437
|
+
('test3', 3),
|
438
|
+
(NULL, 0);
|
439
|
+
SQL
|
440
|
+
end
|
441
|
+
|
442
|
+
it { expect { insert }.to raise_error }
|
443
|
+
it { count(current).should == 2 } # Because the previous
|
444
|
+
it { count(history).should == 2 } # records are preserved
|
445
|
+
end
|
446
|
+
|
447
|
+
context 'after a failure' do
|
448
|
+
def insert
|
449
|
+
adapter.execute <<-SQL
|
450
|
+
INSERT INTO #{table} (test, foo) VALUES
|
451
|
+
('test4', 3),
|
452
|
+
('test5', 4);
|
453
|
+
SQL
|
454
|
+
end
|
455
|
+
|
456
|
+
it { expect { insert }.to_not raise_error }
|
457
|
+
|
458
|
+
it { count(current).should == 4 }
|
459
|
+
it { count(history).should == 4 }
|
460
|
+
|
461
|
+
it { ids(current).should == ids(history) }
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
398
465
|
end
|
data/spec/support/connection.rb
CHANGED
@@ -6,7 +6,7 @@ module ChronoTest
|
|
6
6
|
extend self
|
7
7
|
|
8
8
|
AR = ActiveRecord::Base
|
9
|
-
log = ENV['VERBOSE'].present? ? $stderr : 'spec/debug.log'.tap{|f| File.
|
9
|
+
log = ENV['VERBOSE'].present? ? $stderr : 'spec/debug.log'.tap{|f| File.open(f, "ab") { |ft| ft.truncate(0) }}
|
10
10
|
AR.logger = ::Logger.new(log).tap do |l|
|
11
11
|
l.level = 0
|
12
12
|
end
|
data/spec/support/helpers.rb
CHANGED
@@ -69,6 +69,11 @@ module ChronoTest::Helpers
|
|
69
69
|
t.string :name
|
70
70
|
t.boolean :active
|
71
71
|
end
|
72
|
+
|
73
|
+
adapter.create_table 'elements', :temporal => true do |t|
|
74
|
+
t.string :title
|
75
|
+
t.string :type
|
76
|
+
end
|
72
77
|
end
|
73
78
|
|
74
79
|
after(:all) do
|
@@ -90,10 +95,16 @@ module ChronoTest::Helpers
|
|
90
95
|
|
91
96
|
belongs_to :foo
|
92
97
|
has_one :baz
|
98
|
+
|
99
|
+
has_timeline :with => :foo
|
93
100
|
end
|
94
101
|
|
95
102
|
class ::Baz < ActiveRecord::Base
|
96
|
-
|
103
|
+
include ChronoModel::TimeGate
|
104
|
+
|
105
|
+
belongs_to :bar
|
106
|
+
|
107
|
+
has_timeline :with => :bar
|
97
108
|
end
|
98
109
|
|
99
110
|
class ::Defoo < ActiveRecord::Base
|
@@ -101,6 +112,14 @@ module ChronoTest::Helpers
|
|
101
112
|
|
102
113
|
default_scope where(:active => true)
|
103
114
|
end
|
115
|
+
|
116
|
+
# STI case (https://github.com/ifad/chronomodel/issues/5)
|
117
|
+
class ::Element < ActiveRecord::Base
|
118
|
+
include ChronoModel::TimeMachine
|
119
|
+
end
|
120
|
+
|
121
|
+
class ::Publication < Element
|
122
|
+
end
|
104
123
|
}
|
105
124
|
|
106
125
|
def define_models!
|
data/spec/time_machine_spec.rb
CHANGED
@@ -10,7 +10,12 @@ describe ChronoModel::TimeMachine do
|
|
10
10
|
describe '.chrono_models' do
|
11
11
|
subject { ChronoModel::TimeMachine.chrono_models }
|
12
12
|
|
13
|
-
it { should == {
|
13
|
+
it { should == {
|
14
|
+
'foos' => Foo::History,
|
15
|
+
'defoos' => Defoo::History,
|
16
|
+
'bars' => Bar::History,
|
17
|
+
'elements' => Element::History
|
18
|
+
} }
|
14
19
|
end
|
15
20
|
|
16
21
|
|
@@ -31,6 +36,10 @@ describe ChronoModel::TimeMachine do
|
|
31
36
|
ts_eval(bar) { update_attributes! :name => 'new bar' }
|
32
37
|
}
|
33
38
|
|
39
|
+
let!(:baz) {
|
40
|
+
Baz.create :name => 'baz', :bar => bar
|
41
|
+
}
|
42
|
+
|
34
43
|
# Specs start here
|
35
44
|
#
|
36
45
|
describe '#as_of' do
|
@@ -110,6 +119,23 @@ describe ChronoModel::TimeMachine do
|
|
110
119
|
it { Defoo.unscoped.as_of(hidden.ts[0]).map(&:name).should == ['active 2', 'hidden 1'] }
|
111
120
|
it { Defoo.unscoped.as_of(hidden.ts[1]).map(&:name).should == ['active 2', 'hidden 2'] }
|
112
121
|
end
|
122
|
+
|
123
|
+
describe 'proxies from non-temporal models to temporal ones' do
|
124
|
+
it { baz.as_of(bar.ts[0]).name.should == 'baz' }
|
125
|
+
it { baz.as_of(bar.ts[1]).name.should == 'baz' }
|
126
|
+
it { baz.as_of(bar.ts[2]).name.should == 'baz' }
|
127
|
+
it { baz.as_of(bar.ts[3]).name.should == 'baz' }
|
128
|
+
|
129
|
+
it { baz.as_of(bar.ts[0]).bar.name.should == 'bar' }
|
130
|
+
it { baz.as_of(bar.ts[1]).bar.name.should == 'foo bar' }
|
131
|
+
it { baz.as_of(bar.ts[2]).bar.name.should == 'bar bar' }
|
132
|
+
it { baz.as_of(bar.ts[3]).bar.name.should == 'new bar' }
|
133
|
+
|
134
|
+
it { baz.as_of(bar.ts[0]).bar.foo.name.should == 'foo bar' }
|
135
|
+
it { baz.as_of(bar.ts[1]).bar.foo.name.should == 'foo bar' }
|
136
|
+
it { baz.as_of(bar.ts[2]).bar.foo.name.should == 'new foo' }
|
137
|
+
it { baz.as_of(bar.ts[3]).bar.foo.name.should == 'new foo' }
|
138
|
+
end
|
113
139
|
end
|
114
140
|
|
115
141
|
describe '#history' do
|
@@ -135,6 +161,82 @@ describe ChronoModel::TimeMachine do
|
|
135
161
|
it { foo.history[2].bars.all?(&:readonly?).should be_true }
|
136
162
|
it { bar.history.all? {|b| b.foo.readonly?}.should be_true }
|
137
163
|
end
|
164
|
+
|
165
|
+
describe 'allows a custom select list' do
|
166
|
+
it { foo.history.select(:id).first.attributes.keys.should == %w( id as_of_time ) }
|
167
|
+
end
|
168
|
+
|
169
|
+
describe 'does not add as_of_time when there are aggregates' do
|
170
|
+
it { foo.history.select('max (id)').to_sql.should_not =~ /as_of_time/ }
|
171
|
+
it { foo.history.select('max (id) as foo, min(id) as bar').first.attributes.keys.should == %w( foo bar ) }
|
172
|
+
end
|
173
|
+
|
174
|
+
describe 'orders by recorded_at, hid by default' do
|
175
|
+
it { foo.history.to_sql.should =~ /order by.*recorded_at,.*hid/i }
|
176
|
+
end
|
177
|
+
|
178
|
+
describe 'allows a custom order list' do
|
179
|
+
it { expect { foo.history.order('id') }.to_not raise_error }
|
180
|
+
it { foo.history.order('id').to_sql.should =~ /order by id/i }
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'with STI models' do
|
184
|
+
let!(:pub) {
|
185
|
+
pub = ts_eval { Publication.create! :title => 'wrong title' }
|
186
|
+
ts_eval(pub) { update_attributes! :title => 'correct title' }
|
187
|
+
}
|
188
|
+
|
189
|
+
it { pub.history.map(&:title).should == ['wrong title', 'correct title'] }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe '#pred' do
|
194
|
+
context 'on the first history entry' do
|
195
|
+
subject { foo.history.first.pred }
|
196
|
+
it { should be_nil }
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'on the second history entry' do
|
200
|
+
subject { foo.history.second.pred }
|
201
|
+
it { should == foo.history.first }
|
202
|
+
end
|
203
|
+
|
204
|
+
context 'on the last history entry' do
|
205
|
+
subject { foo.history.last.pred }
|
206
|
+
it { should == foo.history[foo.history.size - 2] }
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe '#succ' do
|
211
|
+
context 'on the first history entry' do
|
212
|
+
subject { foo.history.first.succ }
|
213
|
+
it { should == foo.history.second }
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'on the second history entry' do
|
217
|
+
subject { foo.history.second.succ }
|
218
|
+
it { should == foo.history.third }
|
219
|
+
end
|
220
|
+
|
221
|
+
context 'on the last history entry' do
|
222
|
+
subject { foo.history.last.succ }
|
223
|
+
it { should be_nil }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe '#first' do
|
228
|
+
subject { foo.history.sample.first }
|
229
|
+
it { should == foo.history.first }
|
230
|
+
end
|
231
|
+
|
232
|
+
describe '#last' do
|
233
|
+
subject { foo.history.sample.last }
|
234
|
+
it { should == foo.history.last }
|
235
|
+
end
|
236
|
+
|
237
|
+
describe '#record' do
|
238
|
+
subject { foo.history.sample.record }
|
239
|
+
it { should == foo }
|
138
240
|
end
|
139
241
|
|
140
242
|
describe '#historical?' do
|
@@ -197,32 +299,133 @@ describe ChronoModel::TimeMachine do
|
|
197
299
|
end
|
198
300
|
end
|
199
301
|
|
200
|
-
describe '#
|
302
|
+
describe '#timeline' do
|
303
|
+
split = lambda {|ts| ts.map!{|t| [t.to_i, t.usec]} }
|
304
|
+
|
201
305
|
timestamps_from = lambda {|*records|
|
202
|
-
records.map(&:history).flatten!.inject([]) {|ret, rec|
|
203
|
-
ret.concat [
|
306
|
+
ts = records.map(&:history).flatten!.inject([]) {|ret, rec|
|
307
|
+
ret.concat [
|
308
|
+
[rec.valid_from.to_i, rec.valid_from.usec + 2],
|
309
|
+
[rec.valid_to.to_i, rec.valid_to.usec + 2]
|
310
|
+
]
|
204
311
|
}.sort.uniq[0..-2]
|
205
312
|
}
|
206
313
|
|
207
314
|
describe 'on records having an :has_many relationship' do
|
208
|
-
|
315
|
+
describe 'by default returns timestamps of the record only' do
|
316
|
+
subject { split.call(foo.timeline) }
|
317
|
+
its(:size) { should == foo.ts.size }
|
318
|
+
it { should == timestamps_from.call(foo) }
|
319
|
+
end
|
209
320
|
|
210
|
-
describe 'returns timestamps
|
321
|
+
describe 'when asked, returns timestamps including the related objects' do
|
322
|
+
subject { split.call(foo.timeline(:with => :bars)) }
|
211
323
|
its(:size) { should == foo.ts.size + bar.ts.size }
|
212
|
-
it { should == timestamps_from.call(foo,
|
324
|
+
it { should == timestamps_from.call(foo, *foo.bars) }
|
213
325
|
end
|
214
326
|
end
|
215
327
|
|
216
|
-
describe 'on records
|
217
|
-
subject { bar.
|
328
|
+
describe 'on records using has_timeline :with' do
|
329
|
+
subject { split.call(bar.timeline) }
|
218
330
|
|
219
331
|
describe 'returns timestamps of the record and its associations' do
|
220
|
-
|
221
|
-
|
332
|
+
|
333
|
+
let!(:expected) do
|
334
|
+
creat = bar.history.first.valid_from
|
335
|
+
c_sec, c_usec = creat.to_i, creat.usec
|
336
|
+
|
337
|
+
timestamps_from.call(foo, bar).reject {|sec, usec|
|
338
|
+
sec < c_sec || ( sec == c_sec && usec < c_usec )
|
339
|
+
}
|
340
|
+
end
|
341
|
+
|
342
|
+
its(:size) { should == expected.size }
|
343
|
+
it { should == expected }
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
describe 'on non-temporal records using has_timeline :with' do
|
348
|
+
subject { split.call(baz.timeline) }
|
349
|
+
|
350
|
+
describe 'returns timestamps of its temporal associations' do
|
351
|
+
its(:size) { should == bar.ts.size }
|
352
|
+
it { should == timestamps_from.call(bar) }
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
describe '#last_changes' do
|
358
|
+
context 'on plain records' do
|
359
|
+
context 'having history' do
|
360
|
+
subject { bar.last_changes }
|
361
|
+
it { should == {'name' => ['bar bar', 'new bar']} }
|
362
|
+
end
|
363
|
+
|
364
|
+
context 'without history' do
|
365
|
+
let(:record) { Bar.create!(:name => 'foreveralone') }
|
366
|
+
subject { record.last_changes }
|
367
|
+
it { should be_nil }
|
368
|
+
after { record.destroy.history.delete_all } # UGLY
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
context 'on history records' do
|
373
|
+
context 'at the beginning of the timeline' do
|
374
|
+
subject { bar.history.first.last_changes }
|
375
|
+
it { should be_nil }
|
376
|
+
end
|
377
|
+
|
378
|
+
context 'in the middle of the timeline' do
|
379
|
+
subject { bar.history.second.last_changes }
|
380
|
+
it { should == {'name' => ['bar', 'foo bar']} }
|
222
381
|
end
|
223
382
|
end
|
224
383
|
end
|
225
384
|
|
385
|
+
describe '#changes_against' do
|
386
|
+
context 'can compare records against history' do
|
387
|
+
it { bar.changes_against(bar.history.first).should ==
|
388
|
+
{'name' => ['bar', 'new bar']} }
|
389
|
+
|
390
|
+
it { bar.changes_against(bar.history.second).should ==
|
391
|
+
{'name' => ['foo bar', 'new bar']} }
|
392
|
+
|
393
|
+
it { bar.changes_against(bar.history.third).should ==
|
394
|
+
{'name' => ['bar bar', 'new bar']} }
|
395
|
+
|
396
|
+
it { bar.changes_against(bar.history.last).should == {} }
|
397
|
+
end
|
398
|
+
|
399
|
+
context 'can compare history against history' do
|
400
|
+
it { bar.history.first.changes_against(bar.history.third).should ==
|
401
|
+
{'name' => ['bar bar', 'bar']} }
|
402
|
+
|
403
|
+
it { bar.history.second.changes_against(bar.history.third).should ==
|
404
|
+
{'name' => ['bar bar', 'foo bar']} }
|
405
|
+
|
406
|
+
it { bar.history.third.changes_against(bar.history.third).should == {} }
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
describe '#pred' do
|
411
|
+
context 'on records having history' do
|
412
|
+
subject { bar.pred }
|
413
|
+
its(:name) { should == 'bar bar' }
|
414
|
+
end
|
415
|
+
|
416
|
+
context 'when there is enough history' do
|
417
|
+
subject { bar.pred.pred.pred.pred }
|
418
|
+
its(:name) { should == 'bar' }
|
419
|
+
end
|
420
|
+
|
421
|
+
context 'when no history is recorded' do
|
422
|
+
let(:record) { Bar.create!(:name => 'quuuux') }
|
423
|
+
subject { record.pred }
|
424
|
+
it { should be_nil }
|
425
|
+
after { record.destroy.history.delete_all }
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
226
429
|
context do
|
227
430
|
let!(:history) { foo.history.first }
|
228
431
|
let!(:current) { foo }
|
@@ -239,7 +442,8 @@ describe ChronoModel::TimeMachine do
|
|
239
442
|
|
240
443
|
describe 'on current records' do
|
241
444
|
subject { current.public_send(attr) }
|
242
|
-
|
445
|
+
|
446
|
+
it { should be_nil }
|
243
447
|
end
|
244
448
|
}
|
245
449
|
}
|
metadata
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chrono_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.5.0.beta
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Marcello Barnaba
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-02-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
16
|
-
requirement: &
|
16
|
+
requirement: &77788870 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3.2'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *77788870
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: pg
|
27
|
-
requirement: &
|
27
|
+
requirement: &77787530 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *77787530
|
36
36
|
description: Give your models as-of date temporal extensions. Built entirely for PostgreSQL
|
37
37
|
>= 9.0
|
38
38
|
email:
|
@@ -55,7 +55,9 @@ files:
|
|
55
55
|
- lib/chrono_model/compatibility.rb
|
56
56
|
- lib/chrono_model/patches.rb
|
57
57
|
- lib/chrono_model/railtie.rb
|
58
|
+
- lib/chrono_model/time_gate.rb
|
58
59
|
- lib/chrono_model/time_machine.rb
|
60
|
+
- lib/chrono_model/utils.rb
|
59
61
|
- lib/chrono_model/version.rb
|
60
62
|
- spec/adapter_spec.rb
|
61
63
|
- spec/config.yml.example
|
@@ -83,9 +85,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
86
|
none: false
|
85
87
|
requirements:
|
86
|
-
- - ! '
|
88
|
+
- - ! '>'
|
87
89
|
- !ruby/object:Gem::Version
|
88
|
-
version:
|
90
|
+
version: 1.3.1
|
89
91
|
requirements: []
|
90
92
|
rubyforge_project:
|
91
93
|
rubygems_version: 1.8.10
|