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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 883f6c37f76cc8b57b2d7f6b59e1a5627592907e
4
- data.tar.gz: 8b35df17eb98dbddd3d0b5c5c046b7b9d7d00501
2
+ SHA256:
3
+ metadata.gz: 940172f2bad591657a9db2e6124c5b36ccab57cae41f8a0a85c2d473345b8f2c
4
+ data.tar.gz: 0060e673c40a25cd8d44f925483c85cb08dc794b15291705fa69e64c2b3d9eca
5
5
  SHA512:
6
- metadata.gz: 99fdb72cb9604d4566049199192b3982f5f44a088ad0f94335e677d8d847fd0a993390d6c17bb2381ec9c32c37c9fd378ba356ce7f9628b0e23aabe82b6702eb
7
- data.tar.gz: 7e0be5293bcea607ee1fc1300ad28b491bc9620a2e4ee965f3763e7dadb509f702019fbbc27b254ba41243bd0e35ac4cf9131ab79beb561f87a814de2483b604
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)
@@ -462,7 +462,13 @@ module ChronoModel
462
462
  end
463
463
 
464
464
  def chrono_setup!
465
- chrono_create_schemas
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 chrono_create_schemas
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
- _on_temporal_schema { tables }.each do |table_name|
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 ActiveRecord::Base.connection.chrono_setup!"
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
  #
@@ -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
- ActiveRecord::Associations::Preloader.new(as_of_time: as_of_time)
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
 
@@ -1,3 +1,3 @@
1
1
  module ChronoModel
2
- VERSION = "0.13.0"
2
+ VERSION = "0.13.1"
3
3
  end
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
 
@@ -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 /as_of_time/ }
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 /order by .+"recorded_at", .+"hid"/i }
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
- ts = records.map(&:history).flatten!.inject([]) {|ret, rec|
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.0
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: 2018-10-29 00:00:00.000000000 Z
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.5.2.1
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