activerecord-bitemporal 4.2.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97a21996b6ff931bf324653432cd481eac86731ee3de16445c98627987683639
4
- data.tar.gz: 34178dcfc6e5fe4cbd1f01554499f075d1be90208f05cc2d39a0039941d03607
3
+ metadata.gz: 25ef613d84eda786e3d04e90aeb9cc95b68887a84863200aa0622be9e89c73ea
4
+ data.tar.gz: 63920865d4f8f27703b6ffeaf4e0c38d297ea4bff22bf01a3884136875b2ccde
5
5
  SHA512:
6
- metadata.gz: da5f747f97ec3c16f14b65d592482b23cc8a0d8ba2d1c691dc98df8b5f84d5dfd8c92e8be67362f8b6be38b0bd584c2bfdd97f5169f9e4d91347a7286ad87770
7
- data.tar.gz: 2a1b5d95ac36a751943a87adcb9231b81633f61139a6ca38ca3d34859a7e9a2f647ce204b549eda50efca9cc48245d7aff7c21499397d4ef673d1f4c02de84a4
6
+ metadata.gz: 6fbe2830897e6c3f70f29431f591e8fa9cd332ae260e23ef3c23b7afff7259819bc54b8bb9de38d26c07a96c1cd3e93af5f5b7b559d2814b61b3cabfb3dcb2a6
7
+ data.tar.gz: 3e45d6a45de435475388a77a534521049b603bddc5910046fc5b8f4c2577ad25e75387f397014374d94b4d3c515c4e4456ff76c4673952f06b497446166a7927
data/.circleci/config.yml CHANGED
@@ -9,13 +9,14 @@ jobs:
9
9
  type: string
10
10
  docker:
11
11
  - image: ruby:<< parameters.ruby-version >>
12
+ environment:
13
+ BUNDLE_GEMFILE: gemfiles/<< parameters.gemfile >>.gemfile
12
14
  - image: cimg/postgres:11.16
13
15
  steps:
14
16
  - checkout
15
17
  - run: gem install bundler
16
18
  - run: bundle install
17
- - run: bundle exec appraisal << parameters.gemfile >> bundle install
18
- - run: bundle exec appraisal << parameters.gemfile >> rspec
19
+ - run: bundle exec rspec
19
20
 
20
21
  workflows:
21
22
  test:
@@ -24,16 +25,17 @@ workflows:
24
25
  matrix:
25
26
  parameters:
26
27
  ruby-version:
27
- - '2.7'
28
28
  - '3.0'
29
29
  - '3.1'
30
+ - '3.2'
31
+ - '3.3'
30
32
  gemfile:
31
- - rails-6.0
32
- - rails-6.1
33
- - rails-7.0
34
- - rails-main
33
+ - rails_6.1
34
+ - rails_7.0
35
+ - rails_7.1
36
+ - rails_main
35
37
  exclude:
36
- - ruby-version: '3.0'
37
- gemfile: rails-6.0
38
- - ruby-version: '3.1'
39
- gemfile: rails-6.0
38
+ - ruby-version: '3.2'
39
+ gemfile: rails_6.1
40
+ - ruby-version: '3.3'
41
+ gemfile: rails_6.1
data/Appraisals CHANGED
@@ -4,10 +4,6 @@ appraise "rails-main" do
4
4
  gem "rails", git: 'https://github.com/rails/rails.git', branch: "main"
5
5
  end
6
6
 
7
- appraise "rails-6.0" do
8
- gem "rails", "~> 6.0.2"
9
- end
10
-
11
7
  appraise "rails-6.1" do
12
8
  gem "rails", "~> 6.1.0"
13
9
  end
@@ -15,3 +11,7 @@ end
15
11
  appraise "rails-7.0" do
16
12
  gem "rails", "~> 7.0.1"
17
13
  end
