sequel_bitemporal 0.6.11 → 0.6.12
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +3 -0
- data/lib/sequel/plugins/bitemporal.rb +134 -12
- data/sequel_bitemporal.gemspec +2 -1
- data/spec/bitemporal_date_spec.rb +3 -7
- data/spec/bitemporal_date_with_range_spec.rb +785 -0
- data/spec/bitemporal_serialization_spec.rb +0 -1
- data/spec/bitemporal_time_spec.rb +3 -6
- data/spec/bitemporal_time_with_range_spec.rb +496 -0
- data/spec/spec_helper.rb +22 -1
- data/spec/support/db.rb +11 -7
- metadata +52 -14
- checksums.yaml +0 -15
data/.travis.yml
CHANGED
@@ -53,33 +53,80 @@ module Sequel
|
|
53
53
|
@audit_updated_by_method = opts.fetch(:audit_updated_by_method){ :updated_by }
|
54
54
|
@propagate_per_column = opts.fetch(:propagate_per_column, false)
|
55
55
|
@version_uses_string_nilifier = version.plugins.map(&:to_s).include? "Sequel::Plugins::StringNilifier"
|
56
|
+
@use_ranges = if opts[:ranges]
|
57
|
+
db = self.db
|
58
|
+
unless db.database_type==:postgres && db.server_version >= 90200
|
59
|
+
raise "Ranges require PostgreSQL 9.2"
|
60
|
+
end
|
61
|
+
true
|
62
|
+
else
|
63
|
+
false
|
64
|
+
end
|
56
65
|
end
|
57
66
|
master.one_to_many :versions, class: version, key: :master_id, graph_alias_base: master.versions_alias
|
58
67
|
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|
|
59
68
|
t = ::Sequel::Plugins::Bitemporal.point_in_time
|
60
69
|
n = ::Sequel::Plugins::Bitemporal.now
|
61
|
-
|
62
|
-
|
70
|
+
if master.use_ranges
|
71
|
+
master.existence_range_contains(t, j) & master.validity_range_contains(n, j)
|
72
|
+
else
|
73
|
+
e = Sequel.qualify j, :expired_at
|
74
|
+
(Sequel.qualify(j, :created_at) <= t) &
|
75
|
+
(Sequel.|({e=>nil}, e > t)) &
|
76
|
+
(Sequel.qualify(j, :valid_from) <= n) &
|
77
|
+
(Sequel.qualify(j, :valid_to) > n)
|
78
|
+
end
|
63
79
|
end) do |ds|
|
64
80
|
t = ::Sequel::Plugins::Bitemporal.point_in_time
|
65
81
|
n = ::Sequel::Plugins::Bitemporal.now
|
66
|
-
|
82
|
+
if model.use_ranges
|
83
|
+
ds.where(model.existence_range_contains(t) & model.validity_range_contains(n))
|
84
|
+
else
|
85
|
+
ds.where do
|
86
|
+
(created_at <= t) &
|
87
|
+
(Sequel.|({expired_at=>nil}, expired_at > t)) &
|
88
|
+
(valid_from <= n) &
|
89
|
+
(valid_to > n)
|
90
|
+
end
|
91
|
+
end
|
67
92
|
end
|
68
93
|
master.def_dataset_method :with_current_version do
|
69
|
-
eager_graph(:current_version).where(
|
94
|
+
eager_graph(:current_version).where(
|
95
|
+
Sequel.negate(
|
96
|
+
Sequel.qualify(model.current_version_alias, :id) => nil
|
97
|
+
)
|
98
|
+
)
|
70
99
|
end
|
71
100
|
master.one_to_many :current_or_future_versions, class: version, key: :master_id, :graph_block=>(proc do |j, lj, js|
|
72
101
|
t = ::Sequel::Plugins::Bitemporal.point_in_time
|
73
102
|
n = ::Sequel::Plugins::Bitemporal.now
|
74
|
-
|
75
|
-
|
103
|
+
if master.use_ranges
|
104
|
+
master.existence_range_contains(t, j) &
|
105
|
+
(Sequel.qualify(j, :valid_to) > n)
|
106
|
+
else
|
107
|
+
e = Sequel.qualify j, :expired_at
|
108
|
+
(Sequel.qualify(j, :created_at) <= t) &
|
109
|
+
Sequel.|({e=>nil}, e > t) &
|
110
|
+
(Sequel.qualify(j, :valid_to) > n)
|
111
|
+
end
|
76
112
|
end) do |ds|
|
77
113
|
t = ::Sequel::Plugins::Bitemporal.point_in_time
|
78
114
|
n = ::Sequel::Plugins::Bitemporal.now
|
79
|
-
|
115
|
+
if model.use_ranges
|
116
|
+
existence_conditions = model.existence_range_contains t, j
|
117
|
+
ds.where{ existence_conditions & (:valid_to > n) }
|
118
|
+
else
|
119
|
+
ds.where do
|
120
|
+
(created_at <= t) &
|
121
|
+
Sequel.|({expired_at=>nil}, expired_at > t) &
|
122
|
+
(valid_to > n)
|
123
|
+
end
|
124
|
+
end
|
80
125
|
end
|
81
126
|
master.def_dataset_method :with_current_or_future_versions do
|
82
|
-
eager_graph(:current_or_future_versions).where(
|
127
|
+
eager_graph(:current_or_future_versions).where(
|
128
|
+
Sequel.negate(current_or_future_versions__id: nil)
|
129
|
+
)
|
83
130
|
end
|
84
131
|
version.many_to_one :master, class: master, key: :master_id
|
85
132
|
version.class_eval do
|
@@ -112,7 +159,75 @@ module Sequel
|
|
112
159
|
module ClassMethods
|
113
160
|
attr_reader :version_class, :versions_alias, :current_version_alias,
|
114
161
|
:propagate_per_column, :audit_class, :audit_updated_by_method,
|
115
|
-
:version_uses_string_nilifier
|
162
|
+
:version_uses_string_nilifier, :use_ranges
|
163
|
+
|
164
|
+
def validity_range_type
|
165
|
+
@validity_range_type ||= begin
|
166
|
+
valid_from_infos = db.schema(
|
167
|
+
version_class.table_name
|
168
|
+
).detect do |column_name, _|
|
169
|
+
column_name==:valid_from
|
170
|
+
end
|
171
|
+
unless valid_from_infos
|
172
|
+
raise "Could not find valid_from column in #{version_class.table_name}"
|
173
|
+
end
|
174
|
+
case valid_from_infos.last[:db_type]
|
175
|
+
when "date"
|
176
|
+
:daterange
|
177
|
+
when "timestamp without time zone"
|
178
|
+
:tsrange
|
179
|
+
when "timestamp with time zone"
|
180
|
+
:tstzrange
|
181
|
+
else
|
182
|
+
raise "Don't know how to handle ranges for type: #{valid_from_infos[:db_type]}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def validity_cast_type
|
188
|
+
case validity_range_type
|
189
|
+
when :daterange
|
190
|
+
:date
|
191
|
+
when :tsrange, :tstzrange
|
192
|
+
:timestamp
|
193
|
+
else
|
194
|
+
raise "Don't know how to handle cast for range type: #{validity_range_type}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def existence_range(qualifier=nil)
|
199
|
+
created_at_column = :created_at
|
200
|
+
created_at_column = Sequel.qualify qualifier, created_at_column if qualifier
|
201
|
+
expired_at_column = :expired_at
|
202
|
+
expired_at_column = Sequel.qualify qualifier, expired_at_column if qualifier
|
203
|
+
Sequel.function(
|
204
|
+
:tsrange, created_at_column, expired_at_column, "[)"
|
205
|
+
).pg_range
|
206
|
+
end
|
207
|
+
|
208
|
+
def existence_range_contains(point_in_time, qualifier=nil)
|
209
|
+
existence_range(qualifier).contains(
|
210
|
+
Sequel.cast(point_in_time, :timestamp)
|
211
|
+
)
|
212
|
+
end
|
213
|
+
|
214
|
+
def validity_range(qualifier=nil)
|
215
|
+
valid_from_column = :valid_from
|
216
|
+
valid_from_column = Sequel.qualify qualifier, valid_from_column if qualifier
|
217
|
+
valid_to_column = :valid_to
|
218
|
+
valid_to_column = Sequel.qualify qualifier, valid_to_column if qualifier
|
219
|
+
|
220
|
+
Sequel.function(
|
221
|
+
validity_range_type, valid_from_column, valid_to_column, "[)"
|
222
|
+
).pg_range
|
223
|
+
end
|
224
|
+
|
225
|
+
def validity_range_contains(now, qualifier=nil)
|
226
|
+
validity_range(qualifier).contains(
|
227
|
+
Sequel.cast(now, validity_cast_type)
|
228
|
+
)
|
229
|
+
end
|
230
|
+
|
116
231
|
end
|
117
232
|
module DatasetMethods
|
118
233
|
end
|
@@ -229,10 +344,17 @@ module Sequel
|
|
229
344
|
return if new?
|
230
345
|
t = ::Sequel::Plugins::Bitemporal.point_in_time
|
231
346
|
n = ::Sequel::Plugins::Bitemporal.now
|
347
|
+
if use_ranges = self.class.use_ranges
|
348
|
+
range_conditions = self.class.existence_range_contains t
|
349
|
+
end
|
232
350
|
versions_dataset.where do
|
233
|
-
|
234
|
-
|
235
|
-
|
351
|
+
if use_ranges
|
352
|
+
range_conditions
|
353
|
+
else
|
354
|
+
(created_at <= t) &
|
355
|
+
Sequel.|({expired_at=>nil}, expired_at > t)
|
356
|
+
end & (valid_from <= n)
|
357
|
+
end.order(Sequel.desc(:valid_to), Sequel.desc(:created_at)).first
|
236
358
|
end
|
237
359
|
end
|
238
360
|
|
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.6.
|
6
|
+
s.version = "0.6.12"
|
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"
|
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.add_runtime_dependency "sequel", "~> 3.30"
|
19
19
|
|
20
20
|
s.add_development_dependency "sqlite3"
|
21
|
+
s.add_development_dependency "pg"
|
21
22
|
s.add_development_dependency "rspec", "~> 2.10.0"
|
22
23
|
s.add_development_dependency "timecop"
|
23
24
|
s.add_development_dependency "rake"
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe "Sequel::Plugins::Bitemporal" do
|
4
|
-
include DbHelpers
|
5
4
|
before :all do
|
6
5
|
db_setup
|
7
6
|
end
|
@@ -469,17 +468,17 @@ describe "Sequel::Plugins::Bitemporal" do
|
|
469
468
|
Timecop.freeze Date.today+1
|
470
469
|
master.update_attributes name: "Single Standard", price: 99
|
471
470
|
master.update_attributes name: "Single Standard", price: 94, valid_from: Date.today+2
|
472
|
-
res = @master_class.eager_graph(:current_or_future_versions).where(
|
471
|
+
res = @master_class.eager_graph(:current_or_future_versions).where(Sequel.negate(current_or_future_versions__id: nil) & {price: 99}).all.first
|
473
472
|
res.should be
|
474
473
|
res.current_or_future_versions.should have(1).item
|
475
474
|
res.current_or_future_versions.first.price.should == 99
|
476
|
-
res = @master_class.eager_graph(:current_or_future_versions).where(
|
475
|
+
res = @master_class.eager_graph(:current_or_future_versions).where(Sequel.negate(current_or_future_versions__id: nil) & {price: 94}).all.first
|
477
476
|
res.should be
|
478
477
|
res.current_or_future_versions.should have(1).item
|
479
478
|
res.current_or_future_versions.first.price.should == 94
|
480
479
|
Timecop.freeze Date.today+1
|
481
480
|
master.destroy
|
482
|
-
@master_class.eager_graph(:current_or_future_versions).where(
|
481
|
+
@master_class.eager_graph(:current_or_future_versions).where(Sequel.negate(current_or_future_versions__id: nil)).all.should be_empty
|
483
482
|
end
|
484
483
|
it "allows loading masters with current or future versions" do
|
485
484
|
master_destroyed = @master_class.new
|
@@ -636,7 +635,6 @@ describe "Sequel::Plugins::Bitemporal" do
|
|
636
635
|
end
|
637
636
|
|
638
637
|
describe "Sequel::Plugins::Bitemporal", "with audit" do
|
639
|
-
include DbHelpers
|
640
638
|
before :all do
|
641
639
|
@audit_class = Class.new do
|
642
640
|
def self.audit(*args); end
|
@@ -732,7 +730,6 @@ describe "Sequel::Plugins::Bitemporal", "with audit" do
|
|
732
730
|
end
|
733
731
|
end
|
734
732
|
describe "Sequel::Plugins::Bitemporal", "with audit, specifying how to get the author" do
|
735
|
-
include DbHelpers
|
736
733
|
before :all do
|
737
734
|
@audit_class = Class.new do
|
738
735
|
def self.audit(*args); end
|
@@ -785,4 +782,3 @@ describe "Sequel::Plugins::Bitemporal", "with audit, specifying how to get the a
|
|
785
782
|
master.update_attributes name: "King size", price: 98
|
786
783
|
end
|
787
784
|
end
|
788
|
-
|