chrono_model 1.2.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +19 -20
- data/README.md +62 -40
- data/lib/active_record/connection_adapters/chronomodel_adapter.rb +17 -11
- data/lib/active_record/tasks/chronomodel_database_tasks.rb +64 -23
- data/lib/chrono_model/adapter/ddl.rb +168 -153
- data/lib/chrono_model/adapter/indexes.rb +99 -94
- data/lib/chrono_model/adapter/migrations.rb +81 -104
- data/lib/chrono_model/adapter/migrations_modules/legacy.rb +41 -0
- data/lib/chrono_model/adapter/migrations_modules/stable.rb +41 -0
- data/lib/chrono_model/adapter/tsrange.rb +20 -5
- data/lib/chrono_model/adapter/upgrade.rb +89 -91
- data/lib/chrono_model/adapter.rb +64 -31
- data/lib/chrono_model/chrono.rb +17 -0
- data/lib/chrono_model/conversions.rb +15 -9
- data/lib/chrono_model/db_console.rb +9 -0
- data/lib/chrono_model/json.rb +9 -6
- data/lib/chrono_model/patches/as_of_time_holder.rb +2 -2
- data/lib/chrono_model/patches/as_of_time_relation.rb +2 -2
- data/lib/chrono_model/patches/association.rb +15 -12
- data/lib/chrono_model/patches/batches.rb +17 -0
- data/lib/chrono_model/patches/db_console.rb +20 -4
- data/lib/chrono_model/patches/join_node.rb +4 -4
- data/lib/chrono_model/patches/preloader.rb +41 -11
- data/lib/chrono_model/patches/relation.rb +53 -8
- data/lib/chrono_model/patches.rb +3 -1
- data/lib/chrono_model/railtie.rb +29 -24
- data/lib/chrono_model/time_gate.rb +3 -3
- data/lib/chrono_model/time_machine/history_model.rb +65 -31
- data/lib/chrono_model/time_machine/time_query.rb +65 -49
- data/lib/chrono_model/time_machine/timeline.rb +52 -28
- data/lib/chrono_model/time_machine.rb +66 -25
- data/lib/chrono_model/utilities.rb +3 -3
- data/lib/chrono_model/version.rb +3 -1
- data/lib/chrono_model.rb +31 -36
- metadata +39 -136
- data/.gitignore +0 -21
- data/.rspec +0 -2
- data/.travis.yml +0 -41
- data/Gemfile +0 -4
- data/README.sql +0 -161
- data/Rakefile +0 -25
- data/chrono_model.gemspec +0 -33
- data/gemfiles/rails_5.0.gemfile +0 -6
- data/gemfiles/rails_5.1.gemfile +0 -6
- data/gemfiles/rails_5.2.gemfile +0 -6
- data/spec/aruba/dbconsole_spec.rb +0 -25
- data/spec/aruba/fixtures/database_with_default_username_and_password.yml +0 -14
- data/spec/aruba/fixtures/database_without_username_and_password.yml +0 -11
- data/spec/aruba/fixtures/empty_structure.sql +0 -27
- data/spec/aruba/fixtures/migrations/56/20160812190335_create_impressions.rb +0 -10
- data/spec/aruba/fixtures/migrations/56/20171115195229_add_temporal_extension_to_impressions.rb +0 -10
- data/spec/aruba/fixtures/railsapp/config/application.rb +0 -17
- data/spec/aruba/fixtures/railsapp/config/boot.rb +0 -5
- data/spec/aruba/fixtures/railsapp/config/environments/development.rb +0 -38
- data/spec/aruba/migrations_spec.rb +0 -48
- data/spec/aruba/rake_task_spec.rb +0 -71
- data/spec/chrono_model/adapter/base_spec.rb +0 -157
- data/spec/chrono_model/adapter/ddl_spec.rb +0 -243
- data/spec/chrono_model/adapter/indexes_spec.rb +0 -72
- data/spec/chrono_model/adapter/migrations_spec.rb +0 -312
- data/spec/chrono_model/conversions_spec.rb +0 -43
- data/spec/chrono_model/history_models_spec.rb +0 -32
- data/spec/chrono_model/json_ops_spec.rb +0 -59
- data/spec/chrono_model/time_machine/as_of_spec.rb +0 -188
- data/spec/chrono_model/time_machine/changes_spec.rb +0 -50
- data/spec/chrono_model/time_machine/counter_cache_race_spec.rb +0 -46
- data/spec/chrono_model/time_machine/default_scope_spec.rb +0 -37
- data/spec/chrono_model/time_machine/history_spec.rb +0 -104
- data/spec/chrono_model/time_machine/keep_cool_spec.rb +0 -27
- data/spec/chrono_model/time_machine/manipulations_spec.rb +0 -84
- data/spec/chrono_model/time_machine/model_identification_spec.rb +0 -46
- data/spec/chrono_model/time_machine/sequence_spec.rb +0 -74
- data/spec/chrono_model/time_machine/sti_spec.rb +0 -100
- data/spec/chrono_model/time_machine/time_query_spec.rb +0 -261
- data/spec/chrono_model/time_machine/timeline_spec.rb +0 -63
- data/spec/chrono_model/time_machine/timestamps_spec.rb +0 -43
- data/spec/chrono_model/time_machine/transactions_spec.rb +0 -69
- data/spec/config.travis.yml +0 -5
- data/spec/config.yml.example +0 -9
- data/spec/spec_helper.rb +0 -33
- data/spec/support/adapter/helpers.rb +0 -53
- data/spec/support/adapter/structure.rb +0 -44
- data/spec/support/aruba.rb +0 -44
- data/spec/support/connection.rb +0 -70
- data/spec/support/matchers/base.rb +0 -56
- data/spec/support/matchers/column.rb +0 -99
- data/spec/support/matchers/function.rb +0 -79
- data/spec/support/matchers/index.rb +0 -69
- data/spec/support/matchers/schema.rb +0 -39
- data/spec/support/matchers/table.rb +0 -275
- data/spec/support/time_machine/helpers.rb +0 -47
- data/spec/support/time_machine/structure.rb +0 -111
- data/sql/json_ops.sql +0 -56
- data/sql/uninstall-json_ops.sql +0 -24
data/lib/chrono_model/adapter.rb
CHANGED
@@ -1,13 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_record/connection_adapters/postgresql_adapter'
|
2
4
|
|
3
5
|
require 'chrono_model/adapter/migrations'
|
6
|
+
|
7
|
+
if ActiveRecord::VERSION::STRING >= '6.1'
|
8
|
+
require 'chrono_model/adapter/migrations_modules/stable'
|
9
|
+
else
|
10
|
+
require 'chrono_model/adapter/migrations_modules/legacy'
|
11
|
+
end
|
12
|
+
|
4
13
|
require 'chrono_model/adapter/ddl'
|
5
14
|
require 'chrono_model/adapter/indexes'
|
6
15
|
require 'chrono_model/adapter/tsrange'
|
7
16
|
require 'chrono_model/adapter/upgrade'
|
8
17
|
|
9
18
|
module ChronoModel
|
10
|
-
|
11
19
|
# This class implements all ActiveRecord::ConnectionAdapters::SchemaStatements
|
12
20
|
# methods adding support for temporal extensions. It inherits from the Postgres
|
13
21
|
# adapter for a clean override of its methods using super.
|
@@ -25,12 +33,28 @@ module ChronoModel
|
|
25
33
|
# The schema holding historical data
|
26
34
|
HISTORY_SCHEMA = 'history'
|
27
35
|
|
36
|
+
if ActiveRecord::VERSION::STRING >= '7.1'
|
37
|
+
def initialize(*)
|
38
|
+
super
|
39
|
+
|
40
|
+
connect!
|
41
|
+
|
42
|
+
unless chrono_supported?
|
43
|
+
raise ChronoModel::Error, 'Your database server is not supported by ChronoModel. ' \
|
44
|
+
'Currently, only PostgreSQL >= 9.3 is supported.'
|
45
|
+
end
|
46
|
+
|
47
|
+
chrono_setup!
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
28
51
|
# Returns true whether the connection adapter supports our
|
29
52
|
# implementation of temporal tables. Currently, Chronomodel
|
30
|
-
# is supported starting with PostgreSQL 9.3
|
53
|
+
# is supported starting with PostgreSQL 9.3 (90300 in PostgreSQL's
|
54
|
+
# `PG_VERSION_NUM` numeric format).
|
31
55
|
#
|
32
56
|
def chrono_supported?
|
33
|
-
postgresql_version >= 90300
|
57
|
+
postgresql_version >= 90300 # rubocop:disable Style/NumericLiterals
|
34
58
|
end
|
35
59
|
|
36
60
|
def chrono_setup!
|
@@ -53,13 +77,13 @@ module ChronoModel
|
|
53
77
|
#
|
54
78
|
# NOTE: These methods are dynamically defined, see the source.
|
55
79
|
#
|
56
|
-
def primary_key(table_name)
|
57
|
-
end
|
80
|
+
def primary_key(table_name); end
|
58
81
|
|
59
|
-
[
|
82
|
+
%i[primary_key indexes default_sequence_name].each do |method|
|
60
83
|
define_method(method) do |*args|
|
61
84
|
table_name = args.first
|
62
85
|
return super(*args) unless is_chrono?(table_name)
|
86
|
+
|
63
87
|
on_schema(TEMPORAL_SCHEMA, recurse: :ignore) { super(*args) }
|
64
88
|
end
|
65
89
|
end
|
@@ -73,12 +97,12 @@ module ChronoModel
|
|
73
97
|
#
|
74
98
|
# NOTE: This method is dynamically defined, see the source.
|
75
99
|
#
|
76
|
-
def column_definitions
|
77
|
-
end
|
100
|
+
def column_definitions; end
|
78
101
|
|
79
102
|
define_method(:column_definitions) do |table_name|
|
80
103
|
return super(table_name) unless is_chrono?(table_name)
|
81
|
-
|
104
|
+
|
105
|
+
on_schema("#{TEMPORAL_SCHEMA},#{schema_search_path}", recurse: :ignore) { super(table_name) }
|
82
106
|
end
|
83
107
|
|
84
108
|
# Evaluates the given block in the temporal schema.
|
@@ -101,16 +125,15 @@ module ChronoModel
|
|
101
125
|
# See specs for examples and behaviour.
|
102
126
|
#
|
103
127
|
def on_schema(schema, recurse: :follow)
|
104
|
-
old_path =
|
128
|
+
old_path = schema_search_path
|
105
129
|
|
106
130
|
count_recursions do
|
107
|
-
if recurse == :follow
|
131
|
+
if (recurse == :follow) || (Thread.current['recursions'] == 1)
|
108
132
|
self.schema_search_path = schema
|
109
133
|
end
|
110
134
|
|
111
135
|
yield
|
112
136
|
end
|
113
|
-
|
114
137
|
ensure
|
115
138
|
# If the transaction is aborted, any execute() call will raise
|
116
139
|
# "transaction is aborted errors" - thus calling the Adapter's
|
@@ -121,7 +144,7 @@ module ChronoModel
|
|
121
144
|
# transaction ends.
|
122
145
|
#
|
123
146
|
transaction_aborted =
|
124
|
-
|
147
|
+
chrono_connection.transaction_status == PG::Connection::PQTRANS_INERROR
|
125
148
|
|
126
149
|
if transaction_aborted && Thread.current['recursions'] == 1
|
127
150
|
@schema_search_path = nil
|
@@ -143,7 +166,8 @@ module ChronoModel
|
|
143
166
|
def chrono_metadata_for(view_name)
|
144
167
|
comment = select_value(
|
145
168
|
"SELECT obj_description(#{quote(view_name)}::regclass)",
|
146
|
-
"ChronoModel metadata for #{view_name}"
|
169
|
+
"ChronoModel metadata for #{view_name}"
|
170
|
+
) if data_source_exists?(view_name)
|
147
171
|
|
148
172
|
MultiJson.load(comment || '{}').with_indifferent_access
|
149
173
|
end
|
@@ -153,29 +177,38 @@ module ChronoModel
|
|
153
177
|
def chrono_metadata_set(view_name, metadata)
|
154
178
|
comment = MultiJson.dump(metadata)
|
155
179
|
|
156
|
-
execute %
|
180
|
+
execute %( COMMENT ON VIEW #{view_name} IS #{quote(comment)} )
|
181
|
+
end
|
182
|
+
|
183
|
+
def valid_table_definition_options
|
184
|
+
super + %i[temporal journal no_journal full_journal]
|
157
185
|
end
|
158
186
|
|
159
187
|
private
|
160
|
-
# Counts the number of recursions in a thread local variable
|
161
|
-
#
|
162
|
-
def count_recursions # yield
|
163
|
-
Thread.current['recursions'] ||= 0
|
164
|
-
Thread.current['recursions'] += 1
|
165
188
|
|
166
|
-
|
189
|
+
# Rails 7.1 uses `@raw_connection`, older versions use `@connection`
|
190
|
+
#
|
191
|
+
def chrono_connection
|
192
|
+
@chrono_connection ||= @raw_connection || @connection
|
193
|
+
end
|
167
194
|
|
168
|
-
|
169
|
-
|
170
|
-
|
195
|
+
# Counts the number of recursions in a thread local variable
|
196
|
+
#
|
197
|
+
def count_recursions # yield
|
198
|
+
Thread.current['recursions'] ||= 0
|
199
|
+
Thread.current['recursions'] += 1
|
171
200
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
201
|
+
yield
|
202
|
+
ensure
|
203
|
+
Thread.current['recursions'] -= 1
|
204
|
+
end
|
205
|
+
|
206
|
+
# Create the temporal and history schemas, unless they already exist
|
207
|
+
#
|
208
|
+
def chrono_ensure_schemas
|
209
|
+
[TEMPORAL_SCHEMA, HISTORY_SCHEMA].each do |schema|
|
210
|
+
execute "CREATE SCHEMA #{schema}" unless schema_exists?(schema)
|
178
211
|
end
|
212
|
+
end
|
179
213
|
end
|
180
|
-
|
181
214
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ChronoModel
|
4
|
+
# A module to add to ActiveRecord::Base to check if they are backed by
|
5
|
+
# temporal tables.
|
6
|
+
module Chrono
|
7
|
+
# Checks whether this Active Record model is backed by a temporal table
|
8
|
+
#
|
9
|
+
# @return [Boolean] false if the connection does not respond to is_chrono?
|
10
|
+
# the result of connection.is_chrono?(table_name) otherwise
|
11
|
+
def chrono?
|
12
|
+
return false unless connection.respond_to? :is_chrono?
|
13
|
+
|
14
|
+
connection.is_chrono?(table_name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,20 +1,26 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module ChronoModel
|
3
4
|
module Conversions
|
4
|
-
|
5
|
+
module_function
|
5
6
|
|
6
|
-
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(?:\.(\d+))?\z
|
7
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(?:\.(\d+))?\z/.freeze
|
7
8
|
|
9
|
+
# rubocop:disable Style/PerlBackrefs
|
8
10
|
def string_to_utc_time(string)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
return string if string.is_a?(Time)
|
12
|
+
|
13
|
+
return unless string =~ ISO_DATETIME
|
14
|
+
|
15
|
+
# .1 is .100000, not .000001
|
16
|
+
usec = $7.ljust(6, '0') unless $7.nil?
|
17
|
+
|
18
|
+
Time.utc $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec.to_i
|
13
19
|
end
|
20
|
+
# rubocop:enable Style/PerlBackrefs
|
14
21
|
|
15
22
|
def time_to_utc_string(time)
|
16
|
-
|
23
|
+
time.to_formatted_s(:db) << '.' << format('%06d', time.usec)
|
17
24
|
end
|
18
25
|
end
|
19
|
-
|
20
26
|
end
|
data/lib/chrono_model/json.rb
CHANGED
@@ -1,28 +1,31 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module ChronoModel
|
3
4
|
module Json
|
4
5
|
extend self
|
5
6
|
|
6
7
|
def create
|
7
|
-
|
8
|
+
ActiveSupport::Deprecation.warn <<-MSG.squish
|
9
|
+
ChronoModel: JSON ops are deprecated. Please migrate to JSONB.
|
10
|
+
MSG
|
8
11
|
|
9
12
|
adapter.execute 'CREATE OR REPLACE LANGUAGE plpythonu'
|
10
|
-
adapter.execute File.read(sql
|
13
|
+
adapter.execute File.read(sql('json_ops.sql'))
|
11
14
|
end
|
12
15
|
|
13
16
|
def drop
|
14
|
-
adapter.execute File.read(sql
|
17
|
+
adapter.execute File.read(sql('uninstall-json_ops.sql'))
|
15
18
|
adapter.execute 'DROP LANGUAGE IF EXISTS plpythonu'
|
16
19
|
end
|
17
20
|
|
18
21
|
private
|
22
|
+
|
19
23
|
def sql(file)
|
20
|
-
File.dirname(__FILE__)
|
24
|
+
"#{File.dirname(__FILE__)}/../../sql/#{file}"
|
21
25
|
end
|
22
26
|
|
23
27
|
def adapter
|
24
28
|
ActiveRecord::Base.connection
|
25
29
|
end
|
26
30
|
end
|
27
|
-
|
28
31
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ChronoModel
|
2
4
|
module Patches
|
3
|
-
|
4
5
|
# This class is a dummy relation whose scope is only to pass around the
|
5
6
|
# as_of_time parameters across ActiveRecord call chains.
|
6
7
|
#
|
@@ -14,6 +15,5 @@ module ChronoModel
|
|
14
15
|
end
|
15
16
|
end
|
16
17
|
end
|
17
|
-
|
18
18
|
end
|
19
19
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ChronoModel
|
2
4
|
module Patches
|
3
|
-
|
4
5
|
# Patches ActiveRecord::Associations::Association to add support for
|
5
6
|
# temporal associations.
|
6
7
|
#
|
@@ -30,23 +31,25 @@ module ChronoModel
|
|
30
31
|
|
31
32
|
scope.as_of_time!(owner.as_of_time)
|
32
33
|
|
33
|
-
|
34
|
+
scope
|
34
35
|
end
|
35
36
|
|
36
37
|
private
|
37
|
-
def _chrono_record?
|
38
|
-
owner.respond_to?(:as_of_time) && owner.as_of_time.present?
|
39
|
-
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
reflection.klass
|
39
|
+
def _chrono_record?
|
40
|
+
owner.class.include?(ChronoModel::Patches::AsOfTimeHolder) && owner.as_of_time.present?
|
41
|
+
end
|
45
42
|
|
46
|
-
|
47
|
-
|
43
|
+
def _chrono_target?
|
44
|
+
@_target_klass ||=
|
45
|
+
if reflection.options[:polymorphic]
|
46
|
+
owner.public_send(reflection.foreign_type).constantize
|
47
|
+
else
|
48
|
+
reflection.klass
|
49
|
+
end
|
48
50
|
|
51
|
+
@_target_klass.chrono?
|
52
|
+
end
|
49
53
|
end
|
50
|
-
|
51
54
|
end
|
52
55
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ChronoModel
|
4
|
+
module Patches
|
5
|
+
module Batches
|
6
|
+
module BatchEnumerator
|
7
|
+
def each(&block)
|
8
|
+
if @relation.try(:history?)
|
9
|
+
@relation.with_hid_pkey { super }
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,11 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ChronoModel
|
2
4
|
module Patches
|
3
|
-
|
4
5
|
module DBConsole
|
5
|
-
|
6
|
-
|
6
|
+
module Config
|
7
|
+
def config
|
8
|
+
super.dup.tap { |config| config['adapter'] = 'postgresql/chronomodel' }
|
9
|
+
end
|
7
10
|
end
|
8
|
-
end
|
9
11
|
|
12
|
+
module DbConfig
|
13
|
+
def db_config
|
14
|
+
return @patched_db_config if @patched_db_config
|
15
|
+
|
16
|
+
original_db_config = super
|
17
|
+
patched_db_configuration_hash = original_db_config.configuration_hash.dup.tap do |config|
|
18
|
+
config[:adapter] = 'postgresql/chronomodel'
|
19
|
+
end
|
20
|
+
original_db_config.instance_variable_set :@configuration_hash, patched_db_configuration_hash
|
21
|
+
|
22
|
+
@patched_db_config = original_db_config
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
10
26
|
end
|
11
27
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ChronoModel
|
2
4
|
module Patches
|
3
|
-
|
4
5
|
# This class supports the AR 5.0 code that expects to receive an
|
5
6
|
# Arel::Table as the left join node. We need to replace the node
|
6
7
|
# with a virtual table that fetches from the history at a given
|
@@ -21,12 +22,11 @@ module ChronoModel
|
|
21
22
|
|
22
23
|
@as_of_time = as_of_time
|
23
24
|
|
24
|
-
virtual_table = history_model
|
25
|
-
|
25
|
+
virtual_table = history_model
|
26
|
+
.virtual_table_at(@as_of_time, table_name: @table_alias || @table_name)
|
26
27
|
|
27
28
|
super(virtual_table)
|
28
29
|
end
|
29
30
|
end
|
30
|
-
|
31
31
|
end
|
32
32
|
end
|
@@ -1,18 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ChronoModel
|
2
4
|
module Patches
|
3
|
-
|
4
5
|
# Patches ActiveRecord::Associations::Preloader to add support for
|
5
6
|
# temporal associations. This is tying itself to Rails internals
|
6
7
|
# and it is ugly :-(.
|
7
8
|
#
|
8
9
|
module Preloader
|
9
|
-
attr_reader :
|
10
|
+
attr_reader :chronomodel_options
|
10
11
|
|
11
12
|
# We overwrite the initializer in order to pass the +as_of_time+
|
12
13
|
# parameter above in the build_preloader
|
13
14
|
#
|
14
|
-
def initialize(options
|
15
|
-
@
|
15
|
+
def initialize(**options)
|
16
|
+
@chronomodel_options = options.extract!(:as_of_time, :model)
|
17
|
+
options[:scope] = chronomodel_scope(options[:scope]) if options.key?(:scope)
|
18
|
+
|
19
|
+
if options.empty?
|
20
|
+
super()
|
21
|
+
else
|
22
|
+
super(**options)
|
23
|
+
end
|
16
24
|
end
|
17
25
|
|
18
26
|
# Patches the AR Preloader (lib/active_record/associations/preloader.rb)
|
@@ -37,14 +45,20 @@ module ChronoModel
|
|
37
45
|
# so we use it directly.
|
38
46
|
#
|
39
47
|
def preload(records, associations, given_preload_scope = nil)
|
40
|
-
|
41
|
-
|
42
|
-
|
48
|
+
super(records, associations, chronomodel_scope(given_preload_scope))
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
43
52
|
|
44
|
-
|
53
|
+
def chronomodel_scope(given_preload_scope)
|
54
|
+
preload_scope = given_preload_scope
|
55
|
+
|
56
|
+
if chronomodel_options[:as_of_time]
|
57
|
+
preload_scope ||= ChronoModel::Patches::AsOfTimeRelation.new(chronomodel_options[:model])
|
58
|
+
preload_scope.as_of_time!(chronomodel_options[:as_of_time])
|
45
59
|
end
|
46
60
|
|
47
|
-
|
61
|
+
preload_scope
|
48
62
|
end
|
49
63
|
|
50
64
|
module Association
|
@@ -59,10 +73,26 @@ module ChronoModel
|
|
59
73
|
scope = scope.as_of(preload_scope.as_of_time)
|
60
74
|
end
|
61
75
|
|
62
|
-
|
76
|
+
scope
|
63
77
|
end
|
64
78
|
end
|
65
|
-
end
|
66
79
|
|
80
|
+
module ThroughAssociation
|
81
|
+
# Builds the preloader scope taking into account a potential
|
82
|
+
# +as_of_time+ passed down the call chain starting at the
|
83
|
+
# end user invocation.
|
84
|
+
#
|
85
|
+
def through_scope
|
86
|
+
scope = super
|
87
|
+
return unless scope # Rails 5.2 may not return a scope
|
88
|
+
|
89
|
+
if preload_scope.try(:as_of_time)
|
90
|
+
scope = scope.as_of(preload_scope.as_of_time)
|
91
|
+
end
|
92
|
+
|
93
|
+
scope
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
67
97
|
end
|
68
98
|
end
|
@@ -1,13 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ChronoModel
|
2
4
|
module Patches
|
3
|
-
|
4
5
|
module Relation
|
5
6
|
include ChronoModel::Patches::AsOfTimeHolder
|
6
7
|
|
8
|
+
if ActiveRecord::Associations::Preloader.instance_methods.include?(:call)
|
9
|
+
def preload_associations(records) # :nodoc:
|
10
|
+
preload = preload_values
|
11
|
+
preload += includes_values unless eager_loading?
|
12
|
+
scope = StrictLoadingScope if strict_loading_value
|
13
|
+
|
14
|
+
preload.each do |associations|
|
15
|
+
ActiveRecord::Associations::Preloader.new(
|
16
|
+
records: records, associations: associations, scope: scope, model: model, as_of_time: as_of_time
|
17
|
+
).call
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def empty_scope?
|
23
|
+
return super unless @_as_of_time
|
24
|
+
|
25
|
+
@values == klass.unscoped.as_of(as_of_time).values
|
26
|
+
end
|
27
|
+
|
7
28
|
def load
|
8
29
|
return super unless @_as_of_time && !loaded?
|
9
30
|
|
10
|
-
super.each {|record| record.as_of_time!(@_as_of_time) }
|
31
|
+
super.each { |record| record.as_of_time!(@_as_of_time) }
|
11
32
|
end
|
12
33
|
|
13
34
|
def merge(*)
|
@@ -20,11 +41,9 @@ module ChronoModel
|
|
20
41
|
return super unless @_as_of_time
|
21
42
|
|
22
43
|
super.tap do |arel|
|
23
|
-
|
24
44
|
arel.join_sources.each do |join|
|
25
45
|
chrono_join_history(join)
|
26
46
|
end
|
27
|
-
|
28
47
|
end
|
29
48
|
end
|
30
49
|
|
@@ -37,11 +56,18 @@ module ChronoModel
|
|
37
56
|
#
|
38
57
|
return if join.left.respond_to?(:as_of_time)
|
39
58
|
|
40
|
-
model =
|
59
|
+
model =
|
60
|
+
if join.left.respond_to?(:table_name)
|
61
|
+
ChronoModel.history_models[join.left.table_name]
|
62
|
+
else
|
63
|
+
ChronoModel.history_models[join.left]
|
64
|
+
end
|
65
|
+
|
41
66
|
return unless model
|
42
67
|
|
43
68
|
join.left = ChronoModel::Patches::JoinNode.new(
|
44
|
-
join.left, model.history, @_as_of_time
|
69
|
+
join.left, model.history, @_as_of_time
|
70
|
+
)
|
45
71
|
end
|
46
72
|
|
47
73
|
# Build a preloader at the +as_of_time+ of this relation.
|
@@ -49,10 +75,29 @@ module ChronoModel
|
|
49
75
|
#
|
50
76
|
def build_preloader
|
51
77
|
ActiveRecord::Associations::Preloader.new(
|
52
|
-
model:
|
78
|
+
model: model, as_of_time: as_of_time
|
53
79
|
)
|
54
80
|
end
|
55
|
-
end
|
56
81
|
|
82
|
+
def find_nth(*)
|
83
|
+
return super unless try(:history?)
|
84
|
+
|
85
|
+
with_hid_pkey { super }
|
86
|
+
end
|
87
|
+
|
88
|
+
def last(*)
|
89
|
+
return super unless try(:history?)
|
90
|
+
|
91
|
+
with_hid_pkey { super }
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def ordered_relation
|
97
|
+
return super unless try(:history?)
|
98
|
+
|
99
|
+
with_hid_pkey { super }
|
100
|
+
end
|
101
|
+
end
|
57
102
|
end
|
58
103
|
end
|
data/lib/chrono_model/patches.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'chrono_model/patches/as_of_time_holder'
|
2
4
|
require 'chrono_model/patches/as_of_time_relation'
|
3
5
|
|
@@ -5,4 +7,4 @@ require 'chrono_model/patches/join_node'
|
|
5
7
|
require 'chrono_model/patches/relation'
|
6
8
|
require 'chrono_model/patches/preloader'
|
7
9
|
require 'chrono_model/patches/association'
|
8
|
-
require 'chrono_model/patches/
|
10
|
+
require 'chrono_model/patches/batches'
|