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 CHANGED
@@ -1,5 +1,8 @@
1
1
  rvm:
2
2
  - 1.9.3
3
3
  - 2.0.0
4
+ env:
5
+ - SQLITE=1
6
+ - PG=1
4
7
  notifications:
5
8
  disabled: true
@@ -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
- e = :expired_at.qualify(j)
62
- (:created_at.qualify(j) <= t) & ({e=>nil} | (e > t)) & (:valid_from.qualify(j) <= n) & (:valid_to.qualify(j) > n)
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
- ds.where{(created_at <= t) & ({expired_at=>nil} | (expired_at > t)) & (valid_from <= n) & (valid_to > n)}
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({:id.qualify(model.current_version_alias) => nil}.sql_negate)
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
- e = :expired_at.qualify(j)
75
- (:created_at.qualify(j) <= t) & ({e=>nil} | (e > t)) & (:valid_to.qualify(j) > n)
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
- ds.where{(created_at <= t) & ({expired_at=>nil} | (expired_at > t)) & (valid_to > n)}
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({current_or_future_versions__id: nil}.sql_negate)
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
- (created_at <= t) & ({expired_at=>nil} | (expired_at > t)) &
234
- (valid_from <= n)
235
- end.order(:valid_to.desc, :created_at.desc).first
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
 
@@ -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.11"
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({current_or_future_versions__id: nil}.sql_negate & {price: 99}).all.first
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({current_or_future_versions__id: nil}.sql_negate & {price: 94}).all.first
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({current_or_future_versions__id: nil}.sql_negate).all.should be_empty
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
-