sequel_bitemporal 0.6.11 → 0.6.12

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.
@@ -0,0 +1,785 @@
1
+ require "spec_helper"
2
+ if DbHelpers.pg?
3
+ describe "Sequel::Plugins::Bitemporal", "with ranges" do
4
+ before :all do
5
+ db_setup ranges: true
6
+ end
7
+ before do
8
+ Timecop.freeze 2009, 11, 28
9
+ end
10
+ after do
11
+ Timecop.return
12
+ end
13
+ it "checks version class is given" do
14
+ lambda{
15
+ @version_class.plugin :bitemporal
16
+ }.should raise_error Sequel::Error, "please specify version class to use for bitemporal plugin"
17
+ end
18
+ it "checks required columns are present" do
19
+ lambda{
20
+ @version_class.plugin :bitemporal, version_class: @master_class
21
+ }.should raise_error Sequel::Error, "bitemporal plugin requires the following missing columns on version class: master_id, valid_from, valid_to, created_at, expired_at"
22
+ end
23
+ it "propagates errors from version to master" do
24
+ master = @master_class.new
25
+ master.should be_valid
26
+ master.attributes = {name: "Single Standard"}
27
+ master.should_not be_valid
28
+ master.errors.should == {price: ["is required"]}
29
+ end
30
+ it "#update_attributes returns false instead of raising errors" do
31
+ master = @master_class.new
32
+ master.update_attributes(name: "Single Standard").should be_false
33
+ master.should be_new
34
+ master.errors.should == {price: ["is required"]}
35
+ master.update_attributes(price: 98).should be_true
36
+ end
37
+ it "allows creating a master and its first version in one step" do
38
+ master = @master_class.new
39
+ result = master.update_attributes name: "Single Standard", price: 98
40
+ result.should be_true
41
+ result.should == master
42
+ master.should_not be_new
43
+ master.should have_versions %Q{
44
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
45
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-28 | MAX DATE | true |
46
+ }
47
+ end
48
+ it "allows creating a new version in the past" do
49
+ master = @master_class.new
50
+ master.update_attributes name: "Single Standard", price: 98, valid_from: Date.today-1
51
+ master.should have_versions %Q{
52
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
53
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-27 | MAX DATE | true |
54
+ }
55
+ end
56
+ it "allows creating a new version in the future" do
57
+ master = @master_class.new
58
+ master.update_attributes name: "Single Standard", price: 98, valid_from: Date.today+1
59
+ master.should have_versions %Q{
60
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
61
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-29 | MAX DATE | |
62
+ }
63
+ end
64
+ it "doesn't loose previous version in same-day update" do
65
+ master = @master_class.new
66
+ master.update_attributes name: "Single Standard", price: 98
67
+ master.update_attributes name: "Single Standard", price: 94
68
+ master.should have_versions %Q{
69
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
70
+ | Single Standard | 98 | 2009-11-28 | 2009-11-28 | 2009-11-28 | MAX DATE | |
71
+ | Single Standard | 94 | 2009-11-28 | | 2009-11-28 | MAX DATE | true |
72
+ }
73
+ end
74
+ it "allows partial updating based on current version" do
75
+ master = @master_class.new
76
+ master.update_attributes name: "Single Standard", price: 98
77
+ master.update_attributes price: 94
78
+ master.update_attributes name: "King Size"
79
+ master.should have_versions %Q{
80
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
81
+ | Single Standard | 98 | 2009-11-28 | 2009-11-28 | 2009-11-28 | MAX DATE | |
82
+ | Single Standard | 94 | 2009-11-28 | 2009-11-28 | 2009-11-28 | MAX DATE | |
83
+ | King Size | 94 | 2009-11-28 | | 2009-11-28 | MAX DATE | true |
84
+ }
85
+ end
86
+ it "expires previous version but keep it in history" do
87
+ master = @master_class.new
88
+ master.update_attributes name: "Single Standard", price: 98
89
+ Timecop.freeze Date.today+1
90
+ master.update_attributes price: 94
91
+ master.should have_versions %Q{
92
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
93
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | MAX DATE | |
94
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
95
+ | Single Standard | 94 | 2009-11-29 | | 2009-11-29 | MAX DATE | true |
96
+ }
97
+ end
98
+ it "doesn't expire no longer valid versions" do
99
+ master = @master_class.new
100
+ master.update_attributes name: "Single Standard", price: 98, valid_to: Date.today+1
101
+ Timecop.freeze Date.today+1
102
+ master.update_attributes(price: 94).should be_false
103
+ master.update_attributes name: "Single Standard", price: 94
104
+ master.should have_versions %Q{
105
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
106
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-28 | 2009-11-29 | |
107
+ | Single Standard | 94 | 2009-11-29 | | 2009-11-29 | MAX DATE | true |
108
+ }
109
+ end
110
+ it "allows shortening validity (SEE COMMENTS FOR IMPROVEMENTS)" do
111
+ master = @master_class.new
112
+ master.update_attributes name: "Single Standard", price: 98
113
+ Timecop.freeze Date.today+1
114
+ master.update_attributes valid_to: Date.today+10
115
+ master.should have_versions %Q{
116
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
117
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | MAX DATE | |
118
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
119
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-29 | 2009-12-09 | true |
120
+ }
121
+ # would be even better if it could be:
122
+ # | name | price | created_at | expired_at | valid_from | valid_to | current |
123
+ # | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | 2009-11-30 | |
124
+ # | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-12-09 | true |
125
+ end
126
+ it "allows extending validity (SEE COMMENTS FOR IMPROVEMENTS)" do
127
+ master = @master_class.new
128
+ master.update_attributes name: "Single Standard", price: 98, valid_to: Date.today+2
129
+ Timecop.freeze Date.today+1
130
+ master.should have_versions %Q{
131
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
132
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-28 | 2009-11-30 | true |
133
+ }
134
+ master.update_attributes valid_to: nil
135
+ master.should have_versions %Q{
136
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
137
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | 2009-11-30 | |
138
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
139
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-29 | MAX DATE | true |
140
+ }
141
+ # would be even better if it could be:
142
+ # | name | price | created_at | expired_at | valid_from | valid_to | current |
143
+ # | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | 2009-11-30 | |
144
+ # | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | MAX DATE | true |
145
+ end
146
+ it "don't create any new version without change" do
147
+ master = @master_class.new
148
+ master.update_attributes name: "Single Standard", price: 98
149
+ master.update_attributes price: 98
150
+ master.update_attributes name: "Single Standard", price: 98
151
+ master.should have_versions %Q{
152
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
153
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-28 | MAX DATE | true |
154
+ }
155
+ end
156
+ it "change in validity still creates a new version (SEE COMMENTS FOR IMPROVEMENTS)" do
157
+ master = @master_class.new
158
+ master.update_attributes name: "Single Standard", price: 98
159
+ Timecop.freeze Date.today+1
160
+ master.update_attributes price: 98, valid_from: Date.today-2
161
+ master.update_attributes price: 98, valid_from: Date.today+1
162
+ master.should have_versions %Q{
163
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
164
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-28 | MAX DATE | true |
165
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-27 | 2009-11-28 | |
166
+ }
167
+ # would be even better if it could be:
168
+ # | name | price | created_at | expired_at | valid_from | valid_to | current |
169
+ # | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | MAX DATE | |
170
+ # | Single Standard | 98 | 2009-11-29 | | 2009-11-27 | MAX DATE | true |
171
+ end
172
+ it "overrides no future versions" do
173
+ master = @master_class.new
174
+ master.update_attributes name: "Single Standard", price: 98, valid_to: Date.today+2
175
+ master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2, valid_to: Date.today+4
176
+ master.update_attributes name: "Single Standard", price: 95, valid_from: Date.today+4, valid_to: Date.today+6
177
+ Timecop.freeze Date.today+1
178
+ master.update_attributes name: "King Size", valid_to: nil
179
+ master.should have_versions %Q{
180
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
181
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | 2009-11-30 | |
182
+ | Single Standard | 94 | 2009-11-28 | | 2009-11-30 | 2009-12-02 | |
183
+ | Single Standard | 95 | 2009-11-28 | | 2009-12-02 | 2009-12-04 | |
184
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
185
+ | King Size | 98 | 2009-11-29 | | 2009-11-29 | 2009-11-30 | true |
186
+ }
187
+ end
188
+ it "overrides multiple future versions" do
189
+ master = @master_class.new
190
+ master.update_attributes name: "Single Standard", price: 98, valid_to: Date.today+2
191
+ master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2, valid_to: Date.today+4
192
+ master.update_attributes name: "Single Standard", price: 95, valid_from: Date.today+4, valid_to: Date.today+6
193
+ Timecop.freeze Date.today+1
194
+ master.update_attributes name: "King Size", valid_to: Date.today+4
195
+ master.should have_versions %Q{
196
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
197
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | 2009-11-30 | |
198
+ | Single Standard | 94 | 2009-11-28 | 2009-11-29 | 2009-11-30 | 2009-12-02 | |
199
+ | Single Standard | 95 | 2009-11-28 | 2009-11-29 | 2009-12-02 | 2009-12-04 | |
200
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
201
+ | Single Standard | 95 | 2009-11-29 | | 2009-12-03 | 2009-12-04 | |
202
+ | King Size | 98 | 2009-11-29 | | 2009-11-29 | 2009-12-03 | true |
203
+ }
204
+ end
205
+ it "overrides all future versions" do
206
+ master = @master_class.new
207
+ master.update_attributes name: "Single Standard", price: 98, valid_to: Date.today+2
208
+ master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2, valid_to: Date.today+4
209
+ master.update_attributes name: "Single Standard", price: 95, valid_from: Date.today+4, valid_to: Date.today+6
210
+ Timecop.freeze Date.today+1
211
+ master.update_attributes name: "King Size", valid_to: Time.utc(9999)
212
+ master.should have_versions %Q{
213
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
214
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | 2009-11-30 | |
215
+ | Single Standard | 94 | 2009-11-28 | 2009-11-29 | 2009-11-30 | 2009-12-02 | |
216
+ | Single Standard | 95 | 2009-11-28 | 2009-11-29 | 2009-12-02 | 2009-12-04 | |
217
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
218
+ | King Size | 98 | 2009-11-29 | | 2009-11-29 | MAX DATE | true |
219
+ }
220
+ end
221
+ it "allows deleting current version" do
222
+ master = @master_class.new
223
+ master.update_attributes name: "Single Standard", price: 92, valid_from: Date.today-2, valid_to: Date.today
224
+ master.update_attributes name: "Single Standard", price: 98
225
+ master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2
226
+ Timecop.freeze Date.today+1
227
+ master.current_version.destroy.should be_true
228
+ master.should have_versions %Q{
229
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
230
+ | Single Standard | 92 | 2009-11-28 | | 2009-11-26 | 2009-11-28 | |
231
+ | Single Standard | 98 | 2009-11-28 | 2009-11-28 | 2009-11-28 | MAX DATE | |
232
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | 2009-11-30 | |
233
+ | Single Standard | 94 | 2009-11-28 | | 2009-11-30 | MAX DATE | |
234
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
235
+ }
236
+ end
237
+ it "allows deleting current version to restore the previous one" do
238
+ master = @master_class.new
239
+ master.update_attributes name: "Single Standard", price: 92, valid_from: Date.today-2, valid_to: Date.today
240
+ master.update_attributes name: "Single Standard", price: 98
241
+ master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2
242
+ Timecop.freeze Date.today+1
243
+ master.current_version.destroy(expand_previous_version: true).should be_true
244
+ master.should have_versions %Q{
245
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
246
+ | Single Standard | 92 | 2009-11-28 | | 2009-11-26 | 2009-11-28 | |
247
+ | Single Standard | 98 | 2009-11-28 | 2009-11-28 | 2009-11-28 | MAX DATE | |
248
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | 2009-11-30 | |
249
+ | Single Standard | 94 | 2009-11-28 | | 2009-11-30 | MAX DATE | |
250
+ | Single Standard | 92 | 2009-11-29 | | 2009-11-29 | 2009-11-30 | true |
251
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
252
+ }
253
+ end
254
+ it "allows deleting a future version" do
255
+ master = @master_class.new
256
+ master.update_attributes name: "Single Standard", price: 98
257
+ master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2
258
+ Timecop.freeze Date.today+1
259
+ master.versions.last.destroy.should be_true
260
+ master.should have_versions %Q{
261
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
262
+ | Single Standard | 98 | 2009-11-28 | 2009-11-28 | 2009-11-28 | MAX DATE | |
263
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | 2009-11-30 | |
264
+ | Single Standard | 94 | 2009-11-28 | 2009-11-29 | 2009-11-30 | MAX DATE | |
265
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | MAX DATE | true |
266
+ }
267
+ end
268
+ it "allows deleting a future version without expanding the current one" do
269
+ master = @master_class.new
270
+ master.update_attributes name: "Single Standard", price: 98
271
+ master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2
272
+ Timecop.freeze Date.today+1
273
+ master.versions.last.destroy(expand_previous_version: false).should be_true
274
+ master.should have_versions %Q{
275
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
276
+ | Single Standard | 98 | 2009-11-28 | 2009-11-28 | 2009-11-28 | MAX DATE | |
277
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-28 | 2009-11-30 | true |
278
+ | Single Standard | 94 | 2009-11-28 | 2009-11-29 | 2009-11-30 | MAX DATE | |
279
+ }
280
+ end
281
+ it "allows deleting all versions" do
282
+ master = @master_class.new
283
+ master.update_attributes name: "Single Standard", price: 98
284
+ master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2
285
+ Timecop.freeze Date.today+1
286
+ master.destroy.should be_true
287
+ master.should have_versions %Q{
288
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
289
+ | Single Standard | 98 | 2009-11-28 | 2009-11-28 | 2009-11-28 | MAX DATE | |
290
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | 2009-11-30 | |
291
+ | Single Standard | 94 | 2009-11-28 | 2009-11-29 | 2009-11-30 | MAX DATE | |
292
+ }
293
+ end
294
+ it "allows simultaneous updates without information loss" do
295
+ master = @master_class.new
296
+ master.update_attributes name: "Single Standard", price: 98
297
+ Timecop.freeze Date.today+1
298
+ master2 = @master_class.find id: master.id
299
+ master.update_attributes name: "Single Standard", price: 94
300
+ master2.update_attributes name: "Single Standard", price: 95
301
+ master.should have_versions %Q{
302
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
303
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | MAX DATE | |
304
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
305
+ | Single Standard | 94 | 2009-11-29 | 2009-11-29 | 2009-11-29 | MAX DATE | |
306
+ | Single Standard | 95 | 2009-11-29 | | 2009-11-29 | MAX DATE | true |
307
+ }
308
+ end
309
+ it "allows simultaneous cumulative updates" do
310
+ master = @master_class.new
311
+ master.update_attributes name: "Single Standard", price: 98
312
+ Timecop.freeze Date.today+1
313
+ master2 = @master_class.find id: master.id
314
+ master.update_attributes price: 94
315
+ master2.update_attributes name: "King Size"
316
+ master.should have_versions %Q{
317
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
318
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | MAX DATE | |
319
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
320
+ | Single Standard | 94 | 2009-11-29 | 2009-11-29 | 2009-11-29 | MAX DATE | |
321
+ | King Size | 94 | 2009-11-29 | | 2009-11-29 | MAX DATE | true |
322
+ }
323
+ end
324
+ it "can expire invalid versions" do
325
+ master = @master_class.new.update_attributes name: "Single Standard", price: 98
326
+ master.current_version.price = nil
327
+ master.current_version.should_not be_valid
328
+ master.current_version.save validate: false
329
+ Timecop.freeze Date.today+1
330
+ master.update_attributes price: 94
331
+ master.should have_versions %Q{
332
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
333
+ | Single Standard | | 2009-11-28 | 2009-11-29 | 2009-11-28 | MAX DATE | |
334
+ | Single Standard | | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
335
+ | Single Standard | 94 | 2009-11-29 | | 2009-11-29 | MAX DATE | true |
336
+ }
337
+ end
338
+ it "can propagate changes to future versions per column" do
339
+ propagate_per_column = @master_class.propagate_per_column
340
+ begin
341
+ @master_class.instance_variable_set :@propagate_per_column, true
342
+ master = @master_class.new
343
+ master.update_attributes name: "Single Standard", price: 12, length: nil, width: 1
344
+ initial_today = Date.today
345
+ Timecop.freeze initial_today+1 do
346
+ master.update_attributes valid_from: initial_today+4, name: "King Size", price: 15, length: 2, width: 2
347
+ end
348
+ Timecop.freeze initial_today+2 do
349
+ master.update_attributes valid_from: initial_today+3, length: 1, width: 1
350
+ end
351
+ Timecop.freeze initial_today+3 do
352
+ master.update_attributes valid_from: initial_today+2, length: 3, width: 4
353
+ end
354
+ master.should have_versions %Q{
355
+ | name | price | length | width | created_at | expired_at | valid_from | valid_to | current |
356
+ | Single Standard | 12 | | 1 | 2009-11-28 | 2009-11-29 | 2009-11-28 | MAX DATE | true |
357
+ | Single Standard | 12 | | 1 | 2009-11-29 | 2009-11-30 | 2009-11-28 | 2009-12-02 | |
358
+ | King Size | 15 | 2 | 2 | 2009-11-29 | | 2009-12-02 | MAX DATE | |
359
+ | Single Standard | 12 | | 1 | 2009-11-30 | 2009-12-01 | 2009-11-28 | 2009-12-01 | |
360
+ | Single Standard | 12 | 1 | 1 | 2009-11-30 | 2009-12-01 | 2009-12-01 | 2009-12-02 | |
361
+ | Single Standard | 12 | | 1 | 2009-12-01 | | 2009-11-28 | 2009-11-30 | |
362
+ | Single Standard | 12 | 3 | 4 | 2009-12-01 | | 2009-11-30 | 2009-12-01 | |
363
+ | Single Standard | 12 | 1 | 4 | 2009-12-01 | | 2009-12-01 | 2009-12-02 | |
364
+ }
365
+ ensure
366
+ @master_class.instance_variable_set :@propagate_per_column, propagate_per_column
367
+ end
368
+ end
369
+ it "allows eager loading with conditions on current version" do
370
+ master = @master_class.new
371
+ master.update_attributes name: "Single Standard", price: 98
372
+ master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2
373
+ @master_class.eager_graph(:current_version).where("rooms_current_version.id IS NOT NULL").first.should be
374
+ Timecop.freeze Date.today+1
375
+ master.destroy
376
+ @master_class.eager_graph(:current_version).where("rooms_current_version.id IS NOT NULL").first.should be_nil
377
+ end
378
+ it "allows loading masters with a current version" do
379
+ master_destroyed = @master_class.new
380
+ master_destroyed.update_attributes name: "Single Standard", price: 98
381
+ master_destroyed.destroy
382
+ master_with_current = @master_class.new
383
+ master_with_current.update_attributes name: "Single Standard", price: 94
384
+ master_with_future = @master_class.new
385
+ master_with_future.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2
386
+ @master_class.with_current_version.all.should have(1).item
387
+ end
388
+ it "gets pending or current version attributes" do
389
+ master = @master_class.new
390
+ master.attributes.should == {}
391
+ master.pending_version.should be_nil
392
+ master.current_version.should be_nil
393
+ master.name.should be_nil
394
+
395
+ master.pending_or_current_version.name.should be_nil
396
+ master.update_attributes name: "Single Standard", price: 98
397
+ master.attributes[:name].should == "Single Standard"
398
+ master.pending_version.should be_nil
399
+ master.pending_or_current_version.name.should == "Single Standard"
400
+ master.name.should == "Single Standard"
401
+
402
+ master.attributes = {name: "King Size"}
403
+ master.attributes[:name].should == "King Size"
404
+ master.pending_version.should be
405
+ master.pending_or_current_version.name.should == "King Size"
406
+ master.name.should == "King Size"
407
+ end
408
+ it "allows to go back in time" do
409
+ master = @master_class.new
410
+ master.update_attributes name: "Single Standard", price: 98, valid_to: Date.today+1
411
+ master.update_attributes name: "Single Standard", price: 95, valid_from: Date.today+1, valid_to: Date.today+2
412
+ master.update_attributes name: "Single Standard", price: 93, valid_from: Date.today+2, valid_to: Date.today+3
413
+ master.update_attributes name: "Single Standard", price: 91, valid_from: Date.today+3
414
+ Timecop.freeze Date.today+1
415
+ master.update_attributes price: 94
416
+ master.update_attributes price: 96, valid_from: Date.today+2
417
+ master.should have_versions %Q{
418
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
419
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-28 | 2009-11-29 | |
420
+ | Single Standard | 95 | 2009-11-28 | 2009-11-29 | 2009-11-29 | 2009-11-30 | |
421
+ | Single Standard | 93 | 2009-11-28 | | 2009-11-30 | 2009-12-01 | |
422
+ | Single Standard | 91 | 2009-11-28 | 2009-11-29 | 2009-12-01 | MAX DATE | |
423
+ | Single Standard | 94 | 2009-11-29 | | 2009-11-29 | 2009-11-30 | true |
424
+ | Single Standard | 96 | 2009-11-29 | | 2009-12-01 | MAX DATE | |
425
+ }
426
+ master.current_version.price.should == 94
427
+ Sequel::Plugins::Bitemporal.at(Date.today-1) do
428
+ master.current_version(true).price.should == 98
429
+ end
430
+ Sequel::Plugins::Bitemporal.at(Date.today+1) do
431
+ master.current_version(true).price.should == 93
432
+ end
433
+ Sequel::Plugins::Bitemporal.at(Date.today+2) do
434
+ master.current_version(true).price.should == 96
435
+ end
436
+ Sequel::Plugins::Bitemporal.as_we_knew_it(Date.today-1) do
437
+ master.current_version(true).price.should == 95
438
+ master.current_version.should be_current
439
+ Sequel::Plugins::Bitemporal.at(Date.today-1) do
440
+ master.current_version(true).price.should == 98
441
+ end
442
+ Sequel::Plugins::Bitemporal.at(Date.today+1) do
443
+ master.current_version(true).price.should == 93
444
+ end
445
+ Sequel::Plugins::Bitemporal.at(Date.today+2) do
446
+ master.current_version(true).price.should == 91
447
+ end
448
+ end
449
+ end
450
+ it "correctly reset time if failure when going back in time" do
451
+ before = Sequel::Plugins::Bitemporal.now
452
+ lambda do
453
+ Sequel::Plugins::Bitemporal.at(Date.today+2) do
454
+ raise StandardError, "error during back in time"
455
+ end
456
+ end.should raise_error StandardError
457
+ Sequel::Plugins::Bitemporal.now.should == before
458
+ lambda do
459
+ Sequel::Plugins::Bitemporal.as_we_knew_it(Date.today+2) do
460
+ raise StandardError, "error during back in time"
461
+ end
462
+ end.should raise_error StandardError
463
+ Sequel::Plugins::Bitemporal.now.should == before
464
+ end
465
+ it "allows eager loading with conditions on current or future versions" do
466
+ master = @master_class.new
467
+ master.update_attributes name: "Single Standard", price: 98
468
+ Timecop.freeze Date.today+1
469
+ master.update_attributes name: "Single Standard", price: 99
470
+ master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2
471
+ res = @master_class.eager_graph(:current_or_future_versions).where(Sequel.negate(current_or_future_versions__id: nil) & {price: 99}).all.first
472
+ res.should be
473
+ res.current_or_future_versions.should have(1).item
474
+ res.current_or_future_versions.first.price.should == 99
475
+ res = @master_class.eager_graph(:current_or_future_versions).where(Sequel.negate(current_or_future_versions__id: nil) & {price: 94}).all.first
476
+ res.should be
477
+ res.current_or_future_versions.should have(1).item
478
+ res.current_or_future_versions.first.price.should == 94
479
+ Timecop.freeze Date.today+1
480
+ master.destroy
481
+ @master_class.eager_graph(:current_or_future_versions).where(Sequel.negate(current_or_future_versions__id: nil)).all.should be_empty
482
+ end
483
+ it "allows loading masters with current or future versions" do
484
+ master_destroyed = @master_class.new
485
+ master_destroyed.update_attributes name: "Single Standard", price: 98
486
+ master_destroyed.destroy
487
+ master_with_current = @master_class.new
488
+ master_with_current.update_attributes name: "Single Standard", price: 94
489
+ master_with_future = @master_class.new
490
+ master_with_future.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2
491
+ @master_class.with_current_or_future_versions.all.should have(2).item
492
+ end
493
+ it "delegates attributes from master to pending_or_current_version" do
494
+ master = @master_class.new
495
+ master.name.should be_nil
496
+ master.update_attributes name: "Single Standard", price: 98
497
+ master.name.should == "Single Standard"
498
+ master.attributes = {name: "King Size"}
499
+ master.name.should == "King Size"
500
+ end
501
+ it "avoids delegation with option delegate: false" do
502
+ closure = @version_class
503
+ without_delegation_class = Class.new Sequel::Model do
504
+ set_dataset :rooms
505
+ plugin :bitemporal, version_class: closure, delegate: false
506
+ end
507
+ master = without_delegation_class.new
508
+ expect{ master.name }.to raise_error NoMethodError
509
+ end
510
+ it "get current_version association name from class name" do
511
+ class MyNameVersion < Sequel::Model
512
+ set_dataset :room_versions
513
+ end
514
+ class MyName < Sequel::Model
515
+ set_dataset :rooms
516
+ plugin :bitemporal, version_class: MyNameVersion
517
+ end
518
+ expect do
519
+ MyName.eager_graph(:current_version).where("my_name_current_version.id IS NOT NULL").first
520
+ end.not_to raise_error
521
+ Object.send :remove_const, :MyName
522
+ Object.send :remove_const, :MyNameVersion
523
+ end
524
+ it "can update master and current version at the same time" do
525
+ master = @master_class.new.update_attributes name: "Single Standard", price: 98
526
+ master.disabled = true
527
+ master.update_attributes price: 94
528
+ master.reload.disabled.should be_true
529
+ end
530
+ it "uses current version for partial_update even if valid_from is specified" do
531
+ master = @master_class.new
532
+ master.update_attributes name: "Single Standard", price: 98, valid_from: Date.today-2, valid_to: Date.today
533
+ master.update_attributes name: "Single Standard", price: 94
534
+ master.should have_versions %Q{
535
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
536
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-26 | 2009-11-28 | |
537
+ | Single Standard | 94 | 2009-11-28 | | 2009-11-28 | MAX DATE | true |
538
+ }
539
+ master.update_attributes name: "King Size", valid_from: Date.today-2
540
+ master.should have_versions %Q{
541
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
542
+ | Single Standard | 98 | 2009-11-28 | 2009-11-28 | 2009-11-26 | 2009-11-28 | |
543
+ | Single Standard | 94 | 2009-11-28 | | 2009-11-28 | MAX DATE | true |
544
+ | King Size | 94 | 2009-11-28 | | 2009-11-26 | 2009-11-28 | |
545
+ }
546
+ end
547
+ it "as_we_knew_it also allows creating and deleting at that time" do
548
+ master = @master_class.new
549
+ master.update_attributes name: "Single Standard", price: 98
550
+ Sequel::Plugins::Bitemporal.as_we_knew_it(Date.today+1) do
551
+ master.update_attributes name: "King Size"
552
+ end
553
+ master.should have_versions %Q{
554
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
555
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | MAX DATE | true |
556
+ | King Size | 98 | 2009-11-29 | | 2009-11-28 | MAX DATE | |
557
+ }
558
+ Sequel::Plugins::Bitemporal.as_we_knew_it(Date.today+2) do
559
+ master.current_version(true).destroy
560
+ end
561
+ master.should have_versions %Q{
562
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
563
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | MAX DATE | true |
564
+ | King Size | 98 | 2009-11-29 | 2009-11-30 | 2009-11-28 | MAX DATE | |
565
+ | King Size | 98 | 2009-11-30 | | 2009-11-28 | 2009-11-28 | |
566
+ }
567
+ end
568
+ it "combines as_we_knew_it and at to set valid_from" do
569
+ master = @master_class.new
570
+ master.update_attributes name: "Single Standard", price: 98, valid_from: Date.today-2, valid_to: Date.today
571
+ master.update_attributes name: "Single Standard", price: 94
572
+ master.should have_versions %Q{
573
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
574
+ | Single Standard | 98 | 2009-11-28 | | 2009-11-26 | 2009-11-28 | |
575
+ | Single Standard | 94 | 2009-11-28 | | 2009-11-28 | MAX DATE | true |
576
+ }
577
+ Sequel::Plugins::Bitemporal.as_we_knew_it(Date.today+1) do
578
+ Sequel::Plugins::Bitemporal.at(Date.today-1) do
579
+ master.update_attributes name: "King Size"
580
+ end
581
+ end
582
+ master.should have_versions %Q{
583
+ | name | price | created_at | expired_at | valid_from | valid_to | current |
584
+ | Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-26 | 2009-11-28 | |
585
+ | Single Standard | 94 | 2009-11-28 | | 2009-11-28 | MAX DATE | true |
586
+ | Single Standard | 98 | 2009-11-29 | | 2009-11-26 | 2009-11-27 | |
587
+ | King Size | 98 | 2009-11-29 | | 2009-11-27 | 2009-11-28 | |
588
+ }
589
+ end
590
+ context "#deleted?" do
591
+ subject{ @master_class.new }
592
+ it "is false unless persisted" do
593
+ subject.should_not be_deleted
594
+ end
595
+ it "is false when persisted with a current version" do
596
+ subject.update_attributes(name: "Single Standard", price: 94).should_not be_deleted
597
+ end
598
+ it "is true when persisted without a current version" do
599
+ subject.update_attributes(name: "Single Standard", price: 94, valid_from: Date.today+1).should be_deleted
600
+ end
601
+ end
602
+ context "#last_version" do
603
+ subject{ @master_class.new }
604
+ it "is nil unless persisted" do
605
+ subject.last_version.should be_nil
606
+ end
607
+ it "is current version when persisted with a current version" do
608
+ subject.update_attributes name: "Single Standard", price: 94
609
+ subject.last_version.should == subject.current_version
610
+ end
611
+ it "is nil with future version but no current version" do
612
+ subject.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+1
613
+ subject.last_version.should be_nil
614
+ end
615
+ it "is last version with previous version but no current version" do
616
+ subject.update_attributes name: "Single Standard", price: 94, valid_from: Date.today-2, valid_to: Date.today-1
617
+ subject.current_version.should be_nil
618
+ subject.last_version.should == subject.versions.last
619
+ end
620
+ end
621
+ context "#restore" do
622
+ subject{ @master_class.new }
623
+ it "make last version current" do
624
+ subject.update_attributes name: "Single Standard", price: 94, valid_from: Date.today-2, valid_to: Date.today-1
625
+ subject.restore
626
+ subject.current_version.should == subject.last_version
627
+ end
628
+ it "can add additional attributes to apply" do
629
+ subject.update_attributes name: "Single Standard", price: 94, valid_from: Date.today-2, valid_to: Date.today-1
630
+ subject.restore name: "New Standard"
631
+ subject.current_version.name.should == "New Standard"
632
+ subject.current_version.price.should == 94
633
+ end
634
+ end
635
+ end
636
+
637
+ describe "Sequel::Plugins::Bitemporal", "with audit and ranges" do
638
+ before :all do
639
+ @audit_class = Class.new do
640
+ def self.audit(*args); end
641
+ end
642
+ db_setup audit_class: @audit_class, ranges: true
643
+ end
644
+ before do
645
+ Timecop.freeze 2009, 11, 28
646
+ end
647
+ after do
648
+ Timecop.return
649
+ end
650
+ let(:author){ mock :author, audit_kind: "user" }
651
+ it "generates a new audit on creation" do
652
+ master = @master_class.new
653
+ master.should_receive(:updated_by).and_return author
654
+ @audit_class.should_receive(:audit).with(
655
+ master,
656
+ {},
657
+ hash_including({name: "Single Standard", price: 98}),
658
+ Date.today,
659
+ author
660
+ )
661
+ master.update_attributes name: "Single Standard", price: 98
662
+ end
663
+ it "generates a new audit on full update" do
664
+ master = @master_class.new
665
+ master.should_receive(:updated_by).twice.and_return author
666
+ master.update_attributes name: "Single Standard", price: 98
667
+ @audit_class.should_receive(:audit).with(
668
+ master,
669
+ hash_including({name: "Single Standard", price: 98}),
670
+ hash_including({name: "King size", price: 98}),
671
+ Date.today,
672
+ author
673
+ )
674
+ master.update_attributes name: "King size", price: 98
675
+ end
676
+ it "generates a new audit on partial update" do
677
+ master = @master_class.new
678
+ master.should_receive(:updated_by).twice.and_return author
679
+ master.update_attributes name: "Single Standard", price: 98
680
+ @audit_class.should_receive(:audit).with(
681
+ master,
682
+ hash_including({name: "Single Standard", price: 98}),
683
+ hash_including({name: "King size", price: 98}),
684
+ Date.today,
685
+ author
686
+ )
687
+ master.update_attributes name: "King size", price: 98
688
+ end
689
+ it "generate a new audit for each future version update when propagating changes" do
690
+ propagate_per_column = @master_class.propagate_per_column
691
+ begin
692
+ @master_class.instance_variable_set :@propagate_per_column, true
693
+ master = @master_class.new
694
+ master.should_receive(:updated_by).exactly(8).times.and_return author
695
+
696
+ master.update_attributes name: "Single Standard", price: 12, length: nil, width: 1
697
+ initial_today = Date.today
698
+ Timecop.freeze initial_today+1 do
699
+ Sequel::Plugins::Bitemporal.at initial_today+4 do
700
+ master.update_attributes valid_from: initial_today+4, name: "King Size", price: 15, length: 2, width: 2
701
+ end
702
+ end
703
+ Timecop.freeze initial_today+2 do
704
+ Sequel::Plugins::Bitemporal.at initial_today+3 do
705
+ master.update_attributes valid_from: initial_today+3, length: 1, width: 1
706
+ end
707
+ end
708
+ @audit_class.should_receive(:audit).with(
709
+ master,
710
+ hash_including({name: "Single Standard", price: 12, length: nil, width: 1}),
711
+ hash_including({name: "Single Standard", price: 12, length: 3, width: 4}),
712
+ initial_today+2,
713
+ author
714
+ )
715
+ @audit_class.should_receive(:audit).with(
716
+ master,
717
+ hash_including({name: "Single Standard", price: 12, length: 1, width: 1}),
718
+ hash_including({name: "Single Standard", price: 12, length: 1, width: 4}),
719
+ initial_today+3,
720
+ author
721
+ )
722
+ Timecop.freeze initial_today+3 do
723
+ Sequel::Plugins::Bitemporal.at initial_today+2 do
724
+ master.update_attributes valid_from: initial_today+2, length: 3, width: 4
725
+ end
726
+ end
727
+ ensure
728
+ @master_class.instance_variable_set :@propagate_per_column, propagate_per_column
729
+ end
730
+ end
731
+ end
732
+ describe "Sequel::Plugins::Bitemporal", "with audit, ranges, specifying how to get the author" do
733
+ before :all do
734
+ @audit_class = Class.new do
735
+ def self.audit(*args); end
736
+ end
737
+ db_setup audit_class: @audit_class, audit_updated_by_method: :author, ranges: true
738
+ end
739
+ let(:author){ mock :author, audit_kind: "user" }
740
+ before do
741
+ Timecop.freeze 2009, 11, 28
742
+ end
743
+ after do
744
+ Timecop.return
745
+ end
746
+ it "generates a new audit on creation" do
747
+ master = @master_class.new
748
+ master.should_receive(:author).and_return author
749
+ @audit_class.should_receive(:audit).with(
750
+ master,
751
+ {},
752
+ hash_including({name: "Single Standard", price: 98}),
753
+ Date.today,
754
+ author
755
+ )
756
+ master.update_attributes name: "Single Standard", price: 98
757
+ end
758
+ it "generates a new audit on full update" do
759
+ master = @master_class.new
760
+ master.should_receive(:author).twice.and_return author
761
+ master.update_attributes name: "Single Standard", price: 98
762
+ @audit_class.should_receive(:audit).with(
763
+ master,
764
+ hash_including({name: "Single Standard", price: 98}),
765
+ hash_including({name: "King size", price: 98}),
766
+ Date.today,
767
+ author
768
+ )
769
+ master.update_attributes name: "King size", price: 98
770
+ end
771
+ it "generates a new audit on partial update" do
772
+ master = @master_class.new
773
+ master.should_receive(:author).twice.and_return author
774
+ master.update_attributes name: "Single Standard", price: 98
775
+ @audit_class.should_receive(:audit).with(
776
+ master,
777
+ hash_including({name: "Single Standard", price: 98}),
778
+ hash_including({name: "King size", price: 98}),
779
+ Date.today,
780
+ author
781
+ )
782
+ master.update_attributes name: "King size", price: 98
783
+ end
784
+ end
785
+ end