chrono_model 1.2.0 → 1.2.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -1
  3. data/lib/chrono_model/time_machine.rb +1 -1
  4. data/lib/chrono_model/time_machine/history_model.rb +4 -0
  5. data/lib/chrono_model/version.rb +1 -1
  6. data/spec/chrono_model/adapter/base_spec.rb +157 -0
  7. data/spec/chrono_model/adapter/ddl_spec.rb +243 -0
  8. data/spec/chrono_model/adapter/indexes_spec.rb +72 -0
  9. data/spec/chrono_model/adapter/migrations_spec.rb +312 -0
  10. data/spec/chrono_model/history_models_spec.rb +32 -0
  11. data/spec/chrono_model/time_machine/as_of_spec.rb +188 -0
  12. data/spec/chrono_model/time_machine/changes_spec.rb +50 -0
  13. data/spec/chrono_model/{adapter → time_machine}/counter_cache_race_spec.rb +2 -2
  14. data/spec/chrono_model/time_machine/default_scope_spec.rb +37 -0
  15. data/spec/chrono_model/time_machine/history_spec.rb +104 -0
  16. data/spec/chrono_model/time_machine/keep_cool_spec.rb +27 -0
  17. data/spec/chrono_model/time_machine/manipulations_spec.rb +84 -0
  18. data/spec/chrono_model/time_machine/model_identification_spec.rb +46 -0
  19. data/spec/chrono_model/time_machine/sequence_spec.rb +74 -0
  20. data/spec/chrono_model/time_machine/sti_spec.rb +100 -0
  21. data/spec/chrono_model/{time_query_spec.rb → time_machine/time_query_spec.rb} +22 -5
  22. data/spec/chrono_model/time_machine/timeline_spec.rb +63 -0
  23. data/spec/chrono_model/time_machine/timestamps_spec.rb +43 -0
  24. data/spec/chrono_model/time_machine/transactions_spec.rb +69 -0
  25. data/spec/support/adapter/helpers.rb +53 -0
  26. data/spec/support/adapter/structure.rb +44 -0
  27. data/spec/support/time_machine/helpers.rb +47 -0
  28. data/spec/support/time_machine/structure.rb +111 -0
  29. metadata +48 -14
  30. data/spec/chrono_model/adapter/sti_bug_spec.rb +0 -49
  31. data/spec/chrono_model/adapter_spec.rb +0 -788
  32. data/spec/chrono_model/time_machine_spec.rb +0 -749
  33. data/spec/support/helpers.rb +0 -198
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15a81b8b75b7b77c03e9e750db3122cc0107e238696882bd67f2fe8fc7647274
4
- data.tar.gz: 599dd55436b014a5db0ce7c1b003aa34ce15e3e12f08dac3e5cc0c5549823a8e
3
+ metadata.gz: 21c2181fc18f6dd3a5bdd89940eae284b340427bc81a4f024b6ad0686c146598
4
+ data.tar.gz: e7afa88021f63cd42e160a800f66e84bff1ba90f706c209df436059f70ae04e3
5
5
  SHA512:
6
- metadata.gz: 2ba2dc61cc6a62e573dea411005abf071e8010bcd249b05f274afefc756b1ac85b47e6469a6c9988f3b97c3f340891606bbe32d97cd75dfc4fcf0c1f99148805
7
- data.tar.gz: 2cce42ff59e611beb10929f8484b8a0ec9eb106f2284770566d51d979eb6b8bb0c0627f88a4e8d1ef0c83cd7dfbd0733b44e09b339994a0424e7114181a74bf6
6
+ metadata.gz: a99b96b4166a7355016840fd7e7c6cf01febc86e89b1a6e07421db89e7d234972e214a9881b9a3bea2c8210bac2f83735a0d5d7d52c877c5b60160165fc09b16
7
+ data.tar.gz: 84300076a2b5211d3f1f3b317c087f893c754fcec43a2b64614b1a80b53304d95d6e686b5e25144758dd5731d7cd12de662dd45b981d9e259810346e186fe75d
data/Rakefile CHANGED
@@ -3,7 +3,8 @@ require "bundler/gem_tasks"
3
3
 
4
4
  # RSpec
5
5
  require 'rspec/core/rake_task'
6
- RSpec::Core::RakeTask.new
6
+ spec_task = RSpec::Core::RakeTask.new
7
+ spec_task.rspec_opts = '-f doc'
7
8
  task :default => ['testapp:create', :spec]
8
9
 
9
10
  # Create a test Rails app in tmp/railsapp for testing the rake
@@ -132,7 +132,7 @@ module ChronoModel
132
132
  # Returns a boolean indicating whether this record is an history entry.
133
133
  #
134
134
  def historical?
135
- self.as_of_time.present? || self.kind_of?(self.class.history)
135
+ self.as_of_time.present?
136
136
  end
