chrono_model 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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