sequel_bitemporal 0.4.15 → 0.4.16
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.
- data/Gemfile +2 -0
- data/lib/sequel/plugins/bitemporal.rb +66 -10
- data/sequel_bitemporal.gemspec +1 -1
- data/spec/bitemporal_date_spec.rb +79 -6
- data/spec/spec_helper.rb +2 -1
- data/spec/support/db.rb +2 -0
- metadata +4 -4
data/Gemfile
CHANGED
@@ -18,7 +18,7 @@ module Sequel
|
|
18
18
|
end
|
19
19
|
|
20
20
|
THREAD_NOW_KEY = :sequel_plugins_bitemporal_now
|
21
|
-
def self.at(time)
|
21
|
+
def self.at(time)
|
22
22
|
previous = Thread.current[THREAD_NOW_KEY]
|
23
23
|
raise ArgumentError, "requires a block" unless block_given?
|
24
24
|
Thread.current[THREAD_NOW_KEY] = time.to_datetime
|
@@ -31,11 +31,14 @@ module Sequel
|
|
31
31
|
Thread.current[THREAD_NOW_KEY] || DateTime.now
|
32
32
|
end
|
33
33
|
|
34
|
+
def self.bitemporal_version_columns
|
35
|
+
@bitemporal_version_columns ||= [:master_id, :valid_from, :valid_to, :created_at, :expired_at]
|
36
|
+
end
|
37
|
+
|
34
38
|
def self.configure(master, opts = {})
|
35
39
|
version = opts[:version_class]
|
36
40
|
raise Error, "please specify version class to use for bitemporal plugin" unless version
|
37
|
-
|
38
|
-
missing = required - version.columns
|
41
|
+
missing = bitemporal_version_columns - version.columns
|
39
42
|
raise Error, "bitemporal plugin requires the following missing column#{"s" if missing.size>1} on version class: #{missing.join(", ")}" unless missing.empty?
|
40
43
|
master.instance_eval do
|
41
44
|
@version_class = version
|
@@ -44,6 +47,7 @@ module Sequel
|
|
44
47
|
@current_version_alias = "#{base_alias}_current_version".to_sym
|
45
48
|
@audit_class = opts[:audit_class]
|
46
49
|
@audit_updated_by_method = opts[:audit_updated_by_method] || :updated_by_id
|
50
|
+
@propagate_per_column = opts.fetch(:propagate_per_column, false)
|
47
51
|
end
|
48
52
|
master.one_to_many :versions, class: version, key: :master_id, graph_alias_base: master.versions_alias
|
49
53
|
master.one_to_one :current_version, class: version, key: :master_id, graph_alias_base: master.current_version_alias, :graph_block=>(proc do |j, lj, js|
|
@@ -91,7 +95,7 @@ module Sequel
|
|
91
95
|
end
|
92
96
|
end
|
93
97
|
unless opts[:delegate]==false
|
94
|
-
(version.columns-
|
98
|
+
(version.columns-bitemporal_version_columns-[:id]).each do |column|
|
95
99
|
master.class_eval <<-EOS
|
96
100
|
def #{column}
|
97
101
|
pending_or_current_version.#{column} if pending_or_current_version
|
@@ -102,6 +106,7 @@ module Sequel
|
|
102
106
|
end
|
103
107
|
module ClassMethods
|
104
108
|
attr_reader :version_class, :versions_alias, :current_version_alias
|
109
|
+
attr_reader :propagate_per_column
|
105
110
|
attr_reader :audit_class, :audit_updated_by_method
|
106
111
|
end
|
107
112
|
module DatasetMethods
|
@@ -140,8 +145,8 @@ module Sequel
|
|
140
145
|
end
|
141
146
|
|
142
147
|
def attributes=(attributes)
|
148
|
+
@current_version_values = current_version ? current_version.values : {}
|
143
149
|
if attributes.delete(:partial_update) && !@pending_version && !new? && current_version
|
144
|
-
@current_version_values = current_version.values if audited?
|
145
150
|
current_attributes = current_version.keys.inject({}) do |hash, key|
|
146
151
|
hash[key] = current_version.send key
|
147
152
|
hash
|
@@ -149,8 +154,6 @@ module Sequel
|
|
149
154
|
current_attributes.delete :valid_from
|
150
155
|
current_attributes.delete :valid_to
|
151
156
|
attributes = current_attributes.merge attributes
|
152
|
-
elsif audited? && !new? && current_version
|
153
|
-
@current_version_values = current_version.values
|
154
157
|
end
|
155
158
|
attributes.delete :id
|
156
159
|
@pending_version ||= model.version_class.new
|
@@ -242,8 +245,51 @@ module Sequel
|
|
242
245
|
versions_dataset.where(id: expired.collect(&:id)).update expired_at: pending_version.created_at
|
243
246
|
end
|
244
247
|
|
248
|
+
def propagate_changes_to_future_versions
|
249
|
+
return true unless self.class.propagate_per_column
|
250
|
+
lock!
|
251
|
+
futures = versions_dataset.where expired_at: nil
|
252
|
+
futures = futures.exclude "valid_from=valid_to"
|
253
|
+
futures = futures.exclude "valid_to<=?", pending_version.valid_from
|
254
|
+
futures = futures.where "valid_from>?", pending_version.valid_from
|
255
|
+
futures = futures.order(:valid_from).all
|
256
|
+
|
257
|
+
excluded_columns = Sequel::Plugins::Bitemporal.bitemporal_version_columns + [:id]
|
258
|
+
to_check_columns = self.class.version_class.columns - excluded_columns
|
259
|
+
updated_by = send(self.class.audit_updated_by_method) if audited?
|
260
|
+
previous_values = @current_version_values
|
261
|
+
current_version_values = pending_version.values
|
262
|
+
|
263
|
+
futures.each do |future_version|
|
264
|
+
attrs = {}
|
265
|
+
to_check_columns.each do |col|
|
266
|
+
if previous_values[col]==future_version[col] &&
|
267
|
+
previous_values[col]!=current_version_values[col]
|
268
|
+
attrs[col] = current_version_values[col]
|
269
|
+
end
|
270
|
+
end
|
271
|
+
if attrs.any?
|
272
|
+
propagated = save_propagated future_version, attrs
|
273
|
+
if !propagated.new? && audited?
|
274
|
+
self.class.audit_class.audit(
|
275
|
+
self,
|
276
|
+
future_version.values,
|
277
|
+
propagated.values,
|
278
|
+
propagated.valid_from,
|
279
|
+
send(self.class.audit_updated_by_method)
|
280
|
+
)
|
281
|
+
end
|
282
|
+
previous_values = future_version.values.dup
|
283
|
+
current_version_values = propagated.values
|
284
|
+
future_version.this.update :expired_at => Sequel::Plugins::Bitemporal.point_in_time
|
285
|
+
else
|
286
|
+
break
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
245
291
|
def save_pending_version
|
246
|
-
current_values_for_audit = @current_version_values || {}
|
292
|
+
current_values_for_audit = @current_version_values || {}
|
247
293
|
pending_version.valid_to ||= Time.utc 9999
|
248
294
|
success = add_version pending_version
|
249
295
|
if success
|
@@ -251,10 +297,10 @@ module Sequel
|
|
251
297
|
self,
|
252
298
|
current_values_for_audit,
|
253
299
|
pending_version.values,
|
254
|
-
pending_version.valid_from,
|
300
|
+
pending_version.valid_from,
|
255
301
|
send(self.class.audit_updated_by_method)
|
256
302
|
) if audited?
|
257
|
-
|
303
|
+
propagate_changes_to_future_versions
|
258
304
|
@pending_version = nil
|
259
305
|
end
|
260
306
|
success
|
@@ -267,6 +313,16 @@ module Sequel
|
|
267
313
|
fossil.send :set_values, expired_attributes.merge(attributes)
|
268
314
|
fossil.save validate: false
|
269
315
|
end
|
316
|
+
|
317
|
+
def save_propagated(version, attributes={})
|
318
|
+
propagated = model.version_class.new
|
319
|
+
version_attributes = version.values.dup
|
320
|
+
version_attributes.delete :id
|
321
|
+
version_attributes[:created_at] = Sequel::Plugins::Bitemporal.point_in_time
|
322
|
+
propagated.send :set_values, version_attributes.merge(attributes)
|
323
|
+
propagated.save validate: false
|
324
|
+
propagated
|
325
|
+
end
|
270
326
|
end
|
271
327
|
end
|
272
328
|
end
|
data/sequel_bitemporal.gemspec
CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "sequel_bitemporal"
|
6
|
-
s.version = "0.4.
|
6
|
+
s.version = "0.4.16"
|
7
7
|
s.authors = ["Joseph HALTER", "Jonathan TRON"]
|
8
8
|
s.email = ["joseph.halter@thetalentbox.com", "jonathan.tron@thetalentbox.com"]
|
9
9
|
s.homepage = "https://github.com/TalentBox/sequel_bitemporal"
|
@@ -312,6 +312,37 @@ describe "Sequel::Plugins::Bitemporal" do
|
|
312
312
|
| Single Standard | 94 | 2009-11-29 | | 2009-11-29 | MAX DATE | true |
|
313
313
|
}
|
314
314
|
end
|
315
|
+
it "can propagate changes to future versions per column" do
|
316
|
+
propagate_per_column = @master_class.propagate_per_column
|
317
|
+
begin
|
318
|
+
@master_class.instance_variable_set :@propagate_per_column, true
|
319
|
+
master = @master_class.new
|
320
|
+
master.update_attributes name: "Single Standard", price: 12, length: nil, width: 1
|
321
|
+
initial_today = Date.today
|
322
|
+
Timecop.freeze initial_today+1 do
|
323
|
+
master.update_attributes valid_from: initial_today+4, name: "King Size", price: 15, length: 2, width: 2, partial_update: true
|
324
|
+
end
|
325
|
+
Timecop.freeze initial_today+2 do
|
326
|
+
master.update_attributes valid_from: initial_today+3, length: 1, width: 1, partial_update: true
|
327
|
+
end
|
328
|
+
Timecop.freeze initial_today+3 do
|
329
|
+
master.update_attributes valid_from: initial_today+2, length: 3, width: 4, partial_update: true
|
330
|
+
end
|
331
|
+
master.should have_versions %Q{
|
332
|
+
| name | price | length | width | created_at | expired_at | valid_from | valid_to | current |
|
333
|
+
| Single Standard | 12 | | 1 | 2009-11-28 | 2009-11-29 | 2009-11-28 | MAX DATE | true |
|
334
|
+
| Single Standard | 12 | | 1 | 2009-11-29 | 2009-11-30 | 2009-11-28 | 2009-12-02 | |
|
335
|
+
| King Size | 15 | 2 | 2 | 2009-11-29 | | 2009-12-02 | MAX DATE | |
|
336
|
+
| Single Standard | 12 | | 1 | 2009-11-30 | 2009-12-01 | 2009-11-28 | 2009-12-01 | |
|
337
|
+
| Single Standard | 12 | 1 | 1 | 2009-11-30 | 2009-12-01 | 2009-12-01 | 2009-12-02 | |
|
338
|
+
| Single Standard | 12 | | 1 | 2009-12-01 | | 2009-11-28 | 2009-11-30 | |
|
339
|
+
| Single Standard | 12 | 3 | 4 | 2009-12-01 | | 2009-11-30 | 2009-12-01 | |
|
340
|
+
| Single Standard | 12 | 1 | 4 | 2009-12-01 | | 2009-12-01 | 2009-12-02 | |
|
341
|
+
}
|
342
|
+
ensure
|
343
|
+
@master_class.instance_variable_set :@propagate_per_column, propagate_per_column
|
344
|
+
end
|
345
|
+
end
|
315
346
|
it "allows eager loading with conditions on current version" do
|
316
347
|
master = @master_class.new
|
317
348
|
master.update_attributes name: "Single Standard", price: 98
|
@@ -532,7 +563,9 @@ end
|
|
532
563
|
describe "Sequel::Plugins::Bitemporal", "with audit" do
|
533
564
|
include DbHelpers
|
534
565
|
before :all do
|
535
|
-
@audit_class = Class.new
|
566
|
+
@audit_class = Class.new do
|
567
|
+
def self.audit(*args); end
|
568
|
+
end
|
536
569
|
db_setup audit_class: @audit_class
|
537
570
|
end
|
538
571
|
before do
|
@@ -556,7 +589,6 @@ describe "Sequel::Plugins::Bitemporal", "with audit" do
|
|
556
589
|
it "generates a new audit on full update" do
|
557
590
|
master = @master_class.new
|
558
591
|
master.should_receive(:updated_by_id).twice.and_return(updated_by_id = stub)
|
559
|
-
@audit_class.stub(:audit)
|
560
592
|
master.update_attributes name: "Single Standard", price: 98
|
561
593
|
@audit_class.should_receive(:audit).with(
|
562
594
|
master,
|
@@ -570,7 +602,6 @@ describe "Sequel::Plugins::Bitemporal", "with audit" do
|
|
570
602
|
it "generates a new audit on partial update" do
|
571
603
|
master = @master_class.new
|
572
604
|
master.should_receive(:updated_by_id).twice.and_return(updated_by_id = stub)
|
573
|
-
@audit_class.stub(:audit)
|
574
605
|
master.update_attributes name: "Single Standard", price: 98
|
575
606
|
@audit_class.should_receive(:audit).with(
|
576
607
|
master,
|
@@ -581,11 +612,55 @@ describe "Sequel::Plugins::Bitemporal", "with audit" do
|
|
581
612
|
)
|
582
613
|
master.update_attributes partial_update: true, name: "King size", price: 98
|
583
614
|
end
|
615
|
+
it "generate a new audit for each future version update when propagating changes" do
|
616
|
+
propagate_per_column = @master_class.propagate_per_column
|
617
|
+
begin
|
618
|
+
@master_class.instance_variable_set :@propagate_per_column, true
|
619
|
+
master = @master_class.new
|
620
|
+
master.should_receive(:updated_by_id).exactly(9).times.and_return(updated_by_id = stub)
|
621
|
+
|
622
|
+
master.update_attributes name: "Single Standard", price: 12, length: nil, width: 1
|
623
|
+
initial_today = Date.today
|
624
|
+
Timecop.freeze initial_today+1 do
|
625
|
+
Sequel::Plugins::Bitemporal.at initial_today+4 do
|
626
|
+
master.update_attributes valid_from: initial_today+4, name: "King Size", price: 15, length: 2, width: 2, partial_update: true
|
627
|
+
end
|
628
|
+
end
|
629
|
+
Timecop.freeze initial_today+2 do
|
630
|
+
Sequel::Plugins::Bitemporal.at initial_today+3 do
|
631
|
+
master.update_attributes valid_from: initial_today+3, length: 1, width: 1, partial_update: true
|
632
|
+
end
|
633
|
+
end
|
634
|
+
@audit_class.should_receive(:audit).with(
|
635
|
+
master,
|
636
|
+
hash_including({name: "Single Standard", price: 12, length: nil, width: 1}),
|
637
|
+
hash_including({name: "Single Standard", price: 12, length: 3, width: 4}),
|
638
|
+
initial_today+2,
|
639
|
+
updated_by_id
|
640
|
+
)
|
641
|
+
@audit_class.should_receive(:audit).with(
|
642
|
+
master,
|
643
|
+
hash_including({name: "Single Standard", price: 12, length: 1, width: 1}),
|
644
|
+
hash_including({name: "Single Standard", price: 12, length: 1, width: 4}),
|
645
|
+
initial_today+3,
|
646
|
+
updated_by_id
|
647
|
+
)
|
648
|
+
Timecop.freeze initial_today+3 do
|
649
|
+
Sequel::Plugins::Bitemporal.at initial_today+2 do
|
650
|
+
master.update_attributes valid_from: initial_today+2, length: 3, width: 4, partial_update: true
|
651
|
+
end
|
652
|
+
end
|
653
|
+
ensure
|
654
|
+
@master_class.instance_variable_set :@propagate_per_column, propagate_per_column
|
655
|
+
end
|
656
|
+
end
|
584
657
|
end
|
585
658
|
describe "Sequel::Plugins::Bitemporal", "with audit, specifying how to get the updated id" do
|
586
659
|
include DbHelpers
|
587
660
|
before :all do
|
588
|
-
@audit_class = Class.new
|
661
|
+
@audit_class = Class.new do
|
662
|
+
def self.audit(*args); end
|
663
|
+
end
|
589
664
|
db_setup audit_class: @audit_class, audit_updated_by_method: :author_id
|
590
665
|
end
|
591
666
|
before do
|
@@ -609,7 +684,6 @@ describe "Sequel::Plugins::Bitemporal", "with audit, specifying how to get the u
|
|
609
684
|
it "generates a new audit on full update" do
|
610
685
|
master = @master_class.new
|
611
686
|
master.should_receive(:author_id).twice.and_return(updated_by_id = stub)
|
612
|
-
@audit_class.stub(:audit)
|
613
687
|
master.update_attributes name: "Single Standard", price: 98
|
614
688
|
@audit_class.should_receive(:audit).with(
|
615
689
|
master,
|
@@ -623,7 +697,6 @@ describe "Sequel::Plugins::Bitemporal", "with audit, specifying how to get the u
|
|
623
697
|
it "generates a new audit on partial update" do
|
624
698
|
master = @master_class.new
|
625
699
|
master.should_receive(:author_id).twice.and_return(updated_by_id = stub)
|
626
|
-
@audit_class.stub(:audit)
|
627
700
|
master.update_attributes name: "Single Standard", price: 98
|
628
701
|
@audit_class.should_receive(:audit).with(
|
629
702
|
master,
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/db.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel_bitemporal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.16
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-07-
|
13
|
+
date: 2012-07-23 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: sequel
|
@@ -127,7 +127,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
127
127
|
version: '0'
|
128
128
|
segments:
|
129
129
|
- 0
|
130
|
-
hash: -
|
130
|
+
hash: -3018429499339951723
|
131
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
132
|
none: false
|
133
133
|
requirements:
|
@@ -136,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
136
|
version: '0'
|
137
137
|
segments:
|
138
138
|
- 0
|
139
|
-
hash: -
|
139
|
+
hash: -3018429499339951723
|
140
140
|
requirements: []
|
141
141
|
rubyforge_project:
|
142
142
|
rubygems_version: 1.8.24
|