activerecord-bitemporal 1.0.0 → 1.1.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: f3801f30411bb5e26d32aa1cb27ed45e5c12ebea99b688fcc27b9be1289099fc
4
- data.tar.gz: a1ef742f3e2b7c8cb3bd280ba0fb92a5f1f45b2504fc29dc7b789d2694c2b284
3
+ metadata.gz: 939636118c751abd0690d9956750820ffcbb970fef7e4c9b6ab29642d461bf79
4
+ data.tar.gz: caf2c137271a7c635318384b42c771d215849fb1ece08de062a0170f014ad0b4
5
5
  SHA512:
6
- metadata.gz: 48b484ead6164bd5784a9060085267e7b6b9ae80a23f5015177ed45e4e82e702634731b2bb90a5ac67018c6d3052edf6839095327bc22bfbab98d412a013f279
7
- data.tar.gz: 004c5991bcebf4079b98665fb295d45cb473ebf2f83f0cdbf4ca08770003e7f4cf38f969b18e8d265fe2b39e3621ae5201953396dcecf9a79010f55c875be1b4
6
+ metadata.gz: '05108b579eb4585ef4835677902933167209be9a7ed13482122f088c5b5a85ea528260b74ed619f3fcbbbb0d2ae85f711a2c06803b2c60d8821b88b9528fa914'
7
+ data.tar.gz: e38f7f394438418cbab0a9f237ef4352cd0b2e6e07dff1fbbd4be7cda37c318ff2ce85a2c332e92e015d47980b06e49e74ce76e3c0a4b232d25bb869a092b604
data/CHANGELOG.md CHANGED
@@ -1,39 +1,19 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
4
-
5
- ### Breaking Changes
6
-
7
- - [#15](https://github.com/kufu/activerecord-bitemporal/pull/15) - `validates :bitemporal_id, uniqueness: true` is no raise by default.
8
- - [#19](https://github.com/kufu/activerecord-bitemporal/pull/19) - Fix create history records after logical destroy in #destroy.
3
+ ## 1.1.0
9
4
 
10
5
  ### Added
11
6
 
12
- - [#12](https://github.com/kufu/activerecord-bitemporal/pull/12) - Added utility (and extension) scopes.
13
- - `.bitemporl_for(id)`
14
- - `.valid_in(from: from, to: to)`
15
- - `.valid_allin(from: from, to: to)`
16
- - `.bitemporal_histories_by(id)`
17
- - `.bitemporal_most_future(id)`
18
- - `.bitemporal_most_past(id)`
19
- - [#15](https://github.com/kufu/activerecord-bitemporal/pull/15) - Added `.bitemporalize`. Use `.bitemporalize` instead of `include ActiveRecord::Bitemporal`.
20
- - [#15](https://github.com/kufu/activerecord-bitemporal/pull/15) - Added `.bitemporalize` options.
7
+ - [Add bitemporal data structure visualizer by wata727 · Pull Request #94](https://github.com/kufu/activerecord-bitemporal/pull/94)
8
+
9
+ ### Changed
21
10
 
22
- | option | describe | default |
23
- | --- | --- | --- |
24
- | `enable_strict_by_validates_bitemporal_id` | raised with `validates :bitemporal_id, uniqueness: true` if `true` | false |
11
+ ### Deprecated
25
12
 
13
+ ### Removed
26
14
 
27
15
  ### Fixed
28
16
 
29
- - [#17](https://github.com/kufu/activerecord-bitemporal/pull/17) - Fixed bug in create record with valid_datetime out of the range valid_from to valid_to.
30
- - [#18](https://github.com/kufu/activerecord-bitemporal/pull/18) - `record.valid_datetime` is not nil when after `Model.valid_at("2019/1/1").ignore_valid_datetime`.
31
- - [#18](https://github.com/kufu/activerecord-bitemporal/pull/18) - `ignore_valid_datetime` is not applied in `ActiveRecord::Bitemporal.valid_at!`.
32
- - [#21](https://github.com/kufu/activerecord-bitemporal/pull/21) - Fixed bug in multi thread with `#update`.
33
- - [#24](https://github.com/kufu/activerecord-bitemporal/pull/24) [#25](https://github.com/kufu/activerecord-bitemporal/pull/25) - Fixed bug. Does not respect table alias on join clause.
34
- - [#27](https://github.com/kufu/activerecord-bitemporal/pull/27) - Fixed a bug that `swapped_id` doesn't change after `#reload`.
35
- - [#28](https://github.com/kufu/activerecord-bitemporal/pull/28) - Fix the bug that `valid_from == valid_to` record is generated.
36
-
37
- ### Deprecated
17
+ ## 1.0.0
38
18
 
39
- - None
19
+ First stable release
@@ -7,8 +7,8 @@ require "activerecord-bitemporal/version"
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = "activerecord-bitemporal"
9
9
  spec.version = ActiveRecord::Bitemporal::VERSION
10
- spec.authors = ["mserizawa"]
11
- spec.email = ["serizawa@smarthr.co.jp"]
10
+ spec.authors = ["SmartHR"]
11
+ spec.email = ["oss@smarthr.co.jp"]
12
12
 
13
13
  spec.summary = "BiTemporal Data Model for ActiveRecord"
14
14
  spec.description = %q{Enable ActiveRecord models to be handled as BiTemporal Data Model.}
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Bitemporal
5
- VERSION = "1.0.0"
5
+ VERSION = "1.1.0"
6
6
  end
7
7
  end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord::Bitemporal
4
+ module Visualizer
5
+ # Figure is a two-dimensional array holding plotted lines and columns
6
+ class Figure < Array
7
+ def print(str, line: 0, column: 0)
8
+ self[line] ||= []
9
+ str.each_char.with_index(column) do |c, i|
10
+ # The `#` represents a zero-length rectangle and should not be overwritten with lines
11
+ next if self[line][i] == '#' && (c == '+' || c == '|' || c == '-')
12
+
13
+ self[line][i] = c
14
+ end
15
+ end
16
+
17
+ def to_s
18
+ map { |l| l&.map { |c| c || ' ' }&.join }.join("\n")
19
+ end
20
+ end
21
+
22
+ module_function
23
+
24
+ def visualize(record, height: 10, width: 40, highlight: true)
25
+ histories = record.class.ignore_bitemporal_datetime.bitemporal_for(record).order(:transaction_from, :valid_from)
26
+
27
+ if highlight
28
+ visualize_records(histories, [record], height: height, width: width)
29
+ else
30
+ visualize_records(histories, height: height, width: width)
31
+ end
32
+ end
33
+
34
+ # e.g. visualize_records(ActiveRecord::Relation, ActiveRecord::Relation)
35
+ def visualize_records(*relations, height: 10, width: 40)
36
+ raise 'More than 3 relations are not supported' if relations.size >= 3
37
+ records = relations.flatten
38
+
39
+ valid_times = (records.map(&:valid_from) + records.map(&:valid_to)).sort.uniq
40
+ transaction_times = (records.map(&:transaction_from) + records.map(&:transaction_to)).sort.uniq
41
+
42
+ time_length = Time.zone.now.strftime('%F %T.%3N').length
43
+
44
+ columns = compute_positions(valid_times, length: width, left_margin: time_length + 1, outlier: ActiveRecord::Bitemporal::DEFAULT_VALID_TO)
45
+ lines = compute_positions(transaction_times, length: height, outlier: ActiveRecord::Bitemporal::DEFAULT_TRANSACTION_TO)
46
+
47
+ headers = Figure.new
48
+ valid_times.each_with_object([]).with_index do |(valid_time, prev_valid_times), line|
49
+ prev_valid_times.each do |valid_time|
50
+ headers.print('|', line: line, column: columns[valid_time])
51
+ end
52
+ headers.print("| #{valid_time.strftime('%F %T.%3N')}", line: line, column: columns[valid_time])
53
+ prev_valid_times << valid_time
54
+ end
55
+
56
+ body = Figure.new
57
+ relations.each.with_index do |relation, idx|
58
+ filler = idx == 0 ? ' ' : '*'
59
+
60
+ relation.each do |record|
61
+ line = lines[record.transaction_from]
62
+ column = columns[record.valid_from]
63
+
64
+ width = columns[record.valid_to] - columns[record.valid_from] - 1
65
+ height = lines[record.transaction_to] - lines[record.transaction_from] - 1
66
+
67
+ body.print("#{record.transaction_from.strftime('%F %T.%3N')} ", line: line)
68
+ if width > 0
69
+ if height > 0
70
+ body.print('+' + '-' * width + '+', line: line, column: column)
71
+ else
72
+ body.print('|' + '#' * width + '|', line: line, column: column)
73
+ end
74
+ else
75
+ body.print('#', line: line, column: column)
76
+ end
77
+
78
+ 1.upto(height) do |i|
79
+ if width > 0
80
+ body.print('|' + filler * width + '|', line: line + i, column: column)
81
+ else
82
+ body.print('#', line: line + i, column: column)
83
+ end
84
+ end
85
+
86
+ body.print("#{record.transaction_to.strftime('%F %T.%3N')} ", line: line + height + 1)
87
+ if width > 0
88
+ body.print('+' + '-' * width + '+', line: line + height + 1, column: column)
89
+ else
90
+ body.print('#', line: line + height + 1, column: column)
91
+ end
92
+ end
93
+ end
94
+
95
+ "#{headers.to_s}\n#{body.to_s}"
96
+ end
97
+
98
+ # Compute a dictionary of where each time should be plotted.
99
+ # The position is normalized to the actual length of time.
100
+ #
101
+ # Example:
102
+ #
103
+ # t1 t2 t3 t4
104
+ # |------|------|-----------------|
105
+ #
106
+ # f(t1, t2, t3, t4) -> { t1 => 0, t2 => 2, t3 => 4, t4 => 10 }
107
+ #
108
+ def compute_positions(times, length:, left_margin: 0, outlier: nil)
109
+ lengths_from_beginning = compute_lengths_from_beginning(times, outlier: outlier)
110
+ # times must be sorted in ascending order. This is caller's responsibility.
111
+ # In that case, the last of lengths_from_beginning is equal to the total length.
112
+ total = lengths_from_beginning.values.last
113
+
114
+ times.each_with_object({}) do |time, ret|
115
+ prev = ret.values.last
116
+ pos = (lengths_from_beginning[time] / total * length).to_i + left_margin
117
+
118
+ if prev
119
+ # If the difference of times is too short, a position that have already been plotted may be computed.
120
+ # But we still want to plot the time, so allocate the required number to plot the smallest area.
121
+ if pos <= prev
122
+ # | -> |*|
123
+ # ^^ 2 columns
124
+ pos = prev + 2
125
+ elsif pos == prev + 1
126
+ # || -> |*|
127
+ # ^ 1 column
128
+ pos += 1
129
+ end
130
+ end
131
+ ret[time] = pos
132
+ end
133
+ end
134
+
135
+ # Example:
136
+ #
137
+ # t1 t2 t3 t4
138
+ # |-----------|-----------|-----------|
139
+ # <-----------> l1
140
+ # <-----------------------> l2
141
+ # <-----------------------------------> l3
142
+ #
143
+ # f([t1, t2, t3, t4]) -> { t1 => 0, t2 => l1, t3 => l2, t4 => l3 }
144
+ #
145
+ def compute_lengths_from_beginning(times, outlier: nil)
146
+ times.each_with_object({}) do |time, ret|
147
+ ret[time] = if time == outlier && times.size > 2
148
+ # If it contains an extremely large value such as 9999-12-31,
149
+ # that point will have a large effect on the visualization,
150
+ # so adjust the length so that it is half of the whole.
151
+ ret.values.last * 2
152
+ else
153
+ time - times.min
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -6,6 +6,7 @@ require "activerecord-bitemporal/bitemporal"
6
6
  require "activerecord-bitemporal/scope"
7
7
  require "activerecord-bitemporal/patches"
8
8
  require "activerecord-bitemporal/version"
9
+ require "activerecord-bitemporal/visualizer"
9
10
 
10
11
  module ActiveRecord::Bitemporal
11
12
  DEFAULT_VALID_FROM = Time.utc(1900, 12, 31).in_time_zone.freeze
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: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
- - mserizawa
7
+ - SmartHR
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-12 00:00:00.000000000 Z
11
+ date: 2022-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -138,7 +138,7 @@ dependencies:
138
138
  version: '0'
139
139
  description: Enable ActiveRecord models to be handled as BiTemporal Data Model.
140
140
  email:
141
- - serizawa@smarthr.co.jp
141
+ - oss@smarthr.co.jp
142
142
  executables: []
143
143
  extensions: []
144
144
  extra_rdoc_files: []
@@ -168,6 +168,7 @@ files:
168
168
  - lib/activerecord-bitemporal/patches.rb
169
169
  - lib/activerecord-bitemporal/scope.rb
170
170
  - lib/activerecord-bitemporal/version.rb
171
+ - lib/activerecord-bitemporal/visualizer.rb
171
172
  homepage: https://github.com/kufu/activerecord-bitemporal
172
173
  licenses:
173
174
  - Apache 2.0