14
+
15
+ appraise "rails-7.1" do
16
+ gem "rails", "~> 7.1.0"
17
+ end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.0.0
4
+
5
+ ### Breaking Changed
6
+
7
+ - [CI against Ruby 3.2, 3.3, Drop Ruby 2.7 and Rails 6.0 #150](https://github.com/kufu/activerecord-bitemporal/pull/150)
8
+
9
+ ### Added
10
+
11
+ - [Support date type of valid time #149](https://github.com/kufu/activerecord-bitemporal/pull/149)
12
+ - [Add support Rails 7.1 #151](https://github.com/kufu/activerecord-bitemporal/pull/151)
13
+ - [Add Persistence#bitemporal_at and ActiveRecord::Bitemporal.bitemporal_at #152](https://github.com/kufu/activerecord-bitemporal/pull/152)
14
+
15
+ ### Changed
16
+
17
+ ### Deprecated
18
+
19
+ ### Removed
20
+
21
+ ### Fixed
22
+
23
+ - [Fix deleted time order between associations #154](https://github.com/kufu/activerecord-bitemporal/pull/154)
24
+
25
+ ### Chores
26
+
27
+ - [Don't use appraisal in CI #145](https://github.com/kufu/activerecord-bitemporal/pull/145)
28
+ - [Remove code for Active Record 6.0 #153](https://github.com/kufu/activerecord-bitemporal/pull/153)
29
+
30
+ ## 4.3.0
31
+
32
+ ### Added
33
+
34
+ - [Add `previously_force_updated?` #142](https://github.com/kufu/activerecord-bitemporal/pull/142)
35
+
36
+ ### Changed
37
+
38
+ ### Deprecated
39
+
40
+ ### Removed
41
+
42
+ ### Fixed
43
+
44
+ ### Chores
45
+
46
+ - [Remove unneeded spec for Rails 5.x #143](https://github.com/kufu/activerecord-bitemporal/pull/143)
47
+
3
48
  ## 4.2.0
4
49
 
5
50
  ### Added
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.description = %q{Enable ActiveRecord models to be handled as BiTemporal Data Model.}
15
15
  spec.homepage = "https://github.com/kufu/activerecord-bitemporal"
16
16
  spec.license = "Apache 2.0"
17
- spec.required_ruby_version = ">= 2.7.0"
17
+ spec.required_ruby_version = ">= 3.0"
18
18
 
19
19
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
20
  f.match(%r{^(test|spec|features)/})
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.add_dependency "activerecord", ">= 6.0"
26
+ spec.add_dependency "activerecord", ">= 6.1"
27
27
 
28
28
  spec.add_development_dependency "bundler"
29
29
  spec.add_development_dependency "rake", "~> 13.0"
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
- gem "rails", "~> 6.0.2"
6
+ gem "rails", "~> 7.1.0"
7
7
 
8
8
  gemspec path: "../"
@@ -68,6 +68,10 @@ module ActiveRecord
68
68
  bitemporal_option[:valid_datetime]&.in_time_zone
69
69
  end
70
70
 
71
+ def valid_date
72
+ valid_datetime&.to_date
73
+ end
74
+
71
75
  def ignore_valid_datetime(&block)
72
76
  with_bitemporal_option(ignore_valid_datetime: true, valid_datetime: nil, &block)
73
77
  end
@@ -88,6 +92,14 @@ module ActiveRecord
88
92
  with_bitemporal_option(ignore_transaction_datetime: true, transaction_datetime: nil, &block)
89
93
  end
90
94
 
95
+ def bitemporal_at(datetime, &block)
96
+ transaction_at(datetime) { valid_at(datetime, &block) }
97
+ end
98
+
99
+ def bitemporal_at!(datetime, &block)
100
+ transaction_at!(datetime) { valid_at!(datetime, &block) }
101
+ end
102
+
91
103
  def merge_by(option)
92
104
  option_ = option.dup
93
105
  if bitemporal_option_storage[:force_valid_datetime]
@@ -224,6 +236,10 @@ module ActiveRecord
224
236
  with_bitemporal_option(transaction_datetime: datetime, &block)
225
237
  end
226
238
 
239
+ def bitemporal_at(datetime, &block)
240
+ transaction_at(datetime) { valid_at(datetime, &block) }
241
+ end
242
+
227
243
  def bitemporal_option_merge_with_association!(other)
228
244
  bitemporal_option_merge!(other)
229
245
 
@@ -238,6 +254,10 @@ module ActiveRecord
238
254
  bitemporal_option[:valid_datetime]&.in_time_zone
239
255
  end
240
256
 
257
+ def valid_date
258
+ valid_datetime&.to_date
259
+ end
260
+
241
261
  def transaction_datetime
242
262
  bitemporal_option[:transaction_datetime]&.in_time_zone
243
263
  end
@@ -277,12 +297,13 @@ module ActiveRecord
277
297
  end
278
298
 
279
299
  refine ActiveRecord::Base do
280
- # MEMO: Do not copy `swapped_id`
300
+ # MEMO: Do not copy bitemporal internal status
281
301
  def dup(*)
282
302
  super.tap { |itself|
283
303
  itself.instance_exec do
284
304
  @_swapped_id_previously_was = nil
285
305
  @_swapped_id = nil
306
+ @previously_force_updated = false
286
307
  end unless itself.frozen?
287
308
  }
288
309
  end
@@ -320,6 +341,7 @@ module ActiveRecord
320
341
  before_instance&.save_without_bitemporal_callbacks!(validate: false)
321
342
  # NOTE: after_instance always exists
322
343
  after_instance.save_without_bitemporal_callbacks!(validate: false)
344
+ @previously_force_updated = self.force_update?
323
345
 
324
346
  # update 後に新しく生成したインスタンスのデータを移行する
325
347
  @_swapped_id_previously_was = swapped_id
@@ -334,17 +356,19 @@ module ActiveRecord
334
356
  end || false
335
357
  end
336
358
 
337
- def destroy(force_delete: false, operated_at: Time.current)
359
+ def destroy(force_delete: false, operated_at: nil)
338
360
  return super() if force_delete
339
361
 
340
- target_datetime = valid_datetime || operated_at
341
-
342
- duplicated_instance = self.class.find_at_time(target_datetime, self.id).dup
343
-
344
362
  ActiveRecord::Base.transaction(requires_new: true, joinable: false) do
345
363
  @destroyed = false
346
364
  _run_destroy_callbacks {
365
+ operated_at ||= Time.current
366
+ target_datetime = valid_datetime || operated_at
367
+
368
+ duplicated_instance = self.class.find_at_time(target_datetime, self.id).dup
369
+
347
370
  @destroyed = update_transaction_to(operated_at)
371
+ @previously_force_updated = force_update?
348
372
 
349
373
  # force_update の場合は削除時の状態の履歴を残さない
350
374
  unless force_update?
@@ -372,6 +396,18 @@ module ActiveRecord
372
396
  false
373
397
  end
374
398
 
399
+ if Gem::Version.new("7.1.0") <= ActiveRecord.version
400
+ # MEMO: Since Rails 7.1 #_find_record refers to a record with find_by!(@primary_key => id)
401
+ # But if @primary_key is "id", it can't refer to the intended record, so we hack it to refer to the record based on self.class.bitemporal_id_key
402
+ # see: https://github.com/rails/rails/blob/v7.1.0/activerecord/lib/active_record/persistence.rb#L1152-#L1171
403
+ def _find_record(*)
404
+ tmp_primary_key, @primary_key = @primary_key, self.class.bitemporal_id_key
405
+ super
406
+ ensure
407
+ @primary_key = tmp_primary_key
408
+ end
409
+ end
410
+
375
411
  module ::ActiveRecord::Persistence
376
412
  # MEMO: Must be override ActiveRecord::Persistence#reload
377
413
  alias_method :active_record_bitemporal_original_reload, :reload unless method_defined? :active_record_bitemporal_original_reload
@@ -397,9 +433,10 @@ module ActiveRecord
397
433
  # NOTE: Hook to copying swapped_id
398
434
  @_swapped_id_previously_was = nil
399
435
  @_swapped_id = fresh_object.swapped_id
436
+ @previously_force_updated = false
400
437
  self
401
438
  end
402
- elsif Gem::Version.new("6.1.0") <= ActiveRecord.version
439
+ else
403
440
  def reload(options = nil)
404
441
  return active_record_bitemporal_original_reload(options) unless self.class.bi_temporal_model?
405
442
 
@@ -420,28 +457,7 @@ module ActiveRecord
420
457
  # NOTE: Hook to copying swapped_id
421
458
  @_swapped_id_previously_was = nil
422
459
  @_swapped_id = fresh_object.swapped_id
423
- self
424
- end
425
- else
426
- def reload(options = nil)
427
- return active_record_bitemporal_original_reload(options) unless self.class.bi_temporal_model?
428
-
429
- self.class.connection.clear_query_cache
430
-
431
- fresh_object =
432
- ActiveRecord::Bitemporal.with_bitemporal_option(**bitemporal_option) {
433
- if options && options[:lock]
434
- self.class.unscoped { self.class.lock(options[:lock]).bitemporal_default_scope.find(id) }
435
- else
436
- self.class.unscoped { self.class.bitemporal_default_scope.find(id) }
437
- end
438
- }
439
-
440
- @attributes = fresh_object.instance_variable_get("@attributes")
441
- @new_record = false
442
- # NOTE: Hook to copying swapped_id
443
- @_swapped_id_previously_was = nil
444
- @_swapped_id = fresh_object.swapped_id
460
+ @previously_force_updated = false
445
461
  self
446
462
  end
447
463
  end
@@ -573,10 +589,13 @@ module ActiveRecord
573
589
  valid_from = record.valid_from if record.force_update?
574
590
 
575
591
  valid_to = record.valid_to.yield_self { |valid_to|
592
+ # NOTE: `cover?` may give incorrect results, when the time zone is not UTC and `valid_from` is date type
593
+ # Therefore, cast to type of `valid_from`
594
+ record_valid_time = finder_class.type_for_attribute(:valid_from).cast(record.valid_datetime)
576
595
  # レコードを更新する時に valid_datetime が valid_from ~ valid_to の範囲外だった場合、
577
596
  # 一番近い未来の履歴レコードを参照して更新する
578
597
  # という仕様があるため、それを考慮して valid_to を設定する
579
- if (record.valid_datetime && (record.valid_from..record.valid_to).cover?(record.valid_datetime)) == false && (record.persisted?)
598
+ if (record_valid_time && (record.valid_from..record.valid_to).cover?(record_valid_time)) == false && (record.persisted?)
580
599
  finder_class.ignore_valid_datetime.where(bitemporal_id: record.bitemporal_id).valid_from_gt(target_datetime).order(valid_from: :asc).first.valid_from
581
600
  else
582
601
  valid_to
@@ -47,26 +47,9 @@ module ActiveRecord::Bitemporal
47
47
  end
48
48
  }
49
49
 
50
- def each_operatable_node_6_0(nodes = predicates, &block)
50
+ def each_operatable_node(nodes = predicates, &block)
51
51
  if block
52
52
  each_operatable_node(nodes).each(&block)
53
- else
54
- Enumerator.new { |y|
55
- Array(nodes).each { |node|
56
- case node
57
- when Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
58
- y << node if node && node.left.respond_to?(:relation)
59
- when Arel::Nodes::Grouping
60
- each_operatable_node(node.expr) { |node| y << node }
61
- end
62
- }
63
- }
64
- end
65
- end
66
-
67
- def each_operatable_node_6_1(nodes = predicates, &block)
68
- if block
69
- each_operatable_node_6_1(nodes).each(&block)
70
53
  else
71
54
  Enumerator.new { |y|
72
55
  Array(nodes).each { |node|
@@ -74,26 +57,18 @@ module ActiveRecord::Bitemporal
74
57
  when Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
75
58
  y << node if node && node.left.respond_to?(:relation)
76
59
  when Arel::Nodes::And
77
- each_operatable_node_6_1(node.children) { |node| y << node }
60
+ each_operatable_node(node.children) { |node| y << node }
78
61
  when Arel::Nodes::Binary
79
- each_operatable_node_6_1(node.left) { |node| y << node }
80
- each_operatable_node_6_1(node.right) { |node| y << node }
62
+ each_operatable_node(node.left) { |node| y << node }
63
+ each_operatable_node(node.right) { |node| y << node }
81
64
  when Arel::Nodes::Unary
82
- each_operatable_node_6_1(node.expr) { |node| y << node }
65
+ each_operatable_node(node.expr) { |node| y << node }
83
66
  end
84
67
  }
85
68
  }
86
69
  end
87
70
  end
88
71
 
89
- def each_operatable_node(nodes = predicates, &block)
90
- if Gem::Version.new("6.1.0") <= ActiveRecord.version
91
- each_operatable_node_6_1(nodes, &block)
92
- else
93
- each_operatable_node_6_0(nodes, &block)
94
- end
95
- end
96
-
97
72
  def bitemporal_query_hash(*names)
98
73
  each_operatable_node
99
74
  .select { |node| names.include? node.left.name.to_s }
@@ -126,31 +101,6 @@ module ActiveRecord::Bitemporal
126
101
  end
127
102
  }
128
103
 
129
- if ActiveRecord.version < Gem::Version.new("6.1.0")
130
- class WhereClauseWithCheckTable < ActiveRecord::Relation::WhereClause
131
- using NodeBitemporalInclude
132
-
133
- def bitemporal_include?(column)
134
- !!predicates.grep(::Arel::Nodes::Node).find do |node|
135
- node.bitemporal_include?(column)
136
- end
137
- end
138
-
139
- private
140
-
141
- def except_predicates(columns)
142
- columns = Array(columns)
143
- predicates.reject do |node|
144
- ::Arel::Nodes::Node === node && node.bitemporal_include?(*columns)
145
- end
146
- end
147
- end
148
-
149
- def where_clause
150
- WhereClauseWithCheckTable.new(super.send(:predicates))
151
- end
152
- end
153
-
154
104
  module MergeWithExceptBitemporalDefaultScope
155
105
  using BitemporalChecker
156
106
  def merge(other)
@@ -166,6 +116,10 @@ module ActiveRecord::Bitemporal
166
116
  bitemporal_clause[:valid_datetime]&.in_time_zone
167
117
  end
168
118
 
119
+ def valid_date
120
+ valid_datetime&.to_date
121
+ end
122
+
169
123
  def transaction_datetime
170
124
  bitemporal_clause[:transaction_datetime]&.in_time_zone
171
125
  end
@@ -239,118 +193,80 @@ module ActiveRecord::Bitemporal
239
193
  end
240
194
  }
241
195
 
242
- if ActiveRecord.version < Gem::Version.new("6.1.0")
243
- module ActiveRecordRelationScope
244
- refine ::ActiveRecord::Relation do
245
- %i(valid_from valid_to transaction_from transaction_to).each { |column|
246
- module_eval <<-STR, __FILE__, __LINE__ + 1
247
- def _ignore_#{column}
248
- unscope(where: "\#{table.name}.#{column}")
249
- .tap { |relation| relation.merge!(bitemporal_value[:through].unscoped._ignore_#{column}) if bitemporal_value[:through] }
250
- end
251
-
252
- def _except_#{column}
253
- return self unless where_clause.bitemporal_include?("\#{table.name}.#{column}")
254
- all._ignore_#{column}.tap { |relation|
255
- relation.unscope_values.delete({ where: "\#{table.name}.#{column}" })
256
- }
257
- end
258
- STR
259
-
260
- [
261
- :lt, # column < datetime
262
- :lteq, # column <= datetime
263
- :gt, # column > datetime
264
- :gteq # column >= datetime
265
- ].each { |op|
266
- module_eval <<-STR, __FILE__, __LINE__ + 1
267
- def _#{column}_#{op}(datetime, without_ignore: false)
268
- target_datetime = datetime&.in_time_zone || Time.current
269
- relation = self.tap { |relation| break relation._ignore_#{column} unless without_ignore }
270
- relation.bitemporal_where_bind!(:#{column}, :#{op}, target_datetime)
271
- .tap { |relation| relation.merge!(bitemporal_value[:through].unscoped._#{column}_#{op}(target_datetime)) if bitemporal_value[:through] }
272
- end
273
- STR
274
- }
275
- }
276
- end
277
- end
278
- else
279
- module ActiveRecordRelationScope
280
- module EqualAttributeName
281
- refine ::Object do
282
- def equal_attribute_name(*)
283
- false
284
- end
196
+ module ActiveRecordRelationScope
197
+ module EqualAttributeName
198
+ refine ::Object do
199
+ def equal_attribute_name(*)
200
+ false
285
201
  end
286
- refine ::Hash do
287
- def equal_attribute_name(other)
288
- self[:where].equal_attribute_name(other)
289
- end
202
+ end
203
+ refine ::Hash do
204
+ def equal_attribute_name(other)
205
+ self[:where].equal_attribute_name(other)
290
206
  end
291
- refine ::Array do
292
- def equal_attribute_name(other)
293
- first.equal_attribute_name(other)
294
- end
207
+ end
208
+ refine ::Array do
209
+ def equal_attribute_name(other)
210
+ first.equal_attribute_name(other)
295
211
  end
296
- refine ::String do
297
- def equal_attribute_name(other)
298
- self == other.to_s
299
- end
212
+ end
213
+ refine ::String do
214
+ def equal_attribute_name(other)
215
+ self == other.to_s
300
216
  end
301
- refine ::Symbol do
302
- def equal_attribute_name(other)
303
- self.to_s == other.to_s
304
- end
217
+ end
218
+ refine ::Symbol do
219
+ def equal_attribute_name(other)
220
+ self.to_s == other.to_s
305
221
  end
306
- refine ::Arel::Attributes::Attribute do
307
- def equal_attribute_name(other)
308
- "#{relation.name}.#{name}" == other.to_s
309
- end
222
+ end
223
+ refine ::Arel::Attributes::Attribute do
224
+ def equal_attribute_name(other)
225
+ "#{relation.name}.#{name}" == other.to_s
310
226
  end
311
227
  end
228
+ end
312
229
 
313
- refine ::ActiveRecord::Relation do
314
- using EqualAttributeName
315
- using NodeBitemporalInclude
230
+ refine ::ActiveRecord::Relation do
231
+ using EqualAttributeName
232
+ using NodeBitemporalInclude
316
233
 
317
- def bitemporal_rewhere_bind(attr_name, operator, value, table = self.table)
318
- rewhere(table[attr_name].public_send(operator, predicate_builder.build_bind_attribute(attr_name, value)))
319
- end
234
+ def bitemporal_rewhere_bind(attr_name, operator, value, table = self.table)
235
+ rewhere(table[attr_name].public_send(operator, predicate_builder.build_bind_attribute(attr_name, value)))
236
+ end
320
237
 
321
- %i(valid_from valid_to transaction_from transaction_to).each { |column|
322
- module_eval <<-STR, __FILE__, __LINE__ + 1
323
- def _ignore_#{column}
324
- unscope(where: :"\#{table.name}.#{column}")
325
- .tap { |relation| relation.unscope!(where: bitemporal_value[:through].arel_table["#{column}"]) if bitemporal_value[:through] }
326
- end
238
+ %i(valid_from valid_to transaction_from transaction_to).each { |column|
239
+ module_eval <<-STR, __FILE__, __LINE__ + 1
240
+ def _ignore_#{column}
241
+ unscope(where: :"\#{table.name}.#{column}")
242
+ .tap { |relation| relation.unscope!(where: bitemporal_value[:through].arel_table["#{column}"]) if bitemporal_value[:through] }
243
+ end
327
244
 
328
- def _except_#{column}
329
- return self unless where_clause.send(:predicates).find { |node|
330
- node.bitemporal_include?("\#{table.name}.#{column}")
331
- }
332
- _ignore_#{column}.tap { |relation|
333
- relation.unscope_values.reject! { |query| query.equal_attribute_name("\#{table.name}.#{column}") }
334
- }
245
+ def _except_#{column}
246
+ return self unless where_clause.send(:predicates).find { |node|
247
+ node.bitemporal_include?("\#{table.name}.#{column}")
248
+ }
249
+ _ignore_#{column}.tap { |relation|
250
+ relation.unscope_values.reject! { |query| query.equal_attribute_name("\#{table.name}.#{column}") }
251
+ }
252
+ end
253
+ STR
254
+
255
+ [
256
+ :lt, # column < datetime
257
+ :lteq, # column <= datetime
258
+ :gt, # column > datetime
259
+ :gteq # column >= datetime
260
+ ].each { |op|
261
+ module_eval <<-STR, __FILE__, __LINE__ + 1
262
+ def _#{column}_#{op}(datetime,**)
263
+ target_datetime = datetime&.in_time_zone || Time.current
264
+ bitemporal_rewhere_bind("#{column}", :#{op}, target_datetime)
265
+ .tap { |relation| break relation.bitemporal_rewhere_bind("#{column}", :#{op}, target_datetime, bitemporal_value[:through].arel_table) if bitemporal_value[:through] }
335
266
  end
336
267
  STR
337
-
338
- [
339
- :lt, # column < datetime
340
- :lteq, # column <= datetime
341
- :gt, # column > datetime
342
- :gteq # column >= datetime
343
- ].each { |op|
344
- module_eval <<-STR, __FILE__, __LINE__ + 1
345
- def _#{column}_#{op}(datetime,**)
346
- target_datetime = datetime&.in_time_zone || Time.current
347
- bitemporal_rewhere_bind("#{column}", :#{op}, target_datetime)
348
- .tap { |relation| break relation.bitemporal_rewhere_bind("#{column}", :#{op}, target_datetime, bitemporal_value[:through].arel_table) if bitemporal_value[:through] }
349
- end
350
- STR
351
- }
352
268
  }
353
- end
269
+ }
354
270
  end
355
271
  end
356
272
  using ActiveRecordRelationScope
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Bitemporal
5
- VERSION = "4.2.0"
5
+ VERSION = "5.0.0"
6
6
  end
7
7
  end
@@ -49,7 +49,8 @@ module ActiveRecord::Bitemporal
49
49
  prev_valid_times.each do |valid_time|
50
50
  headers.print('|', line: line, column: columns[valid_time])
51
51
  end
52
- headers.print("| #{valid_time.strftime('%F %T.%3N')}", line: line, column: columns[valid_time])
52
+ valid_time_str = valid_time.kind_of?(Date) ? valid_time.strftime('%F') : valid_time.strftime('%F %T.%3N')
53
+ headers.print("| #{valid_time_str}", line: line, column: columns[valid_time])
53
54
  prev_valid_times << valid_time
54
55
  end
55
56
 
@@ -92,13 +93,14 @@ module ActiveRecord::Bitemporal
92
93
  end
93
94
  end
94
95
 
96
+ valid_label = valid_times[0].kind_of?(Date) ? 'valid_date' : 'valid_datetime'
95
97
  transaction_label = 'transaction_datetime'
96
98
  right_margin = time_length + 1 - transaction_label.size
97
99
 
98
100
  label = if right_margin >= 0
99
- "#{transaction_label + ' ' * right_margin}| valid_datetime"
101
+ "#{transaction_label + ' ' * right_margin}| #{valid_label}"
100
102
  else
101
- "#{transaction_label[0...right_margin]}| valid_datetime"
103
+ "#{transaction_label[0...right_margin]}| #{valid_label}"
102
104
  end
103
105
 
104
106
  "#{label}\n#{headers.to_s}\n#{body.to_s}"
@@ -104,6 +104,10 @@ module ActiveRecord::Bitemporal::Bitemporalize
104
104
  swapped_id.presence || super
105
105
  end
106
106
 
107
+ def previously_force_updated?
108
+ @previously_force_updated
109
+ end
110
+
107
111
  def valid_from_cannot_be_greater_equal_than_valid_to
108
112
  if valid_from && valid_to && valid_from >= valid_to
109
113
  errors.add(:valid_from, "can't be greater equal than valid_to")
@@ -141,10 +145,12 @@ module ActiveRecord::Bitemporal::Bitemporalize
141
145
  # MEMO: #update_columns is not call #_update_row (and validations, callbacks)
142
146
  update_columns(bitemporal_id_key => swapped_id) unless send(bitemporal_id_key)
143
147
  swap_id!(without_clear_changes_information: true)
148
+ @previously_force_updated = false
144
149
  end
145
150
 
146
151
  after_find do
147
152
  self.swap_id! if self.send(bitemporal_id_key).present?
153
+ @previously_force_updated = false
148
154
  end
149
155
 
150
156
  # Callback hook to `validates :xxx, uniqueness: true`
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-bitemporal
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SmartHR
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-24 00:00:00.000000000 Z
11
+ date: 2024-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '6.0'
19
+ version: '6.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '6.0'
26
+ version: '6.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -159,9 +159,9 @@ files:
159
159
  - bin/console
160
160
  - bin/setup
161
161
  - docker-compose.yml
162
- - gemfiles/rails_6.0.gemfile
163
162
  - gemfiles/rails_6.1.gemfile
164
163
  - gemfiles/rails_7.0.gemfile
164
+ - gemfiles/rails_7.1.gemfile
165
165
  - gemfiles/rails_main.gemfile
166
166
  - lib/activerecord-bitemporal.rb
167
167
  - lib/activerecord-bitemporal/bitemporal.rb
@@ -183,7 +183,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
183
183
  requirements:
184
184
  - - ">="
185
185
  - !ruby/object:Gem::Version
186
- version: 2.7.0
186
+ version: '3.0'
187
187
  required_rubygems_version: !ruby/object:Gem::Requirement
188
188
  requirements:
189
189
  - - ">="