temporal_tables 0.7.1 → 1.0.2
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/workflows/test.yml +53 -0
- data/.rubocop.yml +158 -0
- data/.ruby-version +1 -1
- data/.travis.yml +5 -7
- data/Gemfile +2 -0
- data/README.md +15 -5
- data/Rakefile +7 -2
- data/config.ru +2 -0
- data/gemfiles/Gemfile.6.0.mysql.lock +84 -84
- data/gemfiles/Gemfile.6.0.pg.lock +103 -98
- data/gemfiles/Gemfile.6.1.mysql.lock +180 -0
- data/gemfiles/Gemfile.6.1.pg.lock +180 -0
- data/gemfiles/{Gemfile.5.1.mysql → Gemfile.7.0.mysql} +2 -2
- data/gemfiles/Gemfile.7.0.mysql.lock +173 -0
- data/gemfiles/{Gemfile.5.2.pg → Gemfile.7.0.pg} +1 -1
- data/gemfiles/Gemfile.7.0.pg.lock +173 -0
- data/lib/temporal_tables/arel_table.rb +10 -9
- data/lib/temporal_tables/association_extensions.rb +2 -0
- data/lib/temporal_tables/connection_adapters/mysql_adapter.rb +8 -3
- data/lib/temporal_tables/connection_adapters/postgresql_adapter.rb +5 -3
- data/lib/temporal_tables/history_hook.rb +8 -5
- data/lib/temporal_tables/preloader_extensions.rb +2 -0
- data/lib/temporal_tables/reflection_extensions.rb +11 -14
- data/lib/temporal_tables/relation_extensions.rb +11 -24
- data/lib/temporal_tables/temporal_adapter.rb +93 -97
- data/lib/temporal_tables/temporal_adapter_six_oh.rb +187 -0
- data/lib/temporal_tables/temporal_class.rb +29 -28
- data/lib/temporal_tables/version.rb +3 -1
- data/lib/temporal_tables/whodunnit.rb +5 -3
- data/lib/temporal_tables.rb +48 -33
- data/spec/basic_history_spec.rb +65 -35
- data/spec/internal/app/models/broom.rb +2 -0
- data/spec/internal/app/models/cat.rb +5 -0
- data/spec/internal/app/models/cat_life.rb +5 -0
- data/spec/internal/app/models/coven.rb +2 -0
- data/spec/internal/app/models/flying_machine.rb +2 -0
- data/spec/internal/app/models/person.rb +2 -0
- data/spec/internal/app/models/rocket_broom.rb +2 -0
- data/spec/internal/app/models/wart.rb +3 -1
- data/spec/internal/config/database.ci.yml +12 -0
- data/spec/internal/db/schema.rb +28 -5
- data/spec/spec_helper.rb +39 -5
- data/spec/support/database.rb +10 -6
- data/temporal_tables.gemspec +31 -18
- metadata +108 -39
- data/CHANGELOG.md +0 -73
- data/gemfiles/Gemfile.5.0.mysql +0 -16
- data/gemfiles/Gemfile.5.0.mysql.lock +0 -147
- data/gemfiles/Gemfile.5.0.pg +0 -16
- data/gemfiles/Gemfile.5.0.pg.lock +0 -147
- data/gemfiles/Gemfile.5.1.mysql.lock +0 -147
- data/gemfiles/Gemfile.5.1.pg +0 -16
- data/gemfiles/Gemfile.5.1.pg.lock +0 -147
- data/gemfiles/Gemfile.5.2.mysql +0 -16
- data/gemfiles/Gemfile.5.2.mysql.lock +0 -155
- data/gemfiles/Gemfile.5.2.pg.lock +0 -155
- data/lib/temporal_tables/join_extensions.rb +0 -20
- data/spec/extensions/combustion.rb +0 -9
- data/spec/internal/config/routes.rb +0 -3
@@ -1,39 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TemporalTables
|
2
|
-
module TemporalAdapter
|
3
|
-
def create_table(table_name, options
|
4
|
+
module TemporalAdapter # rubocop:disable Metrics/ModuleLength
|
5
|
+
def create_table(table_name, **options, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
4
6
|
if options[:temporal_bypass]
|
5
|
-
super
|
7
|
+
super(table_name, **options, &block)
|
6
8
|
else
|
7
9
|
skip_table = TemporalTables.skipped_temporal_tables.include?(table_name.to_sym) || table_name.to_s =~ /_h$/
|
8
10
|
|
9
|
-
super
|
11
|
+
super(table_name, **options) do |t|
|
10
12
|
block.call t
|
11
13
|
|
12
14
|
if TemporalTables.add_updated_by_field && !skip_table
|
13
|
-
updated_by_already_exists = t.columns.any? { |c| c.name ==
|
15
|
+
updated_by_already_exists = t.columns.any? { |c| c.name == 'updated_by' }
|
14
16
|
if updated_by_already_exists
|
15
|
-
puts "consider adding #{table_name} to TemporalTables skip_table"
|
17
|
+
puts "consider adding #{table_name} to TemporalTables skip_table" # rubocop:disable Rails/Output
|
16
18
|
else
|
17
|
-
t.column
|
19
|
+
t.column(:updated_by, TemporalTables.updated_by_type)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
24
|
if options[:temporal] || (TemporalTables.create_by_default && !skip_table)
|
23
|
-
add_temporal_table table_name, options
|
25
|
+
add_temporal_table table_name, **options
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
28
|
-
def add_temporal_table(table_name, options
|
29
|
-
create_table
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
def add_temporal_table(table_name, **options) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
31
|
+
create_table(
|
32
|
+
temporal_name(table_name),
|
33
|
+
**options.merge(id: false, primary_key: 'history_id', temporal_bypass: true)
|
34
|
+
) do |t|
|
35
|
+
t.column :id, options.fetch(:id, :integer) if options[:id] != false
|
36
|
+
t.datetime :eff_from, null: false, limit: 6
|
37
|
+
t.datetime :eff_to, null: false, limit: 6, default: '9999-12-31'
|
38
|
+
|
39
|
+
columns(table_name).each do |c|
|
40
|
+
next if c.name == 'id'
|
33
41
|
|
34
|
-
|
35
|
-
next if c.name == "id"
|
36
|
-
t.send c.type, c.name, :limit => c.limit
|
42
|
+
t.send c.type, c.name, limit: c.limit
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
@@ -49,106 +55,108 @@ module TemporalTables
|
|
49
55
|
end
|
50
56
|
|
51
57
|
def remove_temporal_table(table_name)
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
58
|
+
return unless table_exists?(temporal_name(table_name))
|
59
|
+
|
60
|
+
drop_temporal_triggers table_name
|
61
|
+
drop_table_without_temporal temporal_name(table_name)
|
56
62
|
end
|
57
63
|
|
58
|
-
def drop_table(table_name, options
|
59
|
-
super
|
64
|
+
def drop_table(table_name, **options)
|
65
|
+
super(table_name, **options)
|
60
66
|
|
61
|
-
if table_exists?(temporal_name(table_name))
|
62
|
-
super temporal_name(table_name), options
|
63
|
-
end
|
67
|
+
super(temporal_name(table_name), **options) if table_exists?(temporal_name(table_name))
|
64
68
|
end
|
65
69
|
|
66
70
|
def rename_table(name, new_name)
|
67
|
-
if table_exists?(temporal_name(name))
|
68
|
-
drop_temporal_triggers name
|
69
|
-
end
|
71
|
+
drop_temporal_triggers name if table_exists?(temporal_name(name))
|
70
72
|
|
71
73
|
super name, new_name
|
72
74
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
75
|
+
return unless table_exists?(temporal_name(name))
|
76
|
+
|
77
|
+
super(temporal_name(name), temporal_name(new_name))
|
78
|
+
create_temporal_triggers new_name
|
77
79
|
end
|
78
80
|
|
79
|
-
def add_column(table_name, column_name, type, options
|
80
|
-
super
|
81
|
+
def add_column(table_name, column_name, type, **options)
|
82
|
+
super(table_name, column_name, type, **options)
|
81
83
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
84
|
+
return unless table_exists?(temporal_name(table_name))
|
85
|
+
|
86
|
+
super temporal_name(table_name), column_name, type, **options
|
87
|
+
create_temporal_triggers table_name
|
86
88
|
end
|
87
89
|
|
88
|
-
def
|
89
|
-
super
|
90
|
+
def remove_columns(table_name, *column_names, **options)
|
91
|
+
super(table_name, *column_names, **options)
|
90
92
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
93
|
+
return unless table_exists?(temporal_name(table_name))
|
94
|
+
|
95
|
+
super temporal_name(table_name), *column_names, **options
|
96
|
+
create_temporal_triggers table_name
|
95
97
|
end
|
96
98
|
|
97
|
-
def
|
98
|
-
super
|
99
|
+
def remove_column(table_name, column_name, type = nil, **options)
|
100
|
+
super(table_name, column_name, type, **options)
|
99
101
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
102
|
+
return unless table_exists?(temporal_name(table_name))
|
103
|
+
|
104
|
+
super temporal_name(table_name), column_name, type, **options
|
105
|
+
create_temporal_triggers table_name
|
106
|
+
end
|
107
|
+
|
108
|
+
def change_column(table_name, column_name, type, **options)
|
109
|
+
super(table_name, column_name, type, **options)
|
110
|
+
|
111
|
+
return unless table_exists?(temporal_name(table_name))
|
112
|
+
|
113
|
+
super temporal_name(table_name), column_name, type, **options
|
114
|
+
# Don't need to update triggers here...
|
104
115
|
end
|
105
116
|
|
106
117
|
def rename_column(table_name, column_name, new_column_name)
|
107
|
-
super
|
118
|
+
super(table_name, column_name, new_column_name)
|
108
119
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
120
|
+
return unless table_exists?(temporal_name(table_name))
|
121
|
+
|
122
|
+
super temporal_name(table_name), column_name, new_column_name
|
123
|
+
create_temporal_triggers table_name
|
113
124
|
end
|
114
125
|
|
115
|
-
def add_index(table_name, column_name, options
|
116
|
-
super
|
126
|
+
def add_index(table_name, column_name, **options)
|
127
|
+
super(table_name, column_name, **options)
|
117
128
|
|
118
|
-
|
119
|
-
column_names = Array.wrap(column_name)
|
120
|
-
idx_name = temporal_index_name(options[:name] || index_name(table_name, :column => column_names))
|
129
|
+
return unless table_exists?(temporal_name(table_name))
|
121
130
|
|
122
|
-
|
123
|
-
|
131
|
+
idx_name = temporal_index_name(options[:name] || index_name(table_name, column: column_name))
|
132
|
+
super temporal_name(table_name), column_name, **options.except(:unique).merge(name: idx_name)
|
124
133
|
end
|
125
134
|
|
126
|
-
def remove_index(table_name,
|
127
|
-
super
|
135
|
+
def remove_index(table_name, column_name = nil, **options)
|
136
|
+
super(table_name, column_name, **options)
|
128
137
|
|
129
|
-
|
130
|
-
idx_name = temporal_index_name(index_name(table_name, options))
|
138
|
+
return unless table_exists?(temporal_name(table_name))
|
131
139
|
|
132
|
-
|
133
|
-
|
140
|
+
idx_name = temporal_index_name(options[:name] || index_name_for_remove(table_name, column_name, options))
|
141
|
+
super temporal_name(table_name), column_name, name: idx_name
|
134
142
|
end
|
135
143
|
|
136
|
-
def create_temporal_indexes(table_name)
|
144
|
+
def create_temporal_indexes(table_name) # rubocop:disable Metrics/MethodLength
|
137
145
|
indexes = ActiveRecord::Base.connection.indexes(table_name)
|
138
146
|
|
139
147
|
indexes.each do |index|
|
140
148
|
index_name = temporal_index_name(index.name)
|
141
149
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
150
|
+
next if temporal_index_exists?(table_name, index_name)
|
151
|
+
|
152
|
+
add_index(
|
153
|
+
temporal_name(table_name),
|
154
|
+
index.columns,
|
155
|
+
# exclude unique constraints for temporal tables
|
156
|
+
name: index_name,
|
157
|
+
length: index.lengths,
|
158
|
+
order: index.orders
|
159
|
+
)
|
152
160
|
end
|
153
161
|
end
|
154
162
|
|
@@ -156,33 +164,21 @@ module TemporalTables
|
|
156
164
|
"#{table_name}_h"
|
157
165
|
end
|
158
166
|
|
159
|
-
def create_temporal_triggers(
|
160
|
-
raise NotImplementedError,
|
167
|
+
def create_temporal_triggers(_table_name)
|
168
|
+
raise NotImplementedError, 'create_temporal_triggers is not implemented'
|
161
169
|
end
|
162
170
|
|
163
|
-
def drop_temporal_triggers(
|
164
|
-
raise NotImplementedError,
|
171
|
+
def drop_temporal_triggers(_table_name)
|
172
|
+
raise NotImplementedError, 'drop_temporal_triggers is not implemented'
|
165
173
|
end
|
166
174
|
|
167
175
|
# It's important not to increase the length of the returned string.
|
168
176
|
def temporal_index_name(index_name)
|
169
|
-
index_name.to_s.sub(/^index/,
|
177
|
+
index_name.to_s.sub(/^index/, 'ind_h').sub(/_ix(\d+)$/, '_hi\1')
|
170
178
|
end
|
171
179
|
|
172
180
|
def temporal_index_exists?(table_name, index_name)
|
173
|
-
|
174
|
-
when 5
|
175
|
-
case Rails::VERSION::MINOR
|
176
|
-
when 0
|
177
|
-
index_name_exists?(temporal_name(table_name), index_name, false)
|
178
|
-
else
|
179
|
-
index_name_exists?(temporal_name(table_name), index_name)
|
180
|
-
end
|
181
|
-
when 6
|
182
|
-
index_name_exists?(temporal_name(table_name), index_name)
|
183
|
-
else
|
184
|
-
raise "Rails version not supported"
|
185
|
-
end
|
181
|
+
index_name_exists?(temporal_name(table_name), index_name)
|
186
182
|
end
|
187
183
|
end
|
188
184
|
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TemporalTables
|
4
|
+
# The main difference here is the add_index method, which still uses
|
5
|
+
# the old options={} syntax
|
6
|
+
module TemporalAdapterSixOh # rubocop:disable Metrics/ModuleLength
|
7
|
+
def create_table(table_name, **options, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
8
|
+
if options[:temporal_bypass]
|
9
|
+
super(table_name, **options, &block)
|
10
|
+
else
|
11
|
+
skip_table = TemporalTables.skipped_temporal_tables.include?(table_name.to_sym) || table_name.to_s =~ /_h$/
|
12
|
+
|
13
|
+
super(table_name, **options) do |t|
|
14
|
+
block.call t
|
15
|
+
|
16
|
+
if TemporalTables.add_updated_by_field && !skip_table
|
17
|
+
updated_by_already_exists = t.columns.any? { |c| c.name == 'updated_by' }
|
18
|
+
if updated_by_already_exists
|
19
|
+
puts "consider adding #{table_name} to TemporalTables skip_table" # rubocop:disable Rails/Output
|
20
|
+
else
|
21
|
+
t.column(:updated_by, TemporalTables.updated_by_type)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if options[:temporal] || (TemporalTables.create_by_default && !skip_table)
|
27
|
+
add_temporal_table table_name, **options
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_temporal_table(table_name, **options) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
33
|
+
create_table(
|
34
|
+
temporal_name(table_name),
|
35
|
+
**options.merge(id: false, primary_key: 'history_id', temporal_bypass: true)
|
36
|
+
) do |t|
|
37
|
+
t.column :id, options.fetch(:id, :integer) if options[:id] != false
|
38
|
+
t.datetime :eff_from, null: false, limit: 6
|
39
|
+
t.datetime :eff_to, null: false, limit: 6, default: '9999-12-31'
|
40
|
+
|
41
|
+
columns(table_name).each do |c|
|
42
|
+
next if c.name == 'id'
|
43
|
+
|
44
|
+
t.send c.type, c.name, limit: c.limit
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
if TemporalTables.add_updated_by_field && !column_exists?(table_name, :updated_by)
|
49
|
+
change_table table_name do |t|
|
50
|
+
t.column :updated_by, TemporalTables.updated_by_type
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
add_index temporal_name(table_name), [:id, :eff_to]
|
55
|
+
create_temporal_triggers table_name
|
56
|
+
create_temporal_indexes table_name
|
57
|
+
end
|
58
|
+
|
59
|
+
def remove_temporal_table(table_name)
|
60
|
+
return unless table_exists?(temporal_name(table_name))
|
61
|
+
|
62
|
+
drop_temporal_triggers table_name
|
63
|
+
drop_table_without_temporal temporal_name(table_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def drop_table(table_name, **options)
|
67
|
+
super(table_name, **options)
|
68
|
+
|
69
|
+
super(temporal_name(table_name), **options) if table_exists?(temporal_name(table_name))
|
70
|
+
end
|
71
|
+
|
72
|
+
def rename_table(name, new_name)
|
73
|
+
drop_temporal_triggers name if table_exists?(temporal_name(name))
|
74
|
+
|
75
|
+
super name, new_name
|
76
|
+
|
77
|
+
return unless table_exists?(temporal_name(name))
|
78
|
+
|
79
|
+
super(temporal_name(name), temporal_name(new_name))
|
80
|
+
create_temporal_triggers new_name
|
81
|
+
end
|
82
|
+
|
83
|
+
def add_column(table_name, column_name, type, **options)
|
84
|
+
super(table_name, column_name, type, **options)
|
85
|
+
|
86
|
+
return unless table_exists?(temporal_name(table_name))
|
87
|
+
|
88
|
+
super temporal_name(table_name), column_name, type, **options
|
89
|
+
create_temporal_triggers table_name
|
90
|
+
end
|
91
|
+
|
92
|
+
def remove_columns(table_name, *column_names, **options)
|
93
|
+
super(table_name, *column_names, **options)
|
94
|
+
|
95
|
+
return unless table_exists?(temporal_name(table_name))
|
96
|
+
|
97
|
+
super temporal_name(table_name), *column_names, **options
|
98
|
+
create_temporal_triggers table_name
|
99
|
+
end
|
100
|
+
|
101
|
+
def remove_column(table_name, column_name, type = nil, **options)
|
102
|
+
super(table_name, column_name, type, **options)
|
103
|
+
|
104
|
+
return unless table_exists?(temporal_name(table_name))
|
105
|
+
|
106
|
+
super temporal_name(table_name), column_name, type, **options
|
107
|
+
create_temporal_triggers table_name
|
108
|
+
end
|
109
|
+
|
110
|
+
def change_column(table_name, column_name, type, **options)
|
111
|
+
super(table_name, column_name, type, **options)
|
112
|
+
|
113
|
+
return unless table_exists?(temporal_name(table_name))
|
114
|
+
|
115
|
+
super temporal_name(table_name), column_name, type, **options
|
116
|
+
# Don't need to update triggers here...
|
117
|
+
end
|
118
|
+
|
119
|
+
def rename_column(table_name, column_name, new_column_name)
|
120
|
+
super(table_name, column_name, new_column_name)
|
121
|
+
|
122
|
+
return unless table_exists?(temporal_name(table_name))
|
123
|
+
|
124
|
+
super temporal_name(table_name), column_name, new_column_name
|
125
|
+
create_temporal_triggers table_name
|
126
|
+
end
|
127
|
+
|
128
|
+
def add_index(table_name, column_name, options = {})
|
129
|
+
super(table_name, column_name, options)
|
130
|
+
|
131
|
+
return unless table_exists?(temporal_name(table_name))
|
132
|
+
|
133
|
+
column_names = Array.wrap(column_name)
|
134
|
+
idx_name = temporal_index_name(options[:name] || index_name(table_name, column: column_names))
|
135
|
+
super temporal_name(table_name), column_name, options.except(:unique).merge(name: idx_name)
|
136
|
+
end
|
137
|
+
|
138
|
+
def remove_index(table_name, options = {})
|
139
|
+
super(table_name, options)
|
140
|
+
|
141
|
+
return unless table_exists?(temporal_name(table_name))
|
142
|
+
|
143
|
+
idx_name = temporal_index_name(index_name_for_remove(table_name, options))
|
144
|
+
super temporal_name(table_name), name: idx_name
|
145
|
+
end
|
146
|
+
|
147
|
+
def create_temporal_indexes(table_name) # rubocop:disable Metrics/MethodLength
|
148
|
+
indexes = ActiveRecord::Base.connection.indexes(table_name)
|
149
|
+
|
150
|
+
indexes.each do |index|
|
151
|
+
index_name = temporal_index_name(index.name)
|
152
|
+
|
153
|
+
next if temporal_index_exists?(table_name, index_name)
|
154
|
+
|
155
|
+
add_index(
|
156
|
+
temporal_name(table_name),
|
157
|
+
index.columns,
|
158
|
+
# exclude unique constraints for temporal tables
|
159
|
+
name: index_name,
|
160
|
+
length: index.lengths,
|
161
|
+
order: index.orders
|
162
|
+
)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def temporal_name(table_name)
|
167
|
+
"#{table_name}_h"
|
168
|
+
end
|
169
|
+
|
170
|
+
def create_temporal_triggers(_table_name)
|
171
|
+
raise NotImplementedError, 'create_temporal_triggers is not implemented'
|
172
|
+
end
|
173
|
+
|
174
|
+
def drop_temporal_triggers(_table_name)
|
175
|
+
raise NotImplementedError, 'drop_temporal_triggers is not implemented'
|
176
|
+
end
|
177
|
+
|
178
|
+
# It's important not to increase the length of the returned string.
|
179
|
+
def temporal_index_name(index_name)
|
180
|
+
index_name.to_s.sub(/^index/, 'ind_h').sub(/_ix(\d+)$/, '_hi\1')
|
181
|
+
end
|
182
|
+
|
183
|
+
def temporal_index_exists?(table_name, index_name)
|
184
|
+
index_name_exists?(temporal_name(table_name), index_name)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -1,14 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TemporalTables
|
2
4
|
# This is mixed into all History classes.
|
3
5
|
module TemporalClass
|
4
|
-
def self.included(base)
|
6
|
+
def self.included(base) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
5
7
|
base.class_eval do
|
6
8
|
base.extend ClassMethods
|
7
9
|
|
8
|
-
self.table_name +=
|
10
|
+
self.table_name += '_h'
|
9
11
|
|
10
12
|
cattr_accessor :visited_associations
|
11
|
-
|
13
|
+
@visited_associations = []
|
12
14
|
|
13
15
|
# The at_value field stores the time from the query that yielded
|
14
16
|
# this record.
|
@@ -23,26 +25,27 @@ module TemporalTables
|
|
23
25
|
# Iterates all associations, makes sure their history classes are
|
24
26
|
# created and initialized, and modifies the associations to point
|
25
27
|
# to the target classes' history classes.
|
26
|
-
def self.temporalize_associations!
|
28
|
+
def self.temporalize_associations! # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
27
29
|
reflect_on_all_associations.dup.each do |association|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
30
|
+
next if @visited_associations.include?(association.name) || association.options[:polymorphic]
|
31
|
+
|
32
|
+
@visited_associations << association.name
|
33
|
+
|
34
|
+
# Calling .history here will ensure that the history class
|
35
|
+
# for this association is created and initialized
|
36
|
+
clazz = association.class_name.constantize.history
|
37
|
+
|
38
|
+
# Recreate the association, updating it to point at the
|
39
|
+
# history class. The foreign key is explicitly set since it's
|
40
|
+
# inferred from the class_name, but shouldn't be in this case.
|
41
|
+
send(
|
42
|
+
association.macro, association.name,
|
43
|
+
**association.options.merge(
|
44
|
+
class_name: clazz.name,
|
45
|
+
foreign_key: association.foreign_key,
|
46
|
+
primary_key: clazz.orig_class.primary_key
|
44
47
|
)
|
45
|
-
|
48
|
+
)
|
46
49
|
end
|
47
50
|
end
|
48
51
|
end
|
@@ -50,11 +53,11 @@ module TemporalTables
|
|
50
53
|
|
51
54
|
module STIWithHistory
|
52
55
|
def sti_name
|
53
|
-
super.sub
|
56
|
+
super.sub(/History$/, '')
|
54
57
|
end
|
55
58
|
|
56
59
|
def find_sti_class(type_name)
|
57
|
-
type_name +=
|
60
|
+
type_name += 'History' unless type_name =~ /History\Z/
|
58
61
|
|
59
62
|
begin
|
60
63
|
super
|
@@ -66,12 +69,10 @@ module TemporalTables
|
|
66
69
|
|
67
70
|
module ClassMethods
|
68
71
|
def orig_class
|
69
|
-
name.sub(/History$/,
|
72
|
+
name.sub(/History$/, '').constantize
|
70
73
|
end
|
71
74
|
|
72
|
-
|
73
|
-
superclass.descends_from_active_record?
|
74
|
-
end
|
75
|
+
delegate :descends_from_active_record?, to: :superclass
|
75
76
|
|
76
77
|
def build_temporal_constraint(at_value)
|
77
78
|
arel_table[:eff_to].gteq(at_value).and(
|
@@ -89,7 +90,7 @@ module TemporalTables
|
|
89
90
|
end
|
90
91
|
|
91
92
|
def orig_obj
|
92
|
-
@orig_obj ||= orig_class.
|
93
|
+
@orig_obj ||= orig_class.find_by id: orig_id
|
93
94
|
end
|
94
95
|
|
95
96
|
def prev
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TemporalTables
|
2
4
|
module Whodunnit
|
3
5
|
def self.included(base)
|
@@ -10,9 +12,9 @@ module TemporalTables
|
|
10
12
|
|
11
13
|
module InstanceMethods
|
12
14
|
def set_updated_by
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
return unless TemporalTables.updated_by_proc && respond_to?(:updated_by)
|
16
|
+
|
17
|
+
self.updated_by = TemporalTables.updated_by_proc.call(self)
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|