chrono_model 1.2.2 → 2.0.0
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 +4 -4
- data/LICENSE +19 -20
- data/README.md +62 -40
- data/lib/active_record/connection_adapters/chronomodel_adapter.rb +17 -11
- data/lib/active_record/tasks/chronomodel_database_tasks.rb +64 -23
- data/lib/chrono_model/adapter/ddl.rb +168 -153
- data/lib/chrono_model/adapter/indexes.rb +99 -94
- data/lib/chrono_model/adapter/migrations.rb +81 -104
- data/lib/chrono_model/adapter/migrations_modules/legacy.rb +41 -0
- data/lib/chrono_model/adapter/migrations_modules/stable.rb +41 -0
- data/lib/chrono_model/adapter/tsrange.rb +20 -5
- data/lib/chrono_model/adapter/upgrade.rb +89 -91
- data/lib/chrono_model/adapter.rb +64 -31
- data/lib/chrono_model/chrono.rb +17 -0
- data/lib/chrono_model/conversions.rb +15 -9
- data/lib/chrono_model/db_console.rb +9 -0
- data/lib/chrono_model/json.rb +9 -6
- data/lib/chrono_model/patches/as_of_time_holder.rb +2 -2
- data/lib/chrono_model/patches/as_of_time_relation.rb +2 -2
- data/lib/chrono_model/patches/association.rb +15 -12
- data/lib/chrono_model/patches/batches.rb +17 -0
- data/lib/chrono_model/patches/db_console.rb +20 -4
- data/lib/chrono_model/patches/join_node.rb +4 -4
- data/lib/chrono_model/patches/preloader.rb +41 -11
- data/lib/chrono_model/patches/relation.rb +53 -8
- data/lib/chrono_model/patches.rb +3 -1
- data/lib/chrono_model/railtie.rb +29 -24
- data/lib/chrono_model/time_gate.rb +3 -3
- data/lib/chrono_model/time_machine/history_model.rb +65 -31
- data/lib/chrono_model/time_machine/time_query.rb +65 -49
- data/lib/chrono_model/time_machine/timeline.rb +52 -28
- data/lib/chrono_model/time_machine.rb +66 -25
- data/lib/chrono_model/utilities.rb +3 -3
- data/lib/chrono_model/version.rb +3 -1
- data/lib/chrono_model.rb +31 -36
- metadata +39 -136
- data/.gitignore +0 -21
- data/.rspec +0 -2
- data/.travis.yml +0 -41
- data/Gemfile +0 -4
- data/README.sql +0 -161
- data/Rakefile +0 -25
- data/chrono_model.gemspec +0 -33
- data/gemfiles/rails_5.0.gemfile +0 -6
- data/gemfiles/rails_5.1.gemfile +0 -6
- data/gemfiles/rails_5.2.gemfile +0 -6
- data/spec/aruba/dbconsole_spec.rb +0 -25
- data/spec/aruba/fixtures/database_with_default_username_and_password.yml +0 -14
- data/spec/aruba/fixtures/database_without_username_and_password.yml +0 -11
- data/spec/aruba/fixtures/empty_structure.sql +0 -27
- data/spec/aruba/fixtures/migrations/56/20160812190335_create_impressions.rb +0 -10
- data/spec/aruba/fixtures/migrations/56/20171115195229_add_temporal_extension_to_impressions.rb +0 -10
- data/spec/aruba/fixtures/railsapp/config/application.rb +0 -17
- data/spec/aruba/fixtures/railsapp/config/boot.rb +0 -5
- data/spec/aruba/fixtures/railsapp/config/environments/development.rb +0 -38
- data/spec/aruba/migrations_spec.rb +0 -48
- data/spec/aruba/rake_task_spec.rb +0 -71
- data/spec/chrono_model/adapter/base_spec.rb +0 -157
- data/spec/chrono_model/adapter/ddl_spec.rb +0 -243
- data/spec/chrono_model/adapter/indexes_spec.rb +0 -72
- data/spec/chrono_model/adapter/migrations_spec.rb +0 -312
- data/spec/chrono_model/conversions_spec.rb +0 -43
- data/spec/chrono_model/history_models_spec.rb +0 -32
- data/spec/chrono_model/json_ops_spec.rb +0 -59
- data/spec/chrono_model/time_machine/as_of_spec.rb +0 -188
- data/spec/chrono_model/time_machine/changes_spec.rb +0 -50
- data/spec/chrono_model/time_machine/counter_cache_race_spec.rb +0 -46
- data/spec/chrono_model/time_machine/default_scope_spec.rb +0 -37
- data/spec/chrono_model/time_machine/history_spec.rb +0 -104
- data/spec/chrono_model/time_machine/keep_cool_spec.rb +0 -27
- data/spec/chrono_model/time_machine/manipulations_spec.rb +0 -84
- data/spec/chrono_model/time_machine/model_identification_spec.rb +0 -46
- data/spec/chrono_model/time_machine/sequence_spec.rb +0 -74
- data/spec/chrono_model/time_machine/sti_spec.rb +0 -100
- data/spec/chrono_model/time_machine/time_query_spec.rb +0 -261
- data/spec/chrono_model/time_machine/timeline_spec.rb +0 -63
- data/spec/chrono_model/time_machine/timestamps_spec.rb +0 -43
- data/spec/chrono_model/time_machine/transactions_spec.rb +0 -69
- data/spec/config.travis.yml +0 -5
- data/spec/config.yml.example +0 -9
- data/spec/spec_helper.rb +0 -33
- data/spec/support/adapter/helpers.rb +0 -53
- data/spec/support/adapter/structure.rb +0 -44
- data/spec/support/aruba.rb +0 -44
- data/spec/support/connection.rb +0 -70
- data/spec/support/matchers/base.rb +0 -56
- data/spec/support/matchers/column.rb +0 -99
- data/spec/support/matchers/function.rb +0 -79
- data/spec/support/matchers/index.rb +0 -69
- data/spec/support/matchers/schema.rb +0 -39
- data/spec/support/matchers/table.rb +0 -275
- data/spec/support/time_machine/helpers.rb +0 -47
- data/spec/support/time_machine/structure.rb +0 -111
- data/sql/json_ops.sql +0 -56
- data/sql/uninstall-json_ops.sql +0 -24
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
require 'support/adapter/structure'
|
|
3
|
-
|
|
4
|
-
shared_examples_for 'temporal table' do
|
|
5
|
-
it { expect(adapter.is_chrono?(subject)).to be(true) }
|
|
6
|
-
|
|
7
|
-
it { is_expected.to_not have_public_backing }
|
|
8
|
-
|
|
9
|
-
it { is_expected.to have_temporal_backing }
|
|
10
|
-
it { is_expected.to have_history_backing }
|
|
11
|
-
it { is_expected.to have_history_extra_columns }
|
|
12
|
-
it { is_expected.to have_history_functions }
|
|
13
|
-
it { is_expected.to have_public_interface }
|
|
14
|
-
|
|
15
|
-
it { is_expected.to have_columns(columns) }
|
|
16
|
-
it { is_expected.to have_temporal_columns(columns) }
|
|
17
|
-
it { is_expected.to have_history_columns(columns) }
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
shared_examples_for 'plain table' do
|
|
21
|
-
it { expect(adapter.is_chrono?(subject)).to be(false) }
|
|
22
|
-
|
|
23
|
-
it { is_expected.to have_public_backing }
|
|
24
|
-
|
|
25
|
-
it { is_expected.to_not have_temporal_backing }
|
|
26
|
-
it { is_expected.to_not have_history_backing }
|
|
27
|
-
it { is_expected.to_not have_history_functions }
|
|
28
|
-
it { is_expected.to_not have_public_interface }
|
|
29
|
-
|
|
30
|
-
it { is_expected.to have_columns(columns) }
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
describe ChronoModel::Adapter do
|
|
34
|
-
include ChronoTest::Adapter::Helpers
|
|
35
|
-
include ChronoTest::Adapter::Structure
|
|
36
|
-
|
|
37
|
-
describe '.create_table' do
|
|
38
|
-
with_temporal_table do
|
|
39
|
-
it_should_behave_like 'temporal table'
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
with_plain_table do
|
|
43
|
-
it_should_behave_like 'plain table'
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
describe '.rename_table' do
|
|
48
|
-
renamed = 'foo_table'
|
|
49
|
-
subject { renamed }
|
|
50
|
-
|
|
51
|
-
context 'temporal: true' do
|
|
52
|
-
before :all do
|
|
53
|
-
adapter.create_table table, :temporal => true, &columns
|
|
54
|
-
adapter.add_index table, :test
|
|
55
|
-
adapter.add_index table, [:foo, :bar]
|
|
56
|
-
|
|
57
|
-
adapter.rename_table table, renamed
|
|
58
|
-
end
|
|
59
|
-
after(:all) { adapter.drop_table(renamed) }
|
|
60
|
-
|
|
61
|
-
it_should_behave_like 'temporal table'
|
|
62
|
-
|
|
63
|
-
it 'renames indexes' do
|
|
64
|
-
new_index_names = adapter.indexes(renamed).map(&:name)
|
|
65
|
-
expected_index_names = [[:test], [:foo, :bar]].map do |idx_cols|
|
|
66
|
-
"index_#{renamed}_on_#{idx_cols.join('_and_')}"
|
|
67
|
-
end
|
|
68
|
-
expect(new_index_names.to_set).to eq expected_index_names.to_set
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
context 'temporal: false' do
|
|
73
|
-
before :all do
|
|
74
|
-
adapter.create_table table, :temporal => false, &columns
|
|
75
|
-
|
|
76
|
-
adapter.rename_table table, renamed
|
|
77
|
-
end
|
|
78
|
-
after(:all) { adapter.drop_table(renamed) }
|
|
79
|
-
|
|
80
|
-
it_should_behave_like 'plain table'
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
describe '.change_table' do
|
|
85
|
-
with_temporal_table do
|
|
86
|
-
before :all do
|
|
87
|
-
adapter.change_table table, :temporal => false
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
it_should_behave_like 'plain table'
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
with_plain_table do
|
|
94
|
-
before :all do
|
|
95
|
-
adapter.add_index table, :foo
|
|
96
|
-
adapter.add_index table, :bar, :unique => true
|
|
97
|
-
|
|
98
|
-
adapter.change_table table, :temporal => true
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
it_should_behave_like 'temporal table'
|
|
102
|
-
|
|
103
|
-
let(:history_indexes) do
|
|
104
|
-
adapter.on_schema(ChronoModel::Adapter::HISTORY_SCHEMA) do
|
|
105
|
-
adapter.indexes(table)
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
it "copies plain index to history" do
|
|
110
|
-
expect(history_indexes.find {|i| i.columns == ['foo']}).to be_present
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
it "copies unique index to history without uniqueness constraint" do
|
|
114
|
-
expect(history_indexes.find {|i| i.columns == ['bar'] && i.unique == false}).to be_present
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
describe '.drop_table' do
|
|
120
|
-
before :all do
|
|
121
|
-
adapter.create_table table, :temporal => true, &columns
|
|
122
|
-
|
|
123
|
-
adapter.drop_table table
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it { is_expected.to_not have_public_backing }
|
|
127
|
-
it { is_expected.to_not have_temporal_backing }
|
|
128
|
-
it { is_expected.to_not have_history_backing }
|
|
129
|
-
it { is_expected.to_not have_history_functions }
|
|
130
|
-
it { is_expected.to_not have_public_interface }
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
describe '.add_index' do
|
|
134
|
-
with_temporal_table do
|
|
135
|
-
before :all do
|
|
136
|
-
adapter.add_index table, [:foo, :bar], :name => 'foobar_index'
|
|
137
|
-
adapter.add_index table, [:test], :name => 'test_index'
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
it { is_expected.to have_temporal_index 'foobar_index', %w( foo bar ) }
|
|
141
|
-
it { is_expected.to have_history_index 'foobar_index', %w( foo bar ) }
|
|
142
|
-
it { is_expected.to have_temporal_index 'test_index', %w( test ) }
|
|
143
|
-
it { is_expected.to have_history_index 'test_index', %w( test ) }
|
|
144
|
-
|
|
145
|
-
it { is_expected.to_not have_index 'foobar_index', %w( foo bar ) }
|
|
146
|
-
it { is_expected.to_not have_index 'test_index', %w( test ) }
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
with_plain_table do
|
|
150
|
-
before :all do
|
|
151
|
-
adapter.add_index table, [:foo, :bar], :name => 'foobar_index'
|
|
152
|
-
adapter.add_index table, [:test], :name => 'test_index'
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
it { is_expected.to_not have_temporal_index 'foobar_index', %w( foo bar ) }
|
|
156
|
-
it { is_expected.to_not have_history_index 'foobar_index', %w( foo bar ) }
|
|
157
|
-
it { is_expected.to_not have_temporal_index 'test_index', %w( test ) }
|
|
158
|
-
it { is_expected.to_not have_history_index 'test_index', %w( test ) }
|
|
159
|
-
|
|
160
|
-
it { is_expected.to have_index 'foobar_index', %w( foo bar ) }
|
|
161
|
-
it { is_expected.to have_index 'test_index', %w( test ) }
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
describe '.remove_index' do
|
|
166
|
-
with_temporal_table do
|
|
167
|
-
before :all do
|
|
168
|
-
adapter.add_index table, [:foo, :bar], :name => 'foobar_index'
|
|
169
|
-
adapter.add_index table, [:test], :name => 'test_index'
|
|
170
|
-
|
|
171
|
-
adapter.remove_index table, :name => 'test_index'
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
it { is_expected.to_not have_temporal_index 'test_index', %w( test ) }
|
|
175
|
-
it { is_expected.to_not have_history_index 'test_index', %w( test ) }
|
|
176
|
-
it { is_expected.to_not have_index 'test_index', %w( test ) }
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
with_plain_table do
|
|
180
|
-
before :all do
|
|
181
|
-
adapter.add_index table, [:foo, :bar], :name => 'foobar_index'
|
|
182
|
-
adapter.add_index table, [:test], :name => 'test_index'
|
|
183
|
-
|
|
184
|
-
adapter.remove_index table, :name => 'test_index'
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
it { is_expected.to_not have_temporal_index 'test_index', %w( test ) }
|
|
188
|
-
it { is_expected.to_not have_history_index 'test_index', %w( test ) }
|
|
189
|
-
it { is_expected.to_not have_index 'test_index', %w( test ) }
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
describe '.add_column' do
|
|
194
|
-
let(:extra_columns) { [['foobarbaz', 'integer']] }
|
|
195
|
-
|
|
196
|
-
with_temporal_table do
|
|
197
|
-
before :all do
|
|
198
|
-
adapter.add_column table, :foobarbaz, :integer
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
it { is_expected.to have_columns(extra_columns) }
|
|
202
|
-
it { is_expected.to have_temporal_columns(extra_columns) }
|
|
203
|
-
it { is_expected.to have_history_columns(extra_columns) }
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
with_plain_table do
|
|
207
|
-
before :all do
|
|
208
|
-
adapter.add_column table, :foobarbaz, :integer
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
it { is_expected.to have_columns(extra_columns) }
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
describe '.remove_column' do
|
|
216
|
-
let(:resulting_columns) { columns.reject {|c,_| c == 'foo'} }
|
|
217
|
-
|
|
218
|
-
with_temporal_table do
|
|
219
|
-
before :all do
|
|
220
|
-
adapter.remove_column table, :foo
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
it { is_expected.to have_columns(resulting_columns) }
|
|
224
|
-
it { is_expected.to have_temporal_columns(resulting_columns) }
|
|
225
|
-
it { is_expected.to have_history_columns(resulting_columns) }
|
|
226
|
-
|
|
227
|
-
it { is_expected.to_not have_columns([['foo', 'integer']]) }
|
|
228
|
-
it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
|
|
229
|
-
it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
with_plain_table do
|
|
233
|
-
before :all do
|
|
234
|
-
adapter.remove_column table, :foo
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
it { is_expected.to have_columns(resulting_columns) }
|
|
238
|
-
it { is_expected.to_not have_columns([['foo', 'integer']]) }
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
describe '.rename_column' do
|
|
243
|
-
with_temporal_table do
|
|
244
|
-
before :all do
|
|
245
|
-
adapter.rename_column table, :foo, :taratapiatapioca
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
it { is_expected.to_not have_columns([['foo', 'integer']]) }
|
|
249
|
-
it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
|
|
250
|
-
it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
|
|
251
|
-
|
|
252
|
-
it { is_expected.to have_columns([['taratapiatapioca', 'integer']]) }
|
|
253
|
-
it { is_expected.to have_temporal_columns([['taratapiatapioca', 'integer']]) }
|
|
254
|
-
it { is_expected.to have_history_columns([['taratapiatapioca', 'integer']]) }
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
with_plain_table do
|
|
258
|
-
before :all do
|
|
259
|
-
adapter.rename_column table, :foo, :taratapiatapioca
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
it { is_expected.to_not have_columns([['foo', 'integer']]) }
|
|
263
|
-
it { is_expected.to have_columns([['taratapiatapioca', 'integer']]) }
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
describe '.change_column' do
|
|
268
|
-
with_temporal_table do
|
|
269
|
-
before :all do
|
|
270
|
-
adapter.change_column table, :foo, :float
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
it { is_expected.to_not have_columns([['foo', 'integer']]) }
|
|
274
|
-
it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
|
|
275
|
-
it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
|
|
276
|
-
|
|
277
|
-
it { is_expected.to have_columns([['foo', 'double precision']]) }
|
|
278
|
-
it { is_expected.to have_temporal_columns([['foo', 'double precision']]) }
|
|
279
|
-
it { is_expected.to have_history_columns([['foo', 'double precision']]) }
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
with_plain_table do
|
|
283
|
-
before(:all) do
|
|
284
|
-
adapter.change_column table, :foo, :float
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
it { is_expected.to_not have_columns([['foo', 'integer']]) }
|
|
288
|
-
it { is_expected.to have_columns([['foo', 'double precision']]) }
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
describe '.remove_column' do
|
|
293
|
-
with_temporal_table do
|
|
294
|
-
before :all do
|
|
295
|
-
adapter.remove_column table, :foo
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
it { is_expected.to_not have_columns([['foo', 'integer']]) }
|
|
299
|
-
it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
|
|
300
|
-
it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
with_plain_table do
|
|
304
|
-
before :all do
|
|
305
|
-
adapter.remove_column table, :foo
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
it { is_expected.to_not have_columns([['foo', 'integer']]) }
|
|
309
|
-
end
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe ChronoModel::Conversions do
|
|
4
|
-
|
|
5
|
-
describe 'string_to_utc_time' do
|
|
6
|
-
subject { described_class.string_to_utc_time(string) }
|
|
7
|
-
|
|
8
|
-
context 'given a valid UTC time string' do
|
|
9
|
-
let(:string) { '2017-02-06 09:46:31.129626' }
|
|
10
|
-
|
|
11
|
-
it { is_expected.to be_a(Time) }
|
|
12
|
-
it { expect(subject.year).to eq 2017 }
|
|
13
|
-
it { expect(subject.month).to eq 2 }
|
|
14
|
-
it { expect(subject.day).to eq 6 }
|
|
15
|
-
it { expect(subject.hour).to eq 9 }
|
|
16
|
-
it { expect(subject.min).to eq 46 }
|
|
17
|
-
it { expect(subject.sec).to eq 31 }
|
|
18
|
-
it { expect(subject.usec).to eq 129626 } # Ref Issue #32
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
context 'given a valid UTC string without least significant zeros' do
|
|
22
|
-
let(:string) { '2017-02-06 09:46:31.129' }
|
|
23
|
-
|
|
24
|
-
it { is_expected.to be_a(Time) }
|
|
25
|
-
it { expect(subject.usec).to eq 129000 } # Ref Issue #32
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
context 'given an invalid UTC time string' do
|
|
29
|
-
let(:string) { 'foobar' }
|
|
30
|
-
|
|
31
|
-
it { is_expected.to be(nil) }
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
describe 'time_to_utc_string' do
|
|
36
|
-
subject { described_class.time_to_utc_string(time) }
|
|
37
|
-
|
|
38
|
-
let(:time) { Time.utc(1981, 4, 11, 2, 42, 10, 123456) }
|
|
39
|
-
|
|
40
|
-
it { is_expected.to eq '1981-04-11 02:42:10.123456' }
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
end
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
require 'support/time_machine/structure'
|
|
3
|
-
|
|
4
|
-
describe ChronoModel do
|
|
5
|
-
describe '.history_models' do
|
|
6
|
-
subject { ChronoModel.history_models }
|
|
7
|
-
|
|
8
|
-
it 'tracks recorded history models' do
|
|
9
|
-
expected = {}
|
|
10
|
-
|
|
11
|
-
# support/time_machine/structure
|
|
12
|
-
expected['foos'] = Foo::History if defined?(Foo::History)
|
|
13
|
-
expected['bars'] = Bar::History if defined?(Bar::History)
|
|
14
|
-
expected['sub_bars'] = SubBar::History if defined?(SubBar::History)
|
|
15
|
-
|
|
16
|
-
# default_scope_spec
|
|
17
|
-
expected['defoos'] = Defoo::History if defined?(Defoo::History)
|
|
18
|
-
|
|
19
|
-
# counter_cache_race_spec
|
|
20
|
-
expected['sections'] = Section::History if defined?(Section::History)
|
|
21
|
-
expected['articles'] = Article::History if defined?(Article::History)
|
|
22
|
-
|
|
23
|
-
# sti_spec
|
|
24
|
-
expected['animals'] = Animal::History if defined?(Animal::History)
|
|
25
|
-
expected['elements'] = Element::History if defined?(Element::History)
|
|
26
|
-
|
|
27
|
-
is_expected.to eq(expected)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it { expect(subject.size).to be > 0 }
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
##########################################################
|
|
2
|
-
### DEPRECATED: JSON operators are an hack and there is no
|
|
3
|
-
### reason not to use jsonb other than migrating your data
|
|
4
|
-
##########################################################
|
|
5
|
-
if ENV['HAVE_PLPYTHON'] == '1'
|
|
6
|
-
|
|
7
|
-
require 'spec_helper'
|
|
8
|
-
require 'support/helpers'
|
|
9
|
-
|
|
10
|
-
require 'chrono_model/json'
|
|
11
|
-
|
|
12
|
-
describe 'JSON equality operator' do
|
|
13
|
-
include ChronoTest::Helpers::Adapter
|
|
14
|
-
|
|
15
|
-
table 'json_test'
|
|
16
|
-
|
|
17
|
-
before :all do
|
|
18
|
-
ChronoModel::Json.create
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
after :all do
|
|
22
|
-
ChronoModel::Json.drop
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":1}'::json ])).to eq true }
|
|
26
|
-
it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a" : 1}'::json ])).to eq true }
|
|
27
|
-
it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":2}'::json ])).to eq false }
|
|
28
|
-
it { expect(adapter.select_value(%[ SELECT '{"a":1,"b":2}'::json = '{"b":2,"a":1}'::json ])).to eq true }
|
|
29
|
-
it { expect(adapter.select_value(%[ SELECT '{"a":1,"b":2,"x":{"c":4,"d":5}}'::json = '{"b":2, "x": { "d": 5, "c": 4}, "a":1}'::json ])).to eq true }
|
|
30
|
-
|
|
31
|
-
context 'on a temporal table' do
|
|
32
|
-
before :all do
|
|
33
|
-
adapter.create_table table, :temporal => true do |t|
|
|
34
|
-
t.json 'data'
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
adapter.execute %[
|
|
38
|
-
INSERT INTO #{table} ( data ) VALUES ( '{"a":1,"b":2}' )
|
|
39
|
-
]
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
after :all do
|
|
43
|
-
adapter.drop_table table
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
it { expect {
|
|
47
|
-
adapter.execute "UPDATE #{table} SET data = NULL"
|
|
48
|
-
}.to_not raise_error }
|
|
49
|
-
|
|
50
|
-
it { expect {
|
|
51
|
-
adapter.execute %[UPDATE #{table} SET data = '{"x":1,"y":2}']
|
|
52
|
-
|
|
53
|
-
}.to_not raise_error }
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
end
|
|
@@ -1,188 +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 '.as_of' do
|
|
8
|
-
it { expect(Foo.as_of(1.month.ago)).to eq [] }
|
|
9
|
-
|
|
10
|
-
it { expect(Foo.as_of($t.foos[0].ts[0])).to eq [$t.foo, $t.foos[0]] }
|
|
11
|
-
it { expect(Foo.as_of($t.foos[1].ts[0])).to eq [$t.foo, $t.foos[0], $t.foos[1]] }
|
|
12
|
-
it { expect(Foo.as_of(Time.now )).to eq [$t.foo, $t.foos[0], $t.foos[1]] }
|
|
13
|
-
|
|
14
|
-
it { expect(Bar.as_of($t.foos[1].ts[0])).to eq [$t.bar] }
|
|
15
|
-
|
|
16
|
-
it { expect(Bar.as_of($t.bars[0].ts[0])).to eq [$t.bar, $t.bars[0]] }
|
|
17
|
-
it { expect(Bar.as_of($t.bars[1].ts[0])).to eq [$t.bar, $t.bars[0], $t.bars[1]] }
|
|
18
|
-
it { expect(Bar.as_of(Time.now )).to eq [$t.bar, $t.bars[0], $t.bars[1]] }
|
|
19
|
-
|
|
20
|
-
it { expect(Foo.as_of($t.foos[0].ts[0]).first).to be_a(Foo) }
|
|
21
|
-
it { expect(Bar.as_of($t.foos[0].ts[0]).first).to be_a(Bar) }
|
|
22
|
-
|
|
23
|
-
# Associations
|
|
24
|
-
context do
|
|
25
|
-
subject { $t.foos[0].id }
|
|
26
|
-
|
|
27
|
-
it { expect(Foo.as_of($t.foos[0].ts[0]).find(subject).bars).to eq [] }
|
|
28
|
-
it { expect(Foo.as_of($t.foos[1].ts[0]).find(subject).bars).to eq [] }
|
|
29
|
-
it { expect(Foo.as_of($t.bars[0].ts[0]).find(subject).bars).to eq [$t.bars[0]] }
|
|
30
|
-
it { expect(Foo.as_of($t.bars[1].ts[0]).find(subject).bars).to eq [$t.bars[0]] }
|
|
31
|
-
it { expect(Foo.as_of(Time.now ).find(subject).bars).to eq [$t.bars[0]] }
|
|
32
|
-
|
|
33
|
-
it { expect(Foo.as_of($t.bars[0].ts[0]).find(subject).bars.first).to be_a(Bar) }
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
context do
|
|
37
|
-
subject { $t.foos[1].id }
|
|
38
|
-
|
|
39
|
-
it { expect { Foo.as_of($t.foos[0].ts[0]).find(subject) }.to raise_error(ActiveRecord::RecordNotFound) }
|
|
40
|
-
it { expect { Foo.as_of($t.foos[1].ts[0]).find(subject) }.to_not raise_error }
|
|
41
|
-
|
|
42
|
-
it { expect(Foo.as_of($t.bars[0].ts[0]).find(subject).bars).to eq [] }
|
|
43
|
-
it { expect(Foo.as_of($t.bars[1].ts[0]).find(subject).bars).to eq [$t.bars[1]] }
|
|
44
|
-
it { expect(Foo.as_of(Time.now ).find(subject).bars).to eq [$t.bars[1]] }
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
describe '#as_of' do
|
|
50
|
-
describe 'accepts a Time instance' do
|
|
51
|
-
it { expect($t.foo.as_of(Time.now).name).to eq 'new foo' }
|
|
52
|
-
it { expect($t.bar.as_of(Time.now).name).to eq 'new bar' }
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
describe 'ignores time zones' do
|
|
57
|
-
it { expect($t.foo.as_of(Time.now.in_time_zone('America/Havana')).name).to eq 'new foo' }
|
|
58
|
-
it { expect($t.bar.as_of(Time.now.in_time_zone('America/Havana')).name).to eq 'new bar' }
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
describe 'returns records as they were before' do
|
|
63
|
-
it { expect($t.foo.as_of($t.foo.ts[0]).name).to eq 'foo' }
|
|
64
|
-
it { expect($t.foo.as_of($t.foo.ts[1]).name).to eq 'foo bar' }
|
|
65
|
-
it { expect($t.foo.as_of($t.foo.ts[2]).name).to eq 'new foo' }
|
|
66
|
-
|
|
67
|
-
it { expect($t.bar.as_of($t.bar.ts[0]).name).to eq 'bar' }
|
|
68
|
-
it { expect($t.bar.as_of($t.bar.ts[1]).name).to eq 'foo bar' }
|
|
69
|
-
it { expect($t.bar.as_of($t.bar.ts[2]).name).to eq 'bar bar' }
|
|
70
|
-
it { expect($t.bar.as_of($t.bar.ts[3]).name).to eq 'new bar' }
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
describe 'takes care of associated records' do
|
|
75
|
-
it { expect($t.foo.as_of($t.foo.ts[0]).bars).to eq [] }
|
|
76
|
-
it { expect($t.foo.as_of($t.foo.ts[1]).bars).to eq [] }
|
|
77
|
-
it { expect($t.foo.as_of($t.foo.ts[2]).bars).to eq [$t.bar] }
|
|
78
|
-
|
|
79
|
-
it { expect($t.foo.as_of($t.foo.ts[2]).bars.first.name).to eq 'foo bar' }
|
|
80
|
-
|
|
81
|
-
it { expect($t.foo.as_of($t.bar.ts[0]).bars).to eq [$t.bar] }
|
|
82
|
-
it { expect($t.foo.as_of($t.bar.ts[1]).bars).to eq [$t.bar] }
|
|
83
|
-
it { expect($t.foo.as_of($t.bar.ts[2]).bars).to eq [$t.bar] }
|
|
84
|
-
it { expect($t.foo.as_of($t.bar.ts[3]).bars).to eq [$t.bar] }
|
|
85
|
-
|
|
86
|
-
it { expect($t.foo.as_of($t.bar.ts[0]).bars.first.name).to eq 'bar' }
|
|
87
|
-
it { expect($t.foo.as_of($t.bar.ts[1]).bars.first.name).to eq 'foo bar' }
|
|
88
|
-
it { expect($t.foo.as_of($t.bar.ts[2]).bars.first.name).to eq 'bar bar' }
|
|
89
|
-
it { expect($t.foo.as_of($t.bar.ts[3]).bars.first.name).to eq 'new bar' }
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
it { expect($t.bar.as_of($t.bar.ts[0]).foo).to eq $t.foo }
|
|
93
|
-
it { expect($t.bar.as_of($t.bar.ts[1]).foo).to eq $t.foo }
|
|
94
|
-
it { expect($t.bar.as_of($t.bar.ts[2]).foo).to eq $t.foo }
|
|
95
|
-
it { expect($t.bar.as_of($t.bar.ts[3]).foo).to eq $t.foo }
|
|
96
|
-
|
|
97
|
-
it { expect($t.bar.as_of($t.bar.ts[0]).foo.name).to eq 'foo bar' }
|
|
98
|
-
it { expect($t.bar.as_of($t.bar.ts[1]).foo.name).to eq 'foo bar' }
|
|
99
|
-
it { expect($t.bar.as_of($t.bar.ts[2]).foo.name).to eq 'new foo' }
|
|
100
|
-
it { expect($t.bar.as_of($t.bar.ts[3]).foo.name).to eq 'new foo' }
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
describe 'supports historical queries with includes()' do
|
|
105
|
-
it { expect(Foo.as_of($t.foo.ts[0]).includes(:bars).first.bars).to eq [] }
|
|
106
|
-
it { expect(Foo.as_of($t.foo.ts[1]).includes(:bars).first.bars).to eq [] }
|
|
107
|
-
it { expect(Foo.as_of($t.foo.ts[2]).includes(:bars).first.bars).to eq [$t.bar] }
|
|
108
|
-
|
|
109
|
-
it { expect(Foo.as_of($t.bar.ts[0]).includes(:bars).first.bars.first.name).to eq 'bar' }
|
|
110
|
-
it { expect(Foo.as_of($t.bar.ts[1]).includes(:bars).first.bars.first.name).to eq 'foo bar' }
|
|
111
|
-
it { expect(Foo.as_of($t.bar.ts[2]).includes(:bars).first.bars.first.name).to eq 'bar bar' }
|
|
112
|
-
it { expect(Foo.as_of($t.bar.ts[3]).includes(:bars).first.bars.first.name).to eq 'new bar' }
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
it { expect(Foo.as_of($t.foo.ts[0]).includes(bars: :sub_bars).first.bars).to eq [] }
|
|
116
|
-
it { expect(Foo.as_of($t.foo.ts[1]).includes(bars: :sub_bars).first.bars).to eq [] }
|
|
117
|
-
it { expect(Foo.as_of($t.foo.ts[2]).includes(bars: :sub_bars).first.bars).to eq [$t.bar] }
|
|
118
|
-
|
|
119
|
-
it { expect(Foo.as_of($t.bar.ts[0]).includes(bars: :sub_bars).first.bars.first.name).to eq 'bar' }
|
|
120
|
-
it { expect(Foo.as_of($t.bar.ts[1]).includes(bars: :sub_bars).first.bars.first.name).to eq 'foo bar' }
|
|
121
|
-
it { expect(Foo.as_of($t.bar.ts[2]).includes(bars: :sub_bars).first.bars.first.name).to eq 'bar bar' }
|
|
122
|
-
it { expect(Foo.as_of($t.bar.ts[3]).includes(bars: :sub_bars).first.bars.first.name).to eq 'new bar' }
|
|
123
|
-
|
|
124
|
-
it { expect(Bar.as_of($t.bar.ts[0]).includes(:foo).first.foo).to eq $t.foo }
|
|
125
|
-
it { expect(Bar.as_of($t.bar.ts[1]).includes(:foo).first.foo).to eq $t.foo }
|
|
126
|
-
it { expect(Bar.as_of($t.bar.ts[2]).includes(:foo).first.foo).to eq $t.foo }
|
|
127
|
-
it { expect(Bar.as_of($t.bar.ts[3]).includes(:foo).first.foo).to eq $t.foo }
|
|
128
|
-
|
|
129
|
-
it { expect(Bar.as_of($t.bar.ts[0]).includes(:foo).first.foo.name).to eq 'foo bar' }
|
|
130
|
-
it { expect(Bar.as_of($t.bar.ts[1]).includes(:foo).first.foo.name).to eq 'foo bar' }
|
|
131
|
-
it { expect(Bar.as_of($t.bar.ts[2]).includes(:foo).first.foo.name).to eq 'new foo' }
|
|
132
|
-
it { expect(Bar.as_of($t.bar.ts[3]).includes(:foo).first.foo.name).to eq 'new foo' }
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
it { expect(Bar.as_of($t.bar.ts[0]).includes(foo: :sub_bars).first.foo).to eq $t.foo }
|
|
136
|
-
it { expect(Bar.as_of($t.bar.ts[1]).includes(foo: :sub_bars).first.foo).to eq $t.foo }
|
|
137
|
-
it { expect(Bar.as_of($t.bar.ts[2]).includes(foo: :sub_bars).first.foo).to eq $t.foo }
|
|
138
|
-
it { expect(Bar.as_of($t.bar.ts[3]).includes(foo: :sub_bars).first.foo).to eq $t.foo }
|
|
139
|
-
|
|
140
|
-
it { expect(Bar.as_of($t.bar.ts[0]).includes(foo: :sub_bars).first.foo.name).to eq 'foo bar' }
|
|
141
|
-
it { expect(Bar.as_of($t.bar.ts[1]).includes(foo: :sub_bars).first.foo.name).to eq 'foo bar' }
|
|
142
|
-
it { expect(Bar.as_of($t.bar.ts[2]).includes(foo: :sub_bars).first.foo.name).to eq 'new foo' }
|
|
143
|
-
it { expect(Bar.as_of($t.bar.ts[3]).includes(foo: :sub_bars).first.foo.name).to eq 'new foo' }
|
|
144
|
-
|
|
145
|
-
it { expect(Foo.as_of($t.foo.ts[0]).includes(:bars, :sub_bars).first.sub_bars.count).to eq 0 }
|
|
146
|
-
it { expect(Foo.as_of($t.foo.ts[1]).includes(:bars, :sub_bars).first.sub_bars.count).to eq 0 }
|
|
147
|
-
it { expect(Foo.as_of($t.foo.ts[2]).includes(:bars, :sub_bars).first.sub_bars.count).to eq 1 }
|
|
148
|
-
|
|
149
|
-
it { expect(Foo.as_of($t.foo.ts[0]).includes(:bars, :sub_bars).first.sub_bars.first).to be nil }
|
|
150
|
-
it { expect(Foo.as_of($t.foo.ts[1]).includes(:bars, :sub_bars).first.sub_bars.first).to be nil }
|
|
151
|
-
|
|
152
|
-
it { expect(Foo.as_of($t.subbar.ts[0]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'sub-bar' }
|
|
153
|
-
it { expect(Foo.as_of($t.subbar.ts[1]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'bar sub-bar' }
|
|
154
|
-
it { expect(Foo.as_of($t.subbar.ts[2]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'sub-bar sub-bar' }
|
|
155
|
-
it { expect(Foo.as_of($t.subbar.ts[3]).includes(:bars, :sub_bars).first.sub_bars.first.name).to eq 'new sub-bar' }
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
it 'does not raise RecordNotFound when no history records are found' do
|
|
160
|
-
expect { $t.foo.as_of(1.minute.ago) }.to_not raise_error
|
|
161
|
-
|
|
162
|
-
expect($t.foo.as_of(1.minute.ago)).to be(nil)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
it 'raises ActiveRecord::RecordNotFound in the bang variant' do
|
|
167
|
-
expect { $t.foo.as_of!(1.minute.ago) }.to raise_error(ActiveRecord::RecordNotFound)
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
describe 'proxies from non-temporal models to temporal ones' do
|
|
172
|
-
it { expect($t.baz.as_of($t.bar.ts[0]).name).to eq 'baz' }
|
|
173
|
-
it { expect($t.baz.as_of($t.bar.ts[1]).name).to eq 'baz' }
|
|
174
|
-
it { expect($t.baz.as_of($t.bar.ts[2]).name).to eq 'baz' }
|
|
175
|
-
it { expect($t.baz.as_of($t.bar.ts[3]).name).to eq 'baz' }
|
|
176
|
-
|
|
177
|
-
it { expect($t.baz.as_of($t.bar.ts[0]).bar.name).to eq 'bar' }
|
|
178
|
-
it { expect($t.baz.as_of($t.bar.ts[1]).bar.name).to eq 'foo bar' }
|
|
179
|
-
it { expect($t.baz.as_of($t.bar.ts[2]).bar.name).to eq 'bar bar' }
|
|
180
|
-
it { expect($t.baz.as_of($t.bar.ts[3]).bar.name).to eq 'new bar' }
|
|
181
|
-
|
|
182
|
-
it { expect($t.baz.as_of($t.bar.ts[0]).bar.foo.name).to eq 'foo bar' }
|
|
183
|
-
it { expect($t.baz.as_of($t.bar.ts[1]).bar.foo.name).to eq 'foo bar' }
|
|
184
|
-
it { expect($t.baz.as_of($t.bar.ts[2]).bar.foo.name).to eq 'new foo' }
|
|
185
|
-
it { expect($t.baz.as_of($t.bar.ts[3]).bar.foo.name).to eq 'new foo' }
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
end
|
|
@@ -1,50 +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 '#last_changes' do
|
|
8
|
-
context 'on plain records' do
|
|
9
|
-
context 'having history' do
|
|
10
|
-
subject { $t.bar.last_changes }
|
|
11
|
-
it { is_expected.to eq('name' => ['bar bar', 'new bar']) }
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
context 'without history' do
|
|
15
|
-
let(:record) { Bar.create!(:name => 'foreveralone') }
|
|
16
|
-
subject { record.last_changes }
|
|
17
|
-
it { is_expected.to be_nil }
|
|
18
|
-
after { record.destroy.history.delete_all } # UGLY
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
context 'on history records' do
|
|
23
|
-
context 'at the beginning of the timeline' do
|
|
24
|
-
subject { $t.bar.history.first.last_changes }
|
|
25
|
-
it { is_expected.to be_nil }
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
context 'in the middle of the timeline' do
|
|
29
|
-
subject { $t.bar.history.second.last_changes }
|
|
30
|
-
it { is_expected.to eq('name' => ['bar', 'foo bar']) }
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
describe '#changes_against' do
|
|
36
|
-
context 'can compare records against history' do
|
|
37
|
-
it { expect($t.bar.changes_against($t.bar.history.first)).to eq('name' => ['bar', 'new bar']) }
|
|
38
|
-
it { expect($t.bar.changes_against($t.bar.history.second)).to eq('name' => ['foo bar', 'new bar']) }
|
|
39
|
-
it { expect($t.bar.changes_against($t.bar.history.third)).to eq('name' => ['bar bar', 'new bar']) }
|
|
40
|
-
it { expect($t.bar.changes_against($t.bar.history.last)).to eq({}) }
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
context 'can compare history against history' do
|
|
44
|
-
it { expect($t.bar.history.first. changes_against($t.bar.history.third)).to eq('name' => ['bar bar', 'bar']) }
|
|
45
|
-
it { expect($t.bar.history.second.changes_against($t.bar.history.third)).to eq('name' => ['bar bar', 'foo bar']) }
|
|
46
|
-
it { expect($t.bar.history.third. changes_against($t.bar.history.third)).to eq({}) }
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
end
|