chrono_model 0.13.0 → 0.13.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 +5 -5
- data/README.md +17 -1
- data/lib/chrono_model.rb +10 -0
- data/lib/chrono_model/adapter.rb +53 -15
- data/lib/chrono_model/patches.rb +3 -2
- data/lib/chrono_model/version.rb +1 -1
- data/spec/spec_helper.rb +4 -0
- data/spec/time_machine_spec.rb +29 -8
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 940172f2bad591657a9db2e6124c5b36ccab57cae41f8a0a85c2d473345b8f2c
|
4
|
+
data.tar.gz: 0060e673c40a25cd8d44f925483c85cb08dc794b15291705fa69e64c2b3d9eca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b850d300fe412425c472f04e022ff89ca9ab9d299f65c52ac65a4ccfd401828301d524868b57f5490e84d32d7baf4dd2c446ba9cf9ea6ab780910ee9ced80ad5
|
7
|
+
data.tar.gz: 26f7751a0ff8dc234f32d391a6cb84a853edd2213547613db3d413cceaba42475f12537a7f5e647d6c307f9016b445c16f956a5f912542c2c83a19b3de4f31e0
|
data/README.md
CHANGED
@@ -257,7 +257,23 @@ more information.
|
|
257
257
|
|
258
258
|
## History manipulation
|
259
259
|
|
260
|
-
History objects can be changed and `.save`d just like any other record.
|
260
|
+
History objects can be changed and `.save`d just like any other record. They
|
261
|
+
cannot be deleted.
|
262
|
+
|
263
|
+
## Upgrading
|
264
|
+
|
265
|
+
ChronoModel currently performs upgrades by dropping and re-creating the views
|
266
|
+
that give access to current data. If you have built other database objects on
|
267
|
+
these views, the upgrade cannot be performed automatically as the dependant
|
268
|
+
objects must be dropped first.
|
269
|
+
|
270
|
+
When booting, ChronoModel will issue a warning in your logs about the need of
|
271
|
+
a structure upgrade. Structure usually changes across versions. In this case,
|
272
|
+
you need to set up a rake task that drops your dependant objects, runs
|
273
|
+
ChronoModel.upgrade! and then re-creates them.
|
274
|
+
|
275
|
+
A migration system should be introduced, but it is seen as overkill for now,
|
276
|
+
given that usually database objects have creation and dropping scripts.
|
261
277
|
|
262
278
|
|
263
279
|
## Running tests
|
data/lib/chrono_model.rb
CHANGED
@@ -8,6 +8,16 @@ require 'chrono_model/utils'
|
|
8
8
|
module ChronoModel
|
9
9
|
class Error < ActiveRecord::ActiveRecordError #:nodoc:
|
10
10
|
end
|
11
|
+
|
12
|
+
def self.upgrade!
|
13
|
+
connection = ActiveRecord::Base.connection
|
14
|
+
|
15
|
+
unless connection.is_a?(ChronoModel::Adapter)
|
16
|
+
raise ChronoModel::Error, "This database connection is not a ChronoModel::Adapter"
|
17
|
+
end
|
18
|
+
|
19
|
+
connection.chrono_upgrade!
|
20
|
+
end
|
11
21
|
end
|
12
22
|
|
13
23
|
if defined?(Rails)
|
data/lib/chrono_model/adapter.rb
CHANGED
@@ -462,7 +462,13 @@ module ChronoModel
|
|
462
462
|
end
|
463
463
|
|
464
464
|
def chrono_setup!
|
465
|
-
|
465
|
+
chrono_ensure_schemas
|
466
|
+
|
467
|
+
chrono_upgrade_warning
|
468
|
+
end
|
469
|
+
|
470
|
+
def chrono_upgrade!
|
471
|
+
chrono_ensure_schemas
|
466
472
|
|
467
473
|
chrono_upgrade_structure!
|
468
474
|
end
|
@@ -541,37 +547,69 @@ module ChronoModel
|
|
541
547
|
private
|
542
548
|
# Create the temporal and history schemas, unless they already exist
|
543
549
|
#
|
544
|
-
def
|
550
|
+
def chrono_ensure_schemas
|
545
551
|
[TEMPORAL_SCHEMA, HISTORY_SCHEMA].each do |schema|
|
546
552
|
execute "CREATE SCHEMA #{schema}" unless schema_exists?(schema)
|
547
553
|
end
|
548
554
|
end
|
549
555
|
|
556
|
+
# Locate tables needing a structure upgrade
|
557
|
+
#
|
558
|
+
def chrono_tables_needing_upgrade
|
559
|
+
tables = { }
|
560
|
+
|
561
|
+
_on_temporal_schema { self.tables }.each do |table_name|
|
562
|
+
next unless is_chrono?(table_name)
|
563
|
+
metadata = chrono_metadata_for(table_name)
|
564
|
+
version = metadata['chronomodel']
|
565
|
+
|
566
|
+
if version.blank?
|
567
|
+
tables[table_name] = { version: nil, priority: 'CRITICAL' }
|
568
|
+
elsif version != VERSION
|
569
|
+
tables[table_name] = { version: version, priority: 'LOW' }
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
return tables
|
574
|
+
end
|
575
|
+
|
576
|
+
# Emit a warning about tables needing an upgrade
|
577
|
+
#
|
578
|
+
def chrono_upgrade_warning
|
579
|
+
upgrade = chrono_tables_needing_upgrade.map do |table, desc|
|
580
|
+
"#{table} - priority: #{desc[:priority]}"
|
581
|
+
end.join('; ')
|
582
|
+
|
583
|
+
return if upgrade.empty?
|
584
|
+
|
585
|
+
logger.warn "ChronoModel: There are tables needing a structure upgrade, and ChronoModel structures need to be recreated."
|
586
|
+
logger.warn "ChronoModel: Please run ChronoModel.upgrade! to attempt the upgrade. If you have dependant database objects"
|
587
|
+
logger.warn "ChronoModel: the upgrade will fail and you have to drop the dependent objects, run .upgrade! and create them"
|
588
|
+
logger.warn "ChronoModel: again. Sorry. Some features or the whole library may not work correctly until upgrade is complete."
|
589
|
+
logger.warn "ChronoModel: Tables pending upgrade: #{upgrade}"
|
590
|
+
end
|
591
|
+
|
550
592
|
# Upgrades existing structure for each table, if required.
|
551
|
-
# TODO: allow upgrades from pre-0.6 structure with box() and stuff.
|
552
593
|
#
|
553
594
|
def chrono_upgrade_structure!
|
554
595
|
transaction do
|
555
|
-
current = VERSION
|
556
596
|
|
557
|
-
|
558
|
-
next unless is_chrono?(table_name)
|
559
|
-
metadata = chrono_metadata_for(table_name)
|
560
|
-
version = metadata['chronomodel']
|
597
|
+
chrono_tables_needing_upgrade.each do |table_name, desc|
|
561
598
|
|
562
|
-
if version.blank?
|
599
|
+
if desc[:version].blank?
|
600
|
+
logger.info "ChronoModel: Upgrading legacy table #{table_name} to #{VERSION}"
|
563
601
|
upgrade_from_legacy(table_name)
|
602
|
+
logger.info "ChronoModel: legacy #{table_name} upgrade complete"
|
603
|
+
else
|
604
|
+
logger.info "ChronoModel: upgrading #{table_name} from #{version} to #{VERSION}"
|
605
|
+
chrono_create_view_for(table_name)
|
606
|
+
logger.info "ChronoModel: #{table_name} upgrade complete"
|
564
607
|
end
|
565
608
|
|
566
|
-
next if version == current
|
567
|
-
|
568
|
-
logger.info "ChronoModel: upgrading #{table_name} from #{version} to #{current}"
|
569
|
-
chrono_create_view_for(table_name)
|
570
|
-
logger.info "ChronoModel: upgrade complete"
|
571
609
|
end
|
572
610
|
end
|
573
611
|
rescue => e
|
574
|
-
message = "ChronoModel structure upgrade failed: #{e.message}. Please drop dependent objects and then run
|
612
|
+
message = "ChronoModel structure upgrade failed: #{e.message}. Please drop dependent objects first and then run ChronoModel.upgrade! again."
|
575
613
|
|
576
614
|
# Quite important, output it also to stderr.
|
577
615
|
#
|
data/lib/chrono_model/patches.rb
CHANGED
@@ -88,7 +88,8 @@ module ChronoModel
|
|
88
88
|
# Build a preloader at the +as_of_time+ of this relation
|
89
89
|
#
|
90
90
|
def build_preloader
|
91
|
-
|
91
|
+
options = as_of_time ? {as_of_time: as_of_time} : {}
|
92
|
+
ActiveRecord::Associations::Preloader.new(options)
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
@@ -157,7 +158,7 @@ module ChronoModel
|
|
157
158
|
def build_scope
|
158
159
|
scope = super
|
159
160
|
|
160
|
-
if preload_scope.respond_to?(:as_of_time)
|
161
|
+
if preload_scope.respond_to?(:as_of_time) && preload_scope.as_of_time
|
161
162
|
scope = scope.as_of(preload_scope.as_of_time)
|
162
163
|
end
|
163
164
|
|
data/lib/chrono_model/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'simplecov'
|
4
4
|
SimpleCov.start
|
5
5
|
|
6
|
+
require 'byebug'
|
7
|
+
|
6
8
|
require 'chrono_model'
|
7
9
|
|
8
10
|
require 'support/connection'
|
@@ -13,6 +15,8 @@ require 'support/matchers/index'
|
|
13
15
|
require 'support/matchers/function'
|
14
16
|
require 'support/aruba'
|
15
17
|
|
18
|
+
puts "Testing against Active Record #{ActiveRecord::VERSION::STRING} with Arel #{Arel::VERSION}"
|
19
|
+
|
16
20
|
# Rails 5 returns a True/FalseClass
|
17
21
|
AR_TRUE, AR_FALSE = ActiveRecord::VERSION::MAJOR == 4 ? ['t', 'f'] : [true, false]
|
18
22
|
|
data/spec/time_machine_spec.rb
CHANGED
@@ -28,6 +28,10 @@ describe ChronoModel::TimeMachine do
|
|
28
28
|
ts_eval(subbar) { update_attributes! :name => 'sub-bar sub-bar' }
|
29
29
|
ts_eval(subbar) { update_attributes! :name => 'new sub-bar' }
|
30
30
|
|
31
|
+
#
|
32
|
+
foos = Array.new(2) {|i| ts_eval { Foo.create! :name => "foo #{i}" } }
|
33
|
+
bars = Array.new(2) {|i| ts_eval { Bar.create! :name => "bar #{i}", :foo => foos[i] } }
|
34
|
+
|
31
35
|
#
|
32
36
|
baz = Baz.create :name => 'baz', :bar => bar
|
33
37
|
|
@@ -90,6 +94,28 @@ describe ChronoModel::TimeMachine do
|
|
90
94
|
) }
|
91
95
|
end
|
92
96
|
|
97
|
+
describe 'does not interfere with AR standard behaviour' do
|
98
|
+
all_foos = [ foo ] + foos
|
99
|
+
all_bars = [ bar ] + bars
|
100
|
+
|
101
|
+
it { expect(Foo.count).to eq all_foos.size }
|
102
|
+
it { expect(Bar.count).to eq all_bars.size }
|
103
|
+
|
104
|
+
it { expect(Foo.includes(bars: :sub_bars)).to eq all_foos }
|
105
|
+
it { expect(Foo.includes(:bars).preload(bars: :sub_bars)).to eq all_foos }
|
106
|
+
|
107
|
+
it { expect(Foo.includes(:bars).first.name).to eq 'new foo' }
|
108
|
+
it { expect(Foo.includes(:bars).as_of(foo.ts[0]).first.name).to eq 'foo' }
|
109
|
+
|
110
|
+
it { expect(Foo.joins(:bars).map(&:bars).flatten).to eq all_bars }
|
111
|
+
it { expect(Foo.joins(:bars).first.bars.joins(:sub_bars).first.name).to eq 'new bar' }
|
112
|
+
|
113
|
+
it { expect(Foo.joins(bars: :sub_bars).first.bars.joins(:sub_bars).first.sub_bars.first.name).to eq 'new sub-bar' }
|
114
|
+
|
115
|
+
it { expect(Foo.first.bars.includes(:sub_bars)).to eq [ bar ] }
|
116
|
+
|
117
|
+
end
|
118
|
+
|
93
119
|
describe '#as_of' do
|
94
120
|
describe 'accepts a Time instance' do
|
95
121
|
it { expect(foo.as_of(Time.now).name).to eq 'new foo' }
|
@@ -264,7 +290,7 @@ describe ChronoModel::TimeMachine do
|
|
264
290
|
end
|
265
291
|
|
266
292
|
describe 'does not add as_of_time when there are aggregates' do
|
267
|
-
it { expect(foo.history.select('max(id)').to_sql).to_not match
|
293
|
+
it { expect(foo.history.select('max(id)').to_sql).to_not match(/as_of_time/) }
|
268
294
|
|
269
295
|
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 ) }
|
270
296
|
end
|
@@ -278,7 +304,7 @@ describe ChronoModel::TimeMachine do
|
|
278
304
|
|
279
305
|
context '.sorted' do
|
280
306
|
describe 'orders by recorded_at, hid' do
|
281
|
-
it { expect(foo.history.sorted.to_sql).to match
|
307
|
+
it { expect(foo.history.sorted.to_sql).to match(/order by .+"recorded_at", .+"hid"/i) }
|
282
308
|
end
|
283
309
|
end
|
284
310
|
end
|
@@ -416,7 +442,7 @@ describe ChronoModel::TimeMachine do
|
|
416
442
|
split = lambda {|ts| ts.map!{|t| [t.to_i, t.usec]} }
|
417
443
|
|
418
444
|
timestamps_from = lambda {|*records|
|
419
|
-
|
445
|
+
records.map(&:history).flatten!.inject([]) {|ret, rec|
|
420
446
|
ret.push [rec.valid_from.to_i, rec.valid_from.usec] if rec.try(:valid_from)
|
421
447
|
ret.push [rec.valid_to .to_i, rec.valid_to .usec] if rec.try(:valid_to)
|
422
448
|
ret
|
@@ -570,11 +596,6 @@ describe ChronoModel::TimeMachine do
|
|
570
596
|
|
571
597
|
# Class methods
|
572
598
|
context do
|
573
|
-
foos = Array.new(2) {|i| ts_eval { Foo.create! :name => "foo #{i}" } }
|
574
|
-
bars = Array.new(2) {|i| ts_eval { Bar.create! :name => "bar #{i}", :foo => foos[i] } }
|
575
|
-
|
576
|
-
after(:all) { foos.each(&:destroy); bars.each(&:destroy) }
|
577
|
-
|
578
599
|
describe '.as_of' do
|
579
600
|
it { expect(Foo.as_of(1.month.ago)).to eq [] }
|
580
601
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chrono_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.13.
|
4
|
+
version: 0.13.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcello Barnaba
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2019-04-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -278,7 +278,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
278
278
|
version: '0'
|
279
279
|
requirements: []
|
280
280
|
rubyforge_project:
|
281
|
-
rubygems_version: 2.
|
281
|
+
rubygems_version: 2.7.6.2
|
282
282
|
signing_key:
|
283
283
|
specification_version: 4
|
284
284
|
summary: Temporal extensions (SCD Type II) for Active Record
|