activerecord-bitemporal 5.3.0 → 6.0.0
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.
- checksums.yaml +4 -4
- data/.github/auto_assign.yml +1 -1
- data/.github/workflows/release.yml +2 -2
- data/.github/workflows/test.yml +4 -9
- data/Appraisals +14 -4
- data/CHANGELOG.md +37 -0
- data/README.md +1 -1
- data/activerecord-bitemporal.gemspec +2 -2
- data/gemfiles/rails_7.0.gemfile +4 -0
- data/gemfiles/{rails_6.1.gemfile → rails_7.2.gemfile} +1 -1
- data/lib/activerecord-bitemporal/bitemporal.rb +45 -70
- data/lib/activerecord-bitemporal/scope.rb +22 -17
- data/lib/activerecord-bitemporal/version.rb +1 -1
- data/lib/activerecord-bitemporal/visualizer.rb +6 -4
- data/lib/activerecord-bitemporal.rb +15 -7
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b36848b2facc997f9179abd32f96d7fde53cd9464f9009fa25a1ca5f13f57890
|
4
|
+
data.tar.gz: 8498da159a6e051841ce253b7051f0722b0cb33d2358fc13a1b3f5c0664cf1f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd8c531ad27a3ba0693d3923a00f62abb4b730251e235d0f3a78b449f2fdc7336052a4e5c0115748330e95a6f75eff2f20dcfc71d3fa1360d3d7e4c98c2b38af
|
7
|
+
data.tar.gz: fe72790fbcb1372f8127afe702cf2696f930c901aaa2b6243c913243b299a74917bca867d068e09039a6df001c942f7db81b0a3c64daa54a919bbdb4714de4ac
|
data/.github/auto_assign.yml
CHANGED
@@ -19,11 +19,11 @@ jobs:
|
|
19
19
|
GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
|
20
20
|
GEM_HOST_OTP_CODE: ${{ github.event.inputs.rubygems-otp-code }}
|
21
21
|
steps:
|
22
|
-
- uses: actions/checkout@v4
|
22
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
23
23
|
with:
|
24
24
|
fetch-depth: 0
|
25
25
|
|
26
|
-
- uses: ruby/setup-ruby@v1
|
26
|
+
- uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0
|
27
27
|
with:
|
28
28
|
ruby-version: '3.1'
|
29
29
|
bundler-cache: true
|
data/.github/workflows/test.yml
CHANGED
@@ -15,19 +15,14 @@ jobs:
|
|
15
15
|
fail-fast: false
|
16
16
|
matrix:
|
17
17
|
ruby-version:
|
18
|
-
- '3.0'
|
19
18
|
- '3.1'
|
20
19
|
- '3.2'
|
21
20
|
- '3.3'
|
21
|
+
- '3.4'
|
22
22
|
gemfile:
|
23
|
-
- rails_6.1
|
24
23
|
- rails_7.0
|
25
24
|
- rails_7.1
|
26
|
-
|
27
|
-
- ruby-version: '3.2'
|
28
|
-
gemfile: rails_6.1
|
29
|
-
- ruby-version: '3.3'
|
30
|
-
gemfile: rails_6.1
|
25
|
+
- rails_7.2
|
31
26
|
services:
|
32
27
|
postgres:
|
33
28
|
image: postgres:14
|
@@ -45,9 +40,9 @@ jobs:
|
|
45
40
|
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
|
46
41
|
steps:
|
47
42
|
- name: Checkout
|
48
|
-
uses: actions/checkout@v4
|
43
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
49
44
|
- name: Setup Ruby
|
50
|
-
uses: ruby/setup-ruby@v1
|
45
|
+
uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0
|
51
46
|
with:
|
52
47
|
ruby-version: ${{ matrix.ruby-version }}
|
53
48
|
bundler-cache: true
|
data/Appraisals
CHANGED
@@ -1,13 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
appraise "rails-6.1" do
|
4
|
-
gem "rails", "~> 6.1.0"
|
5
|
-
end
|
6
|
-
|
7
3
|
appraise "rails-7.0" do
|
8
4
|
gem "rails", "~> 7.0.1"
|
5
|
+
|
6
|
+
# for Ruby 3.4
|
7
|
+
gem "base64"
|
8
|
+
gem "bigdecimal"
|
9
|
+
gem "mutex_m"
|
10
|
+
|
11
|
+
# NOTE: concurrent-ruby gem no longer loads the logger gem since v1.3.5.
|
12
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/pull/1062
|
13
|
+
# https://github.com/rails/rails/pull/54264
|
14
|
+
gem "concurrent-ruby", "< 1.3.5"
|
9
15
|
end
|
10
16
|
|
11
17
|
appraise "rails-7.1" do
|
12
18
|
gem "rails", "~> 7.1.0"
|
13
19
|
end
|
20
|
+
|
21
|
+
appraise "rails-7.2" do
|
22
|
+
gem "rails", "~> 7.2.0"
|
23
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,42 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 6.0.0
|
4
|
+
|
5
|
+
### Breaking Changed
|
6
|
+
|
7
|
+
- [Add Ruby 3.4 and remove Ruby 3.0 in CI #185](https://github.com/kufu/activerecord-bitemporal/pull/185)
|
8
|
+
- [Drop support Rails 6.1 #192](https://github.com/kufu/activerecord-bitemporal/pull/192)
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- [CI against Rails 7.2 #164](https://github.com/kufu/activerecord-bitemporal/pull/164)
|
13
|
+
- [Support custom column names for valid time in .bitemporalize #200](https://github.com/kufu/activerecord-bitemporal/pull/200)
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
|
17
|
+
### Deprecated
|
18
|
+
|
19
|
+
### Removed
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
|
23
|
+
- [Prevent where clauses from ignored by `ignore_valid_datetime` #190](https://github.com/kufu/activerecord-bitemporal/pull/190)
|
24
|
+
|
25
|
+
### Chores
|
26
|
+
|
27
|
+
- [Add a note to the README that PostgreSQL is required to run the tests. #188](https://github.com/kufu/activerecord-bitemporal/pull/188)
|
28
|
+
- [Remove specs for Rails 5.x #191](https://github.com/kufu/activerecord-bitemporal/pull/191)
|
29
|
+
- [Update auto assgin member #193](https://github.com/kufu/activerecord-bitemporal/pull/193)
|
30
|
+
- [Pin GitHub Actions dependencies to specific commit hashes #194](https://github.com/kufu/activerecord-bitemporal/pull/194)
|
31
|
+
- [Bump ruby/setup-ruby from 1.227.0 to 1.229.0 #195](https://github.com/kufu/activerecord-bitemporal/pull/195)
|
32
|
+
- [Bump ruby/setup-ruby from 1.229.0 to 1.233.0 #197](https://github.com/kufu/activerecord-bitemporal/pull/197)
|
33
|
+
- [Bump ruby/setup-ruby from 1.233.0 to 1.235.0 #198](https://github.com/kufu/activerecord-bitemporal/pull/198)
|
34
|
+
- [Bump ruby/setup-ruby from 1.235.0 to 1.237.0 #199](https://github.com/kufu/activerecord-bitemporal/pull/199)
|
35
|
+
- [Bump ruby/setup-ruby from 1.237.0 to 1.238.0 #201](https://github.com/kufu/activerecord-bitemporal/pull/201)
|
36
|
+
- [Bump ruby/setup-ruby from 1.238.0 to 1.242.0 #202](https://github.com/kufu/activerecord-bitemporal/pull/202)
|
37
|
+
- [Bump ruby/setup-ruby from 1.242.0 to 1.244.0 #203](https://github.com/kufu/activerecord-bitemporal/pull/203)
|
38
|
+
- [Update auto assgin member #204](https://github.com/kufu/activerecord-bitemporal/pull/204)
|
39
|
+
|
3
40
|
## 5.3.0
|
4
41
|
|
5
42
|
### Added
|
data/README.md
CHANGED
@@ -711,7 +711,7 @@ Employee.where(bitemporal_id: employee.bitemporal_id)
|
|
711
711
|
|
712
712
|
## Development
|
713
713
|
|
714
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
714
|
+
After checking out the repo, run `bin/setup` to install dependencies. And start PostgreSQL on port 5432. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
715
715
|
|
716
716
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
717
717
|
|
@@ -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 = ">= 3.
|
17
|
+
spec.required_ruby_version = ">= 3.1"
|
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", ">=
|
26
|
+
spec.add_dependency "activerecord", ">= 7.0"
|
27
27
|
|
28
28
|
spec.add_development_dependency "bundler"
|
29
29
|
spec.add_development_dependency "rake", "~> 13.0"
|
data/gemfiles/rails_7.0.gemfile
CHANGED
@@ -313,7 +313,7 @@ module ActiveRecord
|
|
313
313
|
def _create_record(attribute_names = self.attribute_names)
|
314
314
|
bitemporal_assign_initialize_value(valid_datetime: self.valid_datetime)
|
315
315
|
|
316
|
-
ActiveRecord::Bitemporal.valid_at!(self
|
316
|
+
ActiveRecord::Bitemporal.valid_at!(self[valid_from_key]) {
|
317
317
|
super()
|
318
318
|
}
|
319
319
|
end
|
@@ -346,8 +346,8 @@ module ActiveRecord
|
|
346
346
|
# update 後に新しく生成したインスタンスのデータを移行する
|
347
347
|
@_swapped_id_previously_was = swapped_id
|
348
348
|
@_swapped_id = after_instance.swapped_id
|
349
|
-
self
|
350
|
-
self
|
349
|
+
self[valid_from_key] = after_instance[valid_from_key]
|
350
|
+
self[valid_to_key] = after_instance[valid_to_key]
|
351
351
|
self.transaction_from = after_instance.transaction_from
|
352
352
|
self.transaction_to = after_instance.transaction_to
|
353
353
|
|
@@ -373,14 +373,14 @@ module ActiveRecord
|
|
373
373
|
# force_update の場合は削除時の状態の履歴を残さない
|
374
374
|
unless force_update?
|
375
375
|
# 削除時の状態を履歴レコードとして保存する
|
376
|
-
duplicated_instance
|
376
|
+
duplicated_instance[valid_to_key] = target_datetime
|
377
377
|
duplicated_instance.transaction_from = operated_at
|
378
378
|
duplicated_instance.save_without_bitemporal_callbacks!(validate: false)
|
379
379
|
if @destroyed
|
380
380
|
@_swapped_id_previously_was = swapped_id
|
381
381
|
@_swapped_id = duplicated_instance.swapped_id
|
382
|
-
self
|
383
|
-
self
|
382
|
+
self[valid_from_key] = duplicated_instance[valid_from_key]
|
383
|
+
self[valid_to_key] = duplicated_instance[valid_to_key]
|
384
384
|
self.transaction_from = duplicated_instance.transaction_from
|
385
385
|
self.transaction_to = duplicated_instance.transaction_to
|
386
386
|
end
|
@@ -393,6 +393,7 @@ module ActiveRecord
|
|
393
393
|
rescue => e
|
394
394
|
@destroyed = false
|
395
395
|
@_association_destroy_exception = ActiveRecord::RecordNotDestroyed.new("Failed to destroy the record: class=#{e.class}, message=#{e.message}", self)
|
396
|
+
@_association_destroy_exception.set_backtrace(e.backtrace)
|
396
397
|
false
|
397
398
|
end
|
398
399
|
|
@@ -411,55 +412,29 @@ module ActiveRecord
|
|
411
412
|
module ::ActiveRecord::Persistence
|
412
413
|
# MEMO: Must be override ActiveRecord::Persistence#reload
|
413
414
|
alias_method :active_record_bitemporal_original_reload, :reload unless method_defined? :active_record_bitemporal_original_reload
|
414
|
-
|
415
|
-
|
416
|
-
return active_record_bitemporal_original_reload(options) unless self.class.bi_temporal_model?
|
417
|
-
|
418
|
-
self.class.connection.clear_query_cache
|
419
|
-
|
420
|
-
fresh_object =
|
421
|
-
ActiveRecord::Bitemporal.with_bitemporal_option(**bitemporal_option) {
|
422
|
-
if apply_scoping?(options)
|
423
|
-
_find_record(options)
|
424
|
-
else
|
425
|
-
self.class.unscoped { self.class.bitemporal_default_scope.scoping { _find_record(options) } }
|
426
|
-
end
|
427
|
-
}
|
415
|
+
def reload(options = nil)
|
416
|
+
return active_record_bitemporal_original_reload(options) unless self.class.bi_temporal_model?
|
428
417
|
|
429
|
-
|
430
|
-
@attributes = fresh_object.instance_variable_get(:@attributes)
|
431
|
-
@new_record = false
|
432
|
-
@previously_new_record = false
|
433
|
-
# NOTE: Hook to copying swapped_id
|
434
|
-
@_swapped_id_previously_was = nil
|
435
|
-
@_swapped_id = fresh_object.swapped_id
|
436
|
-
@previously_force_updated = false
|
437
|
-
self
|
438
|
-
end
|
439
|
-
else
|
440
|
-
def reload(options = nil)
|
441
|
-
return active_record_bitemporal_original_reload(options) unless self.class.bi_temporal_model?
|
442
|
-
|
443
|
-
self.class.connection.clear_query_cache
|
444
|
-
|
445
|
-
fresh_object =
|
446
|
-
ActiveRecord::Bitemporal.with_bitemporal_option(**bitemporal_option) {
|
447
|
-
if options && options[:lock]
|
448
|
-
self.class.unscoped { self.class.lock(options[:lock]).bitemporal_default_scope.find(id) }
|
449
|
-
else
|
450
|
-
self.class.unscoped { self.class.bitemporal_default_scope.find(id) }
|
451
|
-
end
|
452
|
-
}
|
418
|
+
self.class.connection.clear_query_cache
|
453
419
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
420
|
+
fresh_object =
|
421
|
+
ActiveRecord::Bitemporal.with_bitemporal_option(**bitemporal_option) {
|
422
|
+
if apply_scoping?(options)
|
423
|
+
_find_record(options)
|
424
|
+
else
|
425
|
+
self.class.unscoped { self.class.bitemporal_default_scope.scoping { _find_record(options) } }
|
426
|
+
end
|
427
|
+
}
|
428
|
+
|
429
|
+
@association_cache = fresh_object.instance_variable_get(:@association_cache)
|
430
|
+
@attributes = fresh_object.instance_variable_get(:@attributes)
|
431
|
+
@new_record = false
|
432
|
+
@previously_new_record = false
|
433
|
+
# NOTE: Hook to copying swapped_id
|
434
|
+
@_swapped_id_previously_was = nil
|
435
|
+
@_swapped_id = fresh_object.swapped_id
|
436
|
+
@previously_force_updated = false
|
437
|
+
self
|
463
438
|
end
|
464
439
|
end
|
465
440
|
|
@@ -467,7 +442,7 @@ module ActiveRecord
|
|
467
442
|
|
468
443
|
def bitemporal_assign_initialize_value(valid_datetime:, current_time: Time.current)
|
469
444
|
# 自身の `valid_from` を設定
|
470
|
-
self
|
445
|
+
self[valid_from_key] = valid_datetime || current_time if self[valid_from_key] == ActiveRecord::Bitemporal::DEFAULT_VALID_FROM
|
471
446
|
|
472
447
|
self.transaction_from = current_time if self.transaction_from == ActiveRecord::Bitemporal::DEFAULT_TRANSACTION_FROM
|
473
448
|
|
@@ -485,7 +460,7 @@ module ActiveRecord
|
|
485
460
|
def bitemporal_build_update_records(valid_datetime:, current_time: Time.current, force_update: false)
|
486
461
|
target_datetime = valid_datetime || current_time
|
487
462
|
# NOTE: force_update の場合は自身のレコードを取得するような時間を指定しておく
|
488
|
-
target_datetime =
|
463
|
+
target_datetime = attribute_changed?(valid_from_key) ? attribute_was(valid_from_key) : self[valid_from_key] if force_update
|
489
464
|
|
490
465
|
# 対象基準日において有効なレコード
|
491
466
|
# NOTE: 論理削除対象
|
@@ -516,26 +491,26 @@ module ActiveRecord
|
|
516
491
|
current_valid_record.assign_transaction_to(current_time)
|
517
492
|
|
518
493
|
# 以前の履歴データは valid_to を詰めて保存
|
519
|
-
before_instance
|
494
|
+
before_instance[valid_to_key] = target_datetime
|
520
495
|
if before_instance.valid_from_cannot_be_greater_equal_than_valid_to
|
521
|
-
raise ValidDatetimeRangeError.new("
|
496
|
+
raise ValidDatetimeRangeError.new("#{valid_from_key} #{before_instance[valid_from_key]} can't be greater equal than #{valid_to_key} #{before_instance[valid_to_key]}")
|
522
497
|
end
|
523
498
|
before_instance.transaction_from = current_time
|
524
499
|
|
525
500
|
# 以降の履歴データは valid_from と valid_to を調整して保存する
|
526
|
-
after_instance
|
527
|
-
after_instance
|
501
|
+
after_instance[valid_from_key] = target_datetime
|
502
|
+
after_instance[valid_to_key] = current_valid_record[valid_to_key]
|
528
503
|
if after_instance.valid_from_cannot_be_greater_equal_than_valid_to
|
529
|
-
raise ValidDatetimeRangeError.new("
|
504
|
+
raise ValidDatetimeRangeError.new("#{valid_from_key} #{after_instance[valid_from_key]} can't be greater equal than #{valid_to_key} #{after_instance[valid_to_key]}")
|
530
505
|
end
|
531
506
|
after_instance.transaction_from = current_time
|
532
507
|
|
533
508
|
# 有効なレコードがない場合
|
534
509
|
else
|
535
510
|
# 一番近い未来にある Instance を取ってきて、その valid_from を valid_to に入れる
|
536
|
-
nearest_instance = self.class.where(bitemporal_id: bitemporal_id).valid_from_gt(target_datetime).
|
511
|
+
nearest_instance = self.class.where(bitemporal_id: bitemporal_id).ignore_valid_datetime.valid_from_gt(target_datetime).order(valid_from_key => :asc).first
|
537
512
|
if nearest_instance.nil?
|
538
|
-
message = "Update failed: Couldn't find #{self.class} with 'bitemporal_id'=#{self.bitemporal_id} and '
|
513
|
+
message = "Update failed: Couldn't find #{self.class} with 'bitemporal_id'=#{self.bitemporal_id} and '#{valid_from_key}' > #{target_datetime}"
|
539
514
|
raise ActiveRecord::RecordNotFound.new(message, self.class, "bitemporal_id", self.bitemporal_id)
|
540
515
|
end
|
541
516
|
|
@@ -546,8 +521,8 @@ module ActiveRecord
|
|
546
521
|
before_instance = nil
|
547
522
|
|
548
523
|
# 以降の履歴データは valid_from と valid_to を調整して保存する
|
549
|
-
after_instance
|
550
|
-
after_instance
|
524
|
+
after_instance[valid_from_key] = target_datetime
|
525
|
+
after_instance[valid_to_key] = nearest_instance[valid_from_key]
|
551
526
|
after_instance.transaction_from = current_time
|
552
527
|
end
|
553
528
|
|
@@ -569,7 +544,7 @@ module ActiveRecord
|
|
569
544
|
|
570
545
|
target_datetime = record.valid_datetime || Time.current
|
571
546
|
|
572
|
-
valid_from = record.
|
547
|
+
valid_from = record[record.valid_from_key].yield_self { |valid_from|
|
573
548
|
# NOTE: valid_from が初期値の場合は現在の時間を基準としてバリデーションする
|
574
549
|
# valid_from が初期値の場合は Persistence#_create_record に Time.current が割り当てられる為
|
575
550
|
# バリデーション時と生成時で若干時間がずれてしまうことには考慮する
|
@@ -586,17 +561,17 @@ module ActiveRecord
|
|
586
561
|
}
|
587
562
|
|
588
563
|
# MEMO: `force_update` does not refer to `valid_datetime`
|
589
|
-
valid_from = record.
|
564
|
+
valid_from = record[record.valid_from_key] if record.force_update?
|
590
565
|
|
591
|
-
valid_to = record.
|
566
|
+
valid_to = record[record.valid_to_key].yield_self { |valid_to|
|
592
567
|
# NOTE: `cover?` may give incorrect results, when the time zone is not UTC and `valid_from` is date type
|
593
568
|
# Therefore, cast to type of `valid_from`
|
594
|
-
record_valid_time = finder_class.type_for_attribute(
|
569
|
+
record_valid_time = finder_class.type_for_attribute(record.valid_from_key).cast(record.valid_datetime)
|
595
570
|
# レコードを更新する時に valid_datetime が valid_from ~ valid_to の範囲外だった場合、
|
596
571
|
# 一番近い未来の履歴レコードを参照して更新する
|
597
572
|
# という仕様があるため、それを考慮して valid_to を設定する
|
598
|
-
if (record_valid_time && (record.
|
599
|
-
finder_class.ignore_valid_datetime.where(bitemporal_id: record.bitemporal_id).valid_from_gteq(target_datetime).order(
|
573
|
+
if (record_valid_time && (record[record.valid_from_key]...record[record.valid_to_key]).cover?(record_valid_time)) == false && (record.persisted?)
|
574
|
+
finder_class.ignore_valid_datetime.where(bitemporal_id: record.bitemporal_id).valid_from_gteq(target_datetime).order(record.valid_from_key => :asc).first[record.valid_from_key]
|
600
575
|
else
|
601
576
|
valid_to
|
602
577
|
end
|
@@ -86,9 +86,9 @@ module ActiveRecord::Bitemporal
|
|
86
86
|
|
87
87
|
refine Relation do
|
88
88
|
def bitemporal_clause(table_name = klass.table_name)
|
89
|
-
node_hash = where_clause.bitemporal_query_hash(
|
90
|
-
valid_from = node_hash.dig(table_name,
|
91
|
-
valid_to = node_hash.dig(table_name,
|
89
|
+
node_hash = where_clause.bitemporal_query_hash(valid_from_key, valid_to_key, "transaction_from", "transaction_to")
|
90
|
+
valid_from = node_hash.dig(table_name, valid_from_key, 1)
|
91
|
+
valid_to = node_hash.dig(table_name, valid_to_key, 1)
|
92
92
|
transaction_from = node_hash.dig(table_name, "transaction_from", 1)
|
93
93
|
transaction_to = node_hash.dig(table_name, "transaction_to", 1)
|
94
94
|
{
|
@@ -255,19 +255,24 @@ module ActiveRecord::Bitemporal
|
|
255
255
|
rewhere(table[attr_name].public_send(operator, predicate_builder.build_bind_attribute(attr_name, value)))
|
256
256
|
end
|
257
257
|
|
258
|
-
|
258
|
+
[
|
259
|
+
[:valid_from, "\#{valid_from_key}"], # Evaluate `valid_from/to_key` at runtime using `\#`
|
260
|
+
[:valid_to, "\#{valid_to_key}"],
|
261
|
+
[:transaction_from, "transaction_from"],
|
262
|
+
[:transaction_to, "transaction_to"]
|
263
|
+
].each { |column, column_name|
|
259
264
|
module_eval <<-STR, __FILE__, __LINE__ + 1
|
260
265
|
def _ignore_#{column}
|
261
|
-
unscope(where: :"\#{table.name}.#{
|
262
|
-
.tap { |relation| relation.unscope!(where: bitemporal_value[:through].arel_table["#{
|
266
|
+
unscope(where: :"\#{table.name}.#{column_name}")
|
267
|
+
.tap { |relation| relation.unscope!(where: bitemporal_value[:through].arel_table["#{column_name}"]) if bitemporal_value[:through] }
|
263
268
|
end
|
264
269
|
|
265
270
|
def _except_#{column}
|
266
271
|
return self unless where_clause.send(:predicates).find { |node|
|
267
|
-
node.bitemporal_include?("\#{table.name}.#{
|
272
|
+
node.bitemporal_include?("\#{table.name}.#{column_name}")
|
268
273
|
}
|
269
274
|
_ignore_#{column}.tap { |relation|
|
270
|
-
relation.unscope_values.reject! { |query| query.equal_attribute_name("\#{table.name}.#{
|
275
|
+
relation.unscope_values.reject! { |query| query.equal_attribute_name("\#{table.name}.#{column_name}") }
|
271
276
|
}
|
272
277
|
end
|
273
278
|
STR
|
@@ -281,8 +286,8 @@ module ActiveRecord::Bitemporal
|
|
281
286
|
module_eval <<-STR, __FILE__, __LINE__ + 1
|
282
287
|
def _#{column}_#{op}(datetime)
|
283
288
|
target_datetime = datetime&.in_time_zone || Time.current
|
284
|
-
bitemporal_rewhere_bind("#{
|
285
|
-
.tap { |relation| break relation.bitemporal_rewhere_bind("#{
|
289
|
+
bitemporal_rewhere_bind("#{column_name}", :#{op}, target_datetime)
|
290
|
+
.tap { |relation| break relation.bitemporal_rewhere_bind("#{column_name}", :#{op}, target_datetime, bitemporal_value[:through].arel_table) if bitemporal_value[:through] }
|
286
291
|
end
|
287
292
|
STR
|
288
293
|
}
|
@@ -428,17 +433,17 @@ module ActiveRecord::Bitemporal
|
|
428
433
|
# beginless range
|
429
434
|
if begin_
|
430
435
|
# from < valid_to
|
431
|
-
relation = relation.bitemporal_where_bind(
|
436
|
+
relation = relation.bitemporal_where_bind(valid_to_key, :gt, begin_.in_time_zone.to_datetime)
|
432
437
|
end
|
433
438
|
|
434
439
|
# endless range
|
435
440
|
if end_
|
436
441
|
if range.exclude_end?
|
437
442
|
# valid_from < to
|
438
|
-
relation = relation.bitemporal_where_bind(
|
443
|
+
relation = relation.bitemporal_where_bind(valid_from_key, :lt, end_.in_time_zone.to_datetime)
|
439
444
|
else
|
440
445
|
# valid_from <= to
|
441
|
-
relation = relation.bitemporal_where_bind(
|
446
|
+
relation = relation.bitemporal_where_bind(valid_from_key, :lteq, end_.in_time_zone.to_datetime)
|
442
447
|
end
|
443
448
|
end
|
444
449
|
|
@@ -453,14 +458,14 @@ module ActiveRecord::Bitemporal
|
|
453
458
|
begin_, end_ = range.begin, range.end
|
454
459
|
|
455
460
|
if begin_
|
456
|
-
relation = relation.bitemporal_where_bind(
|
461
|
+
relation = relation.bitemporal_where_bind(valid_from_key, :gteq, begin_.in_time_zone.to_datetime)
|
457
462
|
end
|
458
463
|
|
459
464
|
if end_
|
460
465
|
if range.exclude_end?
|
461
466
|
raise 'Range with excluding end is not supported'
|
462
467
|
else
|
463
|
-
relation = relation.bitemporal_where_bind(
|
468
|
+
relation = relation.bitemporal_where_bind(valid_to_key, :lteq, end_.in_time_zone.to_datetime)
|
464
469
|
end
|
465
470
|
end
|
466
471
|
|
@@ -476,10 +481,10 @@ module ActiveRecord::Bitemporal
|
|
476
481
|
ignore_valid_datetime.bitemporal_for(*ids)
|
477
482
|
}
|
478
483
|
def self.bitemporal_most_future(id)
|
479
|
-
bitemporal_histories(id).order(
|
484
|
+
bitemporal_histories(id).order(valid_from_key => :asc).last
|
480
485
|
end
|
481
486
|
def self.bitemporal_most_past(id)
|
482
|
-
bitemporal_histories(id).order(
|
487
|
+
bitemporal_histories(id).order(valid_from_key => :asc).first
|
483
488
|
end
|
484
489
|
end
|
485
490
|
end
|
@@ -22,7 +22,7 @@ module ActiveRecord::Bitemporal
|
|
22
22
|
module_function
|
23
23
|
|
24
24
|
def visualize(record, height: 10, width: 40, highlight: true)
|
25
|
-
histories = record.class.ignore_bitemporal_datetime.bitemporal_for(record).order(:transaction_from,
|
25
|
+
histories = record.class.ignore_bitemporal_datetime.bitemporal_for(record).order(:transaction_from, record.valid_from_key)
|
26
26
|
|
27
27
|
if highlight
|
28
28
|
visualize_records(histories, [record], height: height, width: width)
|
@@ -36,7 +36,7 @@ module ActiveRecord::Bitemporal
|
|
36
36
|
raise 'More than 3 relations are not supported' if relations.size >= 3
|
37
37
|
records = relations.flatten
|
38
38
|
|
39
|
-
valid_times = (records.map
|
39
|
+
valid_times = (records.map { _1[_1.valid_from_key] } + records.map { _1[_1.valid_to_key] }).sort.uniq
|
40
40
|
transaction_times = (records.map(&:transaction_from) + records.map(&:transaction_to)).sort.uniq
|
41
41
|
|
42
42
|
time_length = Time.zone.now.strftime('%F %T.%3N').length
|
@@ -60,9 +60,11 @@ module ActiveRecord::Bitemporal
|
|
60
60
|
|
61
61
|
relation.each do |record|
|
62
62
|
line = lines[record.transaction_from]
|
63
|
-
|
63
|
+
valid_from = record[record.valid_from_key]
|
64
|
+
valid_to = record[record.valid_to_key]
|
65
|
+
column = columns[valid_from]
|
64
66
|
|
65
|
-
width = columns[
|
67
|
+
width = columns[valid_to] - columns[valid_from] - 1
|
66
68
|
height = lines[record.transaction_to] - lines[record.transaction_from] - 1
|
67
69
|
|
68
70
|
body.print("#{record.transaction_from.strftime('%F %T.%3N')} ", line: line)
|
@@ -92,8 +92,8 @@ module ActiveRecord::Bitemporal::Bitemporalize
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def valid_from_cannot_be_greater_equal_than_valid_to
|
95
|
-
if
|
96
|
-
errors.add(
|
95
|
+
if self[valid_from_key] && self[valid_to_key] && self[valid_from_key] >= self[valid_to_key]
|
96
|
+
errors.add(valid_from_key, "can't be greater equal than #{valid_to_key}")
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
@@ -107,10 +107,15 @@ module ActiveRecord::Bitemporal::Bitemporalize
|
|
107
107
|
def bitemporalize(
|
108
108
|
enable_strict_by_validates_bitemporal_id: false,
|
109
109
|
enable_default_scope: true,
|
110
|
-
enable_merge_with_except_bitemporal_default_scope: false
|
110
|
+
enable_merge_with_except_bitemporal_default_scope: false,
|
111
|
+
valid_from_key: :valid_from,
|
112
|
+
valid_to_key: :valid_to
|
111
113
|
)
|
114
|
+
return if ancestors.include? InstanceMethods
|
115
|
+
|
112
116
|
extend ClassMethods
|
113
117
|
include InstanceMethods
|
118
|
+
include ActiveRecord::Bitemporal
|
114
119
|
include ActiveRecord::Bitemporal::Scope
|
115
120
|
include ActiveRecord::Bitemporal::Callbacks
|
116
121
|
|
@@ -136,8 +141,11 @@ module ActiveRecord::Bitemporal::Bitemporalize
|
|
136
141
|
@previously_force_updated = false
|
137
142
|
end
|
138
143
|
|
139
|
-
|
140
|
-
|
144
|
+
self.class_attribute :valid_from_key, :valid_to_key, instance_writer: false
|
145
|
+
self.valid_from_key = valid_from_key.to_s
|
146
|
+
self.valid_to_key = valid_to_key.to_s
|
147
|
+
attribute valid_from_key, default: ActiveRecord::Bitemporal::DEFAULT_VALID_FROM
|
148
|
+
attribute valid_to_key, default: ActiveRecord::Bitemporal::DEFAULT_VALID_TO
|
141
149
|
attribute :transaction_from, default: ActiveRecord::Bitemporal::DEFAULT_TRANSACTION_FROM
|
142
150
|
attribute :transaction_to, default: ActiveRecord::Bitemporal::DEFAULT_TRANSACTION_TO
|
143
151
|
|
@@ -147,8 +155,8 @@ module ActiveRecord::Bitemporal::Bitemporalize
|
|
147
155
|
})
|
148
156
|
|
149
157
|
# validations
|
150
|
-
validates
|
151
|
-
validates
|
158
|
+
validates valid_from_key, presence: true
|
159
|
+
validates valid_to_key, presence: true
|
152
160
|
validates :transaction_from, presence: true
|
153
161
|
validates :transaction_to, presence: true
|
154
162
|
validate :valid_from_cannot_be_greater_equal_than_valid_to
|
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
|
+
version: 6.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SmartHR
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-06-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: '
|
19
|
+
version: '7.0'
|
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: '
|
26
|
+
version: '7.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -146,9 +146,9 @@ files:
|
|
146
146
|
- bin/console
|
147
147
|
- bin/setup
|
148
148
|
- compose.yml
|
149
|
-
- gemfiles/rails_6.1.gemfile
|
150
149
|
- gemfiles/rails_7.0.gemfile
|
151
150
|
- gemfiles/rails_7.1.gemfile
|
151
|
+
- gemfiles/rails_7.2.gemfile
|
152
152
|
- lib/activerecord-bitemporal.rb
|
153
153
|
- lib/activerecord-bitemporal/bitemporal.rb
|
154
154
|
- lib/activerecord-bitemporal/callbacks.rb
|
@@ -161,7 +161,7 @@ homepage: https://github.com/kufu/activerecord-bitemporal
|
|
161
161
|
licenses:
|
162
162
|
- Apache 2.0
|
163
163
|
metadata: {}
|
164
|
-
post_install_message:
|
164
|
+
post_install_message:
|
165
165
|
rdoc_options: []
|
166
166
|
require_paths:
|
167
167
|
- lib
|
@@ -169,7 +169,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
169
169
|
requirements:
|
170
170
|
- - ">="
|
171
171
|
- !ruby/object:Gem::Version
|
172
|
-
version: '3.
|
172
|
+
version: '3.1'
|
173
173
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
174
|
requirements:
|
175
175
|
- - ">="
|
@@ -177,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
177
177
|
version: '0'
|
178
178
|
requirements: []
|
179
179
|
rubygems_version: 3.3.27
|
180
|
-
signing_key:
|
180
|
+
signing_key:
|
181
181
|
specification_version: 4
|
182
182
|
summary: BiTemporal Data Model for ActiveRecord
|
183
183
|
test_files: []
|