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,46 +0,0 @@
1
- require 'spec_helper'
2
- require 'support/time_machine/structure'
3
-
4
- describe 'models with counter cache' do
5
- include ChronoTest::TimeMachine::Helpers
6
-
7
- adapter.create_table 'sections', temporal: true, no_journal: %w( articles_count ) do |t|
8
- t.string :name
9
- t.integer :articles_count, default: 0
10
- end
11
-
12
- adapter.create_table 'articles', temporal: true do |t|
13
- t.string :title
14
- t.references :section
15
- end
16
-
17
- class ::Section < ActiveRecord::Base
18
- include ChronoModel::TimeMachine
19
-
20
- has_many :articles
21
- end
22
-
23
- class ::Article < ActiveRecord::Base
24
- include ChronoModel::TimeMachine
25
-
26
- belongs_to :section, counter_cache: true
27
- end
28
-
29
- describe 'are not subject to race condition if no_journal is set on the counter cache column' do
30
- specify do
31
- section = Section.create!
32
-
33
- expect(section.articles_count).to eq(0)
34
- Article.create!(section_id: section.id)
35
- expect(section.reload.articles_count).to eq(1)
36
-
37
- num_threads = 10
38
-
39
- expect {
40
- Array.new(num_threads).map do
41
- Thread.new { Article.create!(section_id: section.id) }
42
- end.each(&:join)
43
- }.to_not raise_error
44
- end
45
- end
46
- end
@@ -1,37 +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
- # Set up database structure
8
- #
9
- adapter.create_table 'defoos', :temporal => true do |t|
10
- t.string :name
11
- t.boolean :active
12
- end
13
-
14
- class ::Defoo < ActiveRecord::Base
15
- include ChronoModel::TimeMachine
16
-
17
- default_scope proc { where(:active => true) }
18
- end
19
-
20
- active = ts_eval { Defoo.create! :name => 'active 1', :active => true }
21
- ts_eval(active) { update_attributes! :name => 'active 2' }
22
-
23
- hidden = ts_eval { Defoo.create! :name => 'hidden 1', :active => false }
24
- ts_eval(hidden) { update_attributes! :name => 'hidden 2' }
25
-
26
- describe 'it honors default_scopes' do
27
- it { expect(Defoo.as_of(active.ts[0]).map(&:name)).to eq ['active 1'] }
28
- it { expect(Defoo.as_of(active.ts[1]).map(&:name)).to eq ['active 2'] }
29
- it { expect(Defoo.as_of(hidden.ts[0]).map(&:name)).to eq ['active 2'] }
30
- it { expect(Defoo.as_of(hidden.ts[1]).map(&:name)).to eq ['active 2'] }
31
-
32
- it { expect(Defoo.unscoped.as_of(active.ts[0]).map(&:name)).to eq ['active 1'] }
33
- it { expect(Defoo.unscoped.as_of(active.ts[1]).map(&:name)).to eq ['active 2'] }
34
- it { expect(Defoo.unscoped.as_of(hidden.ts[0]).map(&:name)).to eq ['active 2', 'hidden 1'] }
35
- it { expect(Defoo.unscoped.as_of(hidden.ts[1]).map(&:name)).to eq ['active 2', 'hidden 2'] }
36
- end
37
- end
@@ -1,104 +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 '.history' do
8
- let(:foo_history) {
9
- ['foo', 'foo bar', 'new foo', 'foo 0', 'foo 1']
10
- }
11
-
12
- let(:bar_history) {
13
- ['bar', 'foo bar', 'bar bar', 'new bar', 'bar 0', 'bar 1']
14
- }
15
-
16
- it { expect(Foo.history.all.map(&:name)).to eq foo_history }
17
- it { expect(Bar.history.all.map(&:name)).to eq bar_history }
18
-
19
- it { expect(Foo.history.first).to be_a(Foo::History) }
20
- it { expect(Bar.history.first).to be_a(Bar::History) }
21
- end
22
-
23
-
24
- describe '#history' do
25
- describe 'returns historical instances' do
26
- it { expect($t.foo.history.size).to eq(3) }
27
- it { expect($t.foo.history.map(&:name)).to eq ['foo', 'foo bar', 'new foo'] }
28
-
29
- it { expect($t.bar.history.size).to eq(4) }
30
- it { expect($t.bar.history.map(&:name)).to eq ['bar', 'foo bar', 'bar bar', 'new bar'] }
31
- end
32
-
33
- describe 'does not return read only records' do
34
- it { expect($t.foo.history.all?(&:readonly?)).to be(false) }
35
- it { expect($t.bar.history.all?(&:readonly?)).to be(false) }
36
- end
37
-
38
- describe 'takes care of associated records' do
39
- subject { $t.foo.history.map {|f| f.bars.first.try(:name)} }
40
- it { is_expected.to eq [nil, 'foo bar', 'new bar'] }
41
- end
42
-
43
- describe 'does not return read only associated records' do
44
- it { expect($t.foo.history[2].bars.all?(&:readonly?)).to_not be(true) }
45
- it { expect($t.bar.history.all? {|b| b.foo.readonly?}).to_not be(true) }
46
- end
47
-
48
- describe 'allows a custom select list' do
49
- it { expect($t.foo.history.select(:id).first.attributes.keys).to eq %w( id ) }
50
- end
51
-
52
- describe 'does not add as_of_time when there are aggregates' do
53
- it { expect($t.foo.history.select('max(id)').to_sql).to_not match(/as_of_time/) }
54
-
55
- it { expect($t.foo.history.except(:order).select('max(id) as foo, min(id) as bar').group('id').first.attributes.keys).to eq %w( id foo bar ) }
56
- end
57
-
58
- context '.sorted' do
59
- describe 'orders by recorded_at, hid' do
60
- it { expect($t.foo.history.sorted.to_sql).to match(/order by .+"recorded_at" ASC, .+"hid" ASC/i) }
61
- end
62
- end
63
- end
64
-
65
-
66
- describe '#current_version' do
67
- describe 'on plain records' do
68
- subject { $t.foo.current_version }
69
- it { is_expected.to eq $t.foo }
70
- end
71
-
72
- describe 'from #as_of' do
73
- subject { $t.foo.as_of(Time.now) }
74
- it { is_expected.to eq $t.foo }
75
- end
76
-
77
- describe 'on historical records' do
78
- subject { $t.foo.history.sample.current_version }
79
- it { is_expected.to eq $t.foo }
80
- end
81
- end
82
-
83
-
84
- describe '#historical?' do
85
- subject { record.historical? }
86
-
87
- describe 'on plain records' do
88
- let(:record) { $t.foo }
89
- it { is_expected.to be(false) }
90
- end
91
-
92
- describe 'on historical records' do
93
- describe 'from #history' do
94
- let(:record) { $t.foo.history.first }
95
- it { is_expected.to be(true) }
96
- end
97
-
98
- describe 'from #as_of' do
99
- let(:record) { $t.foo.as_of(Time.now) }
100
- it { is_expected.to be(true) }
101
- end
102
- end
103
- end
104
- end
@@ -1,27 +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 'does not interfere with AR standard behaviour' do
8
- let(:all_foos) { [ $t.foo ] + $t.foos }
9
- let(:all_bars) { [ $t.bar ] + $t.bars }
10
-
11
- it { expect(Foo.count).to eq all_foos.size }
12
- it { expect(Bar.count).to eq all_bars.size }
13
-
14
- it { expect(Foo.includes(bars: :sub_bars)).to eq all_foos }
15
- it { expect(Foo.includes(:bars).preload(bars: :sub_bars)).to eq all_foos }
16
-
17
- it { expect(Foo.includes(:bars).first.name).to eq 'new foo' }
18
- it { expect(Foo.includes(:bars).as_of($t.foo.ts[0]).first.name).to eq 'foo' }
19
-
20
- it { expect(Foo.joins(:bars).map(&:bars).flatten).to eq all_bars }
21
- it { expect(Foo.joins(:bars).first.bars.joins(:sub_bars).first.name).to eq 'new bar' }
22
-
23
- it { expect(Foo.joins(bars: :sub_bars).first.bars.joins(:sub_bars).first.sub_bars.first.name).to eq 'new sub-bar' }
24
-
25
- it { expect(Foo.first.bars.includes(:sub_bars)).to eq [ $t.bar ] }
26
- end
27
- end
@@ -1,84 +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 '#save' do
8
- subject { $t.bar.history.first }
9
-
10
- it do
11
- with_revert do
12
- subject.name = 'modified bar history'
13
- subject.save
14
- subject.reload
15
-
16
- is_expected.to be_a(Bar::History)
17
- expect(subject.name).to eq 'modified bar history'
18
- end
19
- end
20
- end
21
-
22
- describe '#save!' do
23
- subject { $t.bar.history.second }
24
-
25
- it do
26
- with_revert do
27
- subject.name = 'another modified bar history'
28
- subject.save
29
- subject.reload
30
-
31
- is_expected.to be_a(Bar::History)
32
- expect(subject.name).to eq 'another modified bar history'
33
- end
34
- end
35
- end
36
-
37
- describe '#destroy' do
38
- describe 'on historical records' do
39
- subject { $t.foo.history.first.destroy }
40
- it { expect { subject }.to raise_error(ActiveRecord::ReadOnlyRecord) }
41
- end
42
-
43
- describe 'on current records' do
44
- rec = nil
45
- before(:all) do
46
- rec = ts_eval { Foo.create!(:name => 'alive foo', :fooity => 42) }
47
- ts_eval(rec) { update_attributes!(:name => 'dying foo') }
48
- end
49
- after(:all) do
50
- rec.history.delete_all
51
- end
52
-
53
- subject { rec.destroy }
54
-
55
- it { expect { subject }.to_not raise_error }
56
- it { expect { rec.reload }.to raise_error(ActiveRecord::RecordNotFound) }
57
-
58
- describe 'does not delete its history' do
59
- subject { record.name }
60
-
61
- context do
62
- let(:record) { rec.as_of(rec.ts.first) }
63
- it { is_expected.to eq 'alive foo' }
64
- end
65
-
66
- context do
67
- let(:record) { rec.as_of(rec.ts.last) }
68
- it { is_expected.to eq 'dying foo' }
69
- end
70
-
71
- context do
72
- let(:record) { Foo.as_of(rec.ts.first).where(:fooity => 42).first }
73
- it { is_expected.to eq 'alive foo' }
74
- end
75
-
76
- context do
77
- subject { Foo.history.where(:fooity => 42).map(&:name) }
78
- it { is_expected.to eq ['alive foo', 'dying foo'] }
79
- end
80
- end
81
- end
82
- end
83
-
84
- end
@@ -1,46 +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
- adapter.create_table 'plains' do |t|
8
- t.string :foo
9
- end
10
-
11
- class ::Plain < ActiveRecord::Base
12
- end
13
-
14
- describe '.chrono?' do
15
- subject { model.chrono? }
16
-
17
- context 'on a temporal model' do
18
- let(:model) { Foo }
19
- it { is_expected.to be(true) }
20
- end
21
-
22
- context 'on a plain model' do
23
- let(:model) { Plain }
24
- it { is_expected.to be(false) }
25
- end
26
- end
27
-
28
- describe '.history?' do
29
- subject { model.history? }
30
-
31
- context 'on a temporal parent model' do
32
- let(:model) { Foo }
33
- it { is_expected.to be(false) }
34
- end
35
-
36
- context 'on a temporal history model' do
37
- let(:model) { Foo::History }
38
- it { is_expected.to be(true) }
39
- end
40
-
41
- context 'on a plain model' do
42
- let(:model) { Plain }
43
- it { expect { subject }.to raise_error(NoMethodError) }
44
- end
45
- end
46
- end
@@ -1,74 +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 '#pred' do
8
- context 'on the first history entry' do
9
- subject { $t.foo.history.first.pred }
10
- it { is_expected.to be(nil) }
11
- end
12
-
13
- context 'on the second history entry' do
14
- subject { $t.foo.history.second.pred }
15
- it { is_expected.to eq $t.foo.history.first }
16
- end
17
-
18
- context 'on the last history entry' do
19
- subject { $t.foo.history.last.pred }
20
- it { is_expected.to eq $t.foo.history[$t.foo.history.size - 2] }
21
- end
22
-
23
- context 'on records having history' do
24
- subject { $t.bar.pred }
25
- it { expect(subject.name).to eq 'bar bar' }
26
- end
27
-
28
- context 'when there is enough history' do
29
- subject { $t.bar.pred.pred.pred.pred }
30
- it { expect(subject.name).to eq 'bar' }
31
- end
32
-
33
- context 'when no history is recorded' do
34
- let(:record) { Bar.create!(:name => 'quuuux') }
35
-
36
- subject { record.pred }
37
-
38
- it { is_expected.to be(nil) }
39
-
40
- after { record.destroy.history.delete_all }
41
- end
42
- end
43
-
44
- describe '#succ' do
45
- context 'on the first history entry' do
46
- subject { $t.foo.history.first.succ }
47
-
48
- it { is_expected.to eq $t.foo.history.second }
49
- end
50
-
51
- context 'on the second history entry' do
52
- subject { $t.foo.history.second.succ }
53
-
54
- it { is_expected.to eq $t.foo.history.third }
55
- end
56
-
57
- context 'on the last history entry' do
58
- subject { $t.foo.history.last.succ }
59
-
60
- it { is_expected.to be(nil) }
61
- end
62
- end
63
-
64
- describe '#first' do
65
- subject { $t.foo.history.sample.first }
66
- it { is_expected.to eq $t.foo.history.first }
67
- end
68
-
69
- describe '#last' do
70
- subject { $t.foo.history.sample.last }
71
- it { is_expected.to eq $t.foo.history.last }
72
- end
73
-
74
- end
@@ -1,100 +0,0 @@
1
- require 'spec_helper'
2
- require 'support/time_machine/structure'
3
-
4
- # STI cases
5
- #
6
- # - https://github.com/ifad/chronomodel/issues/5
7
- # - https://github.com/ifad/chronomodel/issues/47
8
- #
9
- describe 'models with STI' do
10
- include ChronoTest::TimeMachine::Helpers
11
-
12
- adapter.create_table 'elements', :temporal => true do |t|
13
- t.string :title
14
- t.string :type
15
- end
16
-
17
- class ::Element < ActiveRecord::Base
18
- include ChronoModel::TimeMachine
19
- end
20
-
21
- class ::Publication < Element
22
- end
23
-
24
- describe '.descendants' do
25
- subject { Element.descendants }
26
-
27
- it { is_expected.to_not include(Element::History) }
28
- it { is_expected.to include(Publication) }
29
- end
30
-
31
- describe '.descendants_with_history' do
32
- subject { Element.descendants_with_history }
33
-
34
- it { is_expected.to include(Element::History) }
35
- it { is_expected.to include(Publication) }
36
- end
37
-
38
- describe 'timeline' do
39
- let(:publication) do
40
- pub = ts_eval { Publication.create! :title => 'wrong title' }
41
- ts_eval(pub) { update_attributes! :title => 'correct title' }
42
-
43
- pub
44
- end
45
-
46
- it { expect(publication.history.map(&:title)).to eq ['wrong title', 'correct title'] }
47
- end
48
-
49
- describe 'identity' do
50
- adapter.create_table 'animals', temporal: true do |t|
51
- t.string :type
52
- end
53
-
54
- class ::Animal < ActiveRecord::Base
55
- include ChronoModel::TimeMachine
56
- end
57
-
58
- class ::Dog < Animal
59
- end
60
-
61
- class ::Goat < Animal
62
- end
63
-
64
- before do
65
- Dog.create!
66
- @later = Time.new
67
- Goat.create!
68
- end
69
-
70
- after do
71
- tables = ['temporal.animals', 'history.animals']
72
- ActiveRecord::Base.connection.execute "truncate #{tables.join(', ')} cascade"
73
- end
74
-
75
- specify "select" do
76
- expect(Animal.first).to be_a(Animal)
77
- expect(Animal.as_of(@later).first).to be_a(Animal)
78
-
79
- expect(Animal.where(type: 'Dog').first).to be_a(Dog)
80
- expect(Dog.first).to be_a(Dog)
81
- expect(Dog.as_of(@later).first).to be_a(Dog)
82
-
83
- expect(Animal.where(type: 'Goat').first).to be_a(Goat)
84
- expect(Goat.first).to be_a(Goat)
85
- expect(Goat.as_of(@later).first).to be(nil)
86
- expect(Goat.as_of(Time.now).first).to be_a(Goat)
87
- end
88
-
89
- specify "count" do
90
- expect(Animal.count).to eq(2)
91
- expect(Animal.as_of(@later).count).to eq(1)
92
-
93
- expect(Dog.count).to eq(1)
94
- expect(Dog.as_of(@later).count).to eq(1)
95
-
96
- expect(Goat.count).to eq(1)
97
- expect(Goat.as_of(@later).count).to eq(0)
98
- end
99
- end
100
- end