137
137
 
138
138
  # Inhibit destroy of historical records
@@ -145,6 +145,10 @@ module ChronoModel
145
145
  self.class.with_hid_pkey { super }
146
146
  end
147
147
 
148
+ def historical?
149
+ true
150
+ end
151
+
148
152
  # Returns the previous history entry, or nil if this
149
153
  # is the first one.
150
154
  #
@@ -1,3 +1,3 @@
1
1
  module ChronoModel
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.1"
3
3
  end
@@ -0,0 +1,157 @@
1
+ require 'spec_helper'
2
+ require 'support/adapter/structure'
3
+
4
+ describe ChronoModel::Adapter do
5
+ include ChronoTest::Adapter::Helpers
6
+ include ChronoTest::Adapter::Structure
7
+
8
+ subject { adapter }
9
+ it { is_expected.to be_a_kind_of(ChronoModel::Adapter) }
10
+
11
+ context do
12
+ subject { adapter.adapter_name }
13
+ it { is_expected.to eq 'PostgreSQL' }
14
+ end
15
+
16
+ context do
17
+ before { expect(adapter).to receive(:postgresql_version).and_return(90300) }
18
+ it { is_expected.to be_chrono_supported }
19
+ end
20
+
21
+ context do
22
+ before { expect(adapter).to receive(:postgresql_version).and_return(90000) }
23
+ it { is_expected.to_not be_chrono_supported }
24
+ end
25
+
26
+ describe '.primary_key' do
27
+ subject { adapter.primary_key(table) }
28
+
29
+ assert = proc do
30
+ it { is_expected.to eq 'id' }
31
+ end
32
+
33
+ with_temporal_table(&assert)
34
+ with_plain_table( &assert)
35
+ end
36
+
37
+ describe '.indexes' do
38
+ subject { adapter.indexes(table) }
39
+
40
+ assert = proc do
41
+ before(:all) do
42
+ adapter.add_index table, :foo, :name => 'foo_index'
43
+ adapter.add_index table, [:bar, :baz], :name => 'bar_index'
44
+ end
45
+
46
+ it { expect(subject.map(&:name)).to match_array %w( foo_index bar_index ) }
47
+ it { expect(subject.map(&:columns)).to match_array [['foo'], ['bar', 'baz']] }
48
+ end
49
+
50
+ with_temporal_table(&assert)
51
+ with_plain_table( &assert)
52
+ end
53
+
54
+ describe '.column_definitions' do
55
+ subject { adapter.column_definitions(table).map {|d| d.take(2)} }
56
+
57
+ assert = proc do
58
+ it { expect(subject & columns).to eq columns }
59
+ it { is_expected.to include(['id', pk_type]) }
60
+ end
61
+
62
+ with_temporal_table(&assert)
63
+ with_plain_table( &assert)
64
+ end
65
+
66
+ describe '.on_schema' do
67
+ before(:all) do
68
+ adapter.execute 'BEGIN'
69
+ 5.times {|i| adapter.execute "CREATE SCHEMA test_#{i}"}
70
+ end
71
+
72
+ after(:all) do
73
+ adapter.execute 'ROLLBACK'
74
+ end
75
+
76
+ context 'by default' do
77
+ it 'saves the schema at each recursion' do
78
+ is_expected.to be_in_schema(:default)
79
+
80
+ adapter.on_schema('test_1') { is_expected.to be_in_schema('test_1')
81
+ adapter.on_schema('test_2') { is_expected.to be_in_schema('test_2')
82
+ adapter.on_schema('test_3') { is_expected.to be_in_schema('test_3')
83
+ }
84
+ is_expected.to be_in_schema('test_2')
85
+ }
86
+ is_expected.to be_in_schema('test_1')
87
+ }
88
+
89
+ is_expected.to be_in_schema(:default)
90
+ end
91
+
92
+ context 'when errors occur' do
93
+ subject do
94
+ adapter.on_schema('test_1') do
95
+
96
+ adapter.on_schema('test_2') do
97
+ adapter.execute 'BEGIN'
98
+ adapter.execute 'ERRORING ON PURPOSE'
99
+ end
100
+
101
+ end
102
+ end
103
+
104
+ it {
105
+ expect { subject }.
106
+ to raise_error(/current transaction is aborted/).
107
+ and change { adapter.instance_variable_get(:@schema_search_path) }
108
+ }
109
+
110
+ after do
111
+ adapter.execute 'ROLLBACK'
112
+ end
113
+ end
114
+ end
115
+
116
+ context 'with recurse: :ignore' do
117
+ it 'ignores recursive calls' do
118
+ is_expected.to be_in_schema(:default)
119
+
120
+ adapter.on_schema('test_1', recurse: :ignore) { is_expected.to be_in_schema('test_1')
121
+ adapter.on_schema('test_2', recurse: :ignore) { is_expected.to be_in_schema('test_1')
122
+ adapter.on_schema('test_3', recurse: :ignore) { is_expected.to be_in_schema('test_1')
123
+ } } }
124
+
125
+ is_expected.to be_in_schema(:default)
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '.is_chrono?' do
131
+ with_temporal_table do
132
+ it { expect(adapter.is_chrono?(table)).to be(true) }
133
+ end
134
+
135
+ with_plain_table do
136
+ it { expect(adapter.is_chrono?(table)).to be(false) }
137
+ end
138
+
139
+ context 'when schemas are not there yet' do
140
+ before(:all) do
141
+ adapter.execute 'BEGIN'
142
+ adapter.execute 'DROP SCHEMA temporal CASCADE'
143
+ adapter.execute 'DROP SCHEMA history CASCADE'
144
+ adapter.execute 'CREATE TABLE test_table (id integer)'
145
+ end
146
+
147
+ after(:all) do
148
+ adapter.execute 'ROLLBACK'
149
+ end
150
+
151
+ it { expect { adapter.is_chrono?(table) }.to_not raise_error }
152
+
153
+ it { expect(adapter.is_chrono?(table)).to be(false) }
154
+ end
155
+ end
156
+
157
+ end
@@ -0,0 +1,243 @@
1
+ require 'spec_helper'
2
+ require 'support/adapter/structure'
3
+
4
+ describe ChronoModel::Adapter do
5
+ include ChronoTest::Adapter::Helpers
6
+ include ChronoTest::Adapter::Structure
7
+
8
+ let(:current) { [ChronoModel::Adapter::TEMPORAL_SCHEMA, table].join('.') }
9
+ let(:history) { [ChronoModel::Adapter::HISTORY_SCHEMA, table].join('.') }
10
+
11
+ def count(table)
12
+ adapter.select_value("SELECT COUNT(*) FROM ONLY #{table}").to_i
13
+ end
14
+
15
+ def ids(table)
16
+ adapter.select_values("SELECT id FROM ONLY #{table} ORDER BY id")
17
+ end
18
+
19
+ context 'INSERT multiple values' do
20
+ before :all do
21
+ adapter.create_table table, :temporal => true, &columns
22
+ end
23
+
24
+ after :all do
25
+ adapter.drop_table table
26
+ end
27
+
28
+ context 'when succeeding' do
29
+ def insert
30
+ adapter.execute <<-SQL
31
+ INSERT INTO #{table} (test, foo) VALUES
32
+ ('test1', 1),
33
+ ('test2', 2);
34
+ SQL
35
+ end
36
+
37
+ it { expect { insert }.to_not raise_error }
38
+ it { expect(count(current)).to eq 2 }
39
+ it { expect(count(history)).to eq 2 }
40
+ end
41
+
42
+ context 'when failing' do
43
+ def insert
44
+ adapter.execute <<-SQL
45
+ INSERT INTO #{table} (test, foo) VALUES
46
+ ('test3', 3),
47
+ (NULL, 0);
48
+ SQL
49
+ end
50
+
51
+ it { expect { insert }.to raise_error(ActiveRecord::StatementInvalid) }
52
+ it { expect(count(current)).to eq 2 } # Because the previous
53
+ it { expect(count(history)).to eq 2 } # records are preserved
54
+ end
55
+
56
+ context 'after a failure' do
57
+ def insert
58
+ adapter.execute <<-SQL
59
+ INSERT INTO #{table} (test, foo) VALUES
60
+ ('test4', 3),
61
+ ('test5', 4);
62
+ SQL
63
+ end
64
+
65
+ it { expect { insert }.to_not raise_error }
66
+
67
+ it { expect(count(current)).to eq 4 }
68
+ it { expect(count(history)).to eq 4 }
69
+
70
+ it { expect(ids(current)).to eq ids(history) }
71
+ end
72
+ end
73
+
74
+ context 'INSERT on NOT NULL columns but with a DEFAULT value' do
75
+ before :all do
76
+ adapter.create_table table, :temporal => true, &columns
77
+ end
78
+
79
+ after :all do
80
+ adapter.drop_table table
81
+ end
82
+
83
+ def insert
84
+ adapter.execute <<-SQL
85
+ INSERT INTO #{table} DEFAULT VALUES
86
+ SQL
87
+ end
88
+
89
+ def select
90
+ adapter.select_values <<-SQL
91
+ SELECT test FROM #{table}
92
+ SQL
93
+ end
94
+
95
+ it { expect { insert }.to_not raise_error }
96
+ it { insert; expect(select.uniq).to eq ['default-value'] }
97
+ end
98
+
99
+ context 'redundant UPDATEs' do
100
+
101
+ before :all do
102
+ adapter.create_table table, :temporal => true, &columns
103
+
104
+ adapter.execute <<-SQL
105
+ INSERT INTO #{table} (test, foo) VALUES ('test1', 1);
106
+ SQL
107
+
108
+ adapter.execute <<-SQL
109
+ UPDATE #{table} SET test = 'test2';
110
+ SQL
111
+
112
+ adapter.execute <<-SQL
113
+ UPDATE #{table} SET test = 'test2';
114
+ SQL
115
+ end
116
+
117
+ after :all do
118
+ adapter.drop_table table
119
+ end
120
+
121
+ it { expect(count(current)).to eq 1 }
122
+ it { expect(count(history)).to eq 2 }
123
+
124
+ end
125
+
126
+ context 'updates on non-journaled fields' do
127
+ before :all do
128
+ adapter.create_table table, :temporal => true do |t|
129
+ t.string 'test'
130
+ t.timestamps null: false
131
+ end
132
+
133
+ adapter.execute <<-SQL
134
+ INSERT INTO #{table} (test, created_at, updated_at) VALUES ('test', now(), now());
135
+ SQL
136
+
137
+ adapter.execute <<-SQL
138
+ UPDATE #{table} SET test = 'test2', updated_at = now();
139
+ SQL
140
+
141
+ 2.times do
142
+ adapter.execute <<-SQL # Redundant update with only updated_at change
143
+ UPDATE #{table} SET test = 'test2', updated_at = now();
144
+ SQL
145
+
146
+ adapter.execute <<-SQL
147
+ UPDATE #{table} SET updated_at = now();
148
+ SQL
149
+ end
150
+ end
151
+
152
+ after :all do
153
+ adapter.drop_table table
154
+ end
155
+
156
+ it { expect(count(current)).to eq 1 }
157
+ it { expect(count(history)).to eq 2 }
158
+ end
159
+
160
+ context 'selective journaled fields' do
161
+ describe 'basic behaviour' do
162
+ specify do
163
+ adapter.create_table table, :temporal => true, :journal => %w( foo ) do |t|
164
+ t.string 'foo'
165
+ t.string 'bar'
166
+ end
167
+
168
+ adapter.execute <<-SQL
169
+ INSERT INTO #{table} (foo, bar) VALUES ('test foo', 'test bar');
170
+ SQL
171
+
172
+ adapter.execute <<-SQL
173
+ UPDATE #{table} SET foo = 'test foo', bar = 'no history';
174
+ SQL
175
+
176
+ 2.times do
177
+ adapter.execute <<-SQL
178
+ UPDATE #{table} SET bar = 'really no history';
179
+ SQL
180
+ end
181
+
182
+ expect(count(current)).to eq 1
183
+ expect(count(history)).to eq 1
184
+
185
+ adapter.drop_table table
186
+ end
187
+ end
188
+
189
+ describe 'schema changes' do
190
+ table 'journaled_things'
191
+
192
+ before do
193
+ adapter.create_table table, :temporal => true, :journal => %w( foo ) do |t|
194
+ t.string 'foo'
195
+ t.string 'bar'
196
+ t.string 'baz'
197
+ end
198
+ end
199
+
200
+ after do
201
+ adapter.drop_table table
202
+ end
203
+
204
+ it 'preserves options upon column change' do
205
+ adapter.change_table table, temporal: true, journal: %w(foo bar)
206
+
207
+ adapter.execute <<-SQL
208
+ INSERT INTO #{table} (foo, bar) VALUES ('test foo', 'test bar');
209
+ SQL
210
+
211
+ expect(count(current)).to eq 1
212
+ expect(count(history)).to eq 1
213
+
214
+ adapter.execute <<-SQL
215
+ UPDATE #{table} SET foo = 'test foo', bar = 'chronomodel';
216
+ SQL
217
+
218
+ expect(count(current)).to eq 1
219
+ expect(count(history)).to eq 2
220
+ end
221
+
222
+ it 'changes option upon table change' do
223
+ adapter.change_table table, temporal: true, journal: %w(bar)
224
+
225
+ adapter.execute <<-SQL
226
+ INSERT INTO #{table} (foo, bar) VALUES ('test foo', 'test bar');
227
+ UPDATE #{table} SET foo = 'test foo', bar = 'no history';
228
+ SQL
229
+
230
+ expect(count(current)).to eq 1
231
+ expect(count(history)).to eq 1
232
+
233
+ adapter.execute <<-SQL
234
+ UPDATE #{table} SET foo = 'test foo again', bar = 'no history';
235
+ SQL
236
+
237
+ expect(count(current)).to eq 1
238
+ expect(count(history)).to eq 1
239
+ end
240
+ end
241
+ end
242
+
243
+ end