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.
- 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
|
-
|