activerecord 6.1.7.10 → 7.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +726 -1404
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/association_relation.rb +0 -10
- data/lib/active_record/associations/association.rb +31 -9
- data/lib/active_record/associations/association_scope.rb +1 -3
- data/lib/active_record/associations/belongs_to_association.rb +15 -4
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +8 -2
- data/lib/active_record/associations/builder/belongs_to.rb +19 -6
- data/lib/active_record/associations/builder/collection_association.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +3 -2
- data/lib/active_record/associations/builder/has_one.rb +2 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -2
- data/lib/active_record/associations/collection_association.rb +14 -23
- data/lib/active_record/associations/collection_proxy.rb +8 -3
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +2 -1
- data/lib/active_record/associations/has_one_association.rb +10 -7
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/preloader/association.rb +161 -47
- data/lib/active_record/associations/preloader/batch.rb +51 -0
- data/lib/active_record/associations/preloader/branch.rb +147 -0
- data/lib/active_record/associations/preloader/through_association.rb +37 -11
- data/lib/active_record/associations/preloader.rb +46 -110
- data/lib/active_record/associations/singular_association.rb +8 -2
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +76 -81
- data/lib/active_record/asynchronous_queries_tracker.rb +57 -0
- data/lib/active_record/attribute_assignment.rb +1 -1
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +41 -16
- data/lib/active_record/attribute_methods/primary_key.rb +2 -2
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +7 -5
- data/lib/active_record/attribute_methods/serialization.rb +66 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
- data/lib/active_record/attribute_methods/write.rb +7 -10
- data/lib/active_record/attribute_methods.rb +6 -9
- data/lib/active_record/attributes.rb +24 -35
- data/lib/active_record/autosave_association.rb +3 -18
- data/lib/active_record/base.rb +19 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/yaml_column.rb +2 -14
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +312 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -558
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +45 -21
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
- data/lib/active_record/connection_adapters/abstract/quoting.rb +12 -14
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -13
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +60 -16
- data/lib/active_record/connection_adapters/abstract/transaction.rb +3 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +112 -66
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +96 -81
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +33 -23
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
- data/lib/active_record/connection_adapters/pool_config.rb +1 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -14
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +6 -32
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +159 -102
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -37
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +23 -19
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
- data/lib/active_record/connection_adapters.rb +6 -5
- data/lib/active_record/connection_handling.rb +20 -38
- data/lib/active_record/core.rb +111 -125
- data/lib/active_record/database_configurations/connection_url_resolver.rb +0 -1
- data/lib/active_record/database_configurations/database_config.rb +12 -0
- data/lib/active_record/database_configurations/hash_config.rb +27 -1
- data/lib/active_record/database_configurations/url_config.rb +2 -2
- data/lib/active_record/database_configurations.rb +17 -9
- data/lib/active_record/delegated_type.rb +33 -11
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +44 -0
- data/lib/active_record/encryption/configurable.rb +61 -0
- data/lib/active_record/encryption/context.rb +35 -0
- data/lib/active_record/encryption/contexts.rb +72 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +208 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +155 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +29 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +42 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_serializer.rb +80 -0
- data/lib/active_record/encryption/null_encryptor.rb +21 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
- data/lib/active_record/encryption/scheme.rb +99 -0
- data/lib/active_record/encryption.rb +55 -0
- data/lib/active_record/enum.rb +41 -41
- data/lib/active_record/errors.rb +66 -3
- data/lib/active_record/fixture_set/file.rb +15 -1
- data/lib/active_record/fixture_set/table_row.rb +40 -5
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +16 -11
- data/lib/active_record/future_result.rb +139 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +55 -17
- data/lib/active_record/insert_all.rb +34 -5
- data/lib/active_record/integration.rb +1 -1
- data/lib/active_record/internal_metadata.rb +1 -5
- data/lib/active_record/locking/optimistic.rb +10 -9
- data/lib/active_record/log_subscriber.rb +6 -2
- data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
- data/lib/active_record/middleware/database_selector.rb +8 -3
- data/lib/active_record/migration/command_recorder.rb +4 -4
- data/lib/active_record/migration/compatibility.rb +89 -10
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration.rb +109 -79
- data/lib/active_record/model_schema.rb +45 -31
- data/lib/active_record/nested_attributes.rb +3 -3
- data/lib/active_record/no_touching.rb +2 -2
- data/lib/active_record/null_relation.rb +2 -6
- data/lib/active_record/persistence.rb +134 -45
- data/lib/active_record/query_cache.rb +2 -2
- data/lib/active_record/query_logs.rb +203 -0
- data/lib/active_record/querying.rb +15 -5
- data/lib/active_record/railtie.rb +117 -17
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +72 -48
- data/lib/active_record/readonly_attributes.rb +11 -0
- data/lib/active_record/reflection.rb +45 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +39 -26
- data/lib/active_record/relation/delegation.rb +6 -6
- data/lib/active_record/relation/finder_methods.rb +31 -22
- data/lib/active_record/relation/merger.rb +20 -13
- data/lib/active_record/relation/predicate_builder.rb +1 -6
- data/lib/active_record/relation/query_attribute.rb +5 -11
- data/lib/active_record/relation/query_methods.rb +230 -49
- data/lib/active_record/relation/record_fetch_warning.rb +2 -2
- data/lib/active_record/relation/spawn_methods.rb +2 -2
- data/lib/active_record/relation/where_clause.rb +8 -4
- data/lib/active_record/relation.rb +166 -77
- data/lib/active_record/result.rb +17 -2
- data/lib/active_record/runtime_registry.rb +2 -4
- data/lib/active_record/sanitization.rb +11 -7
- data/lib/active_record/schema_dumper.rb +3 -3
- data/lib/active_record/schema_migration.rb +0 -4
- data/lib/active_record/scoping/default.rb +61 -12
- data/lib/active_record/scoping/named.rb +3 -11
- data/lib/active_record/scoping.rb +40 -22
- data/lib/active_record/serialization.rb +1 -1
- data/lib/active_record/signed_id.rb +1 -1
- data/lib/active_record/store.rb +1 -6
- data/lib/active_record/tasks/database_tasks.rb +106 -22
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -11
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +9 -13
- data/lib/active_record/timestamp.rb +3 -4
- data/lib/active_record/transactions.rb +9 -14
- data/lib/active_record/translation.rb +2 -2
- data/lib/active_record/type/adapter_specific_registry.rb +32 -7
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +2 -2
- data/lib/active_record/type/serialized.rb +1 -1
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +170 -2
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/crud.rb +18 -22
- data/lib/arel/delete_manager.rb +2 -4
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/casted.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +8 -13
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/update_statement.rb +3 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +10 -4
- data/lib/arel/table.rb +0 -1
- data/lib/arel/tree_manager.rb +0 -12
- data/lib/arel/update_manager.rb +2 -4
- data/lib/arel/visitors/dot.rb +80 -90
- data/lib/arel/visitors/mysql.rb +6 -1
- data/lib/arel/visitors/postgresql.rb +0 -10
- data/lib/arel/visitors/to_sql.rb +43 -2
- data/lib/arel.rb +1 -1
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- metadata +52 -14
@@ -3,7 +3,7 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
5
|
# = Active Record Has One Association
|
6
|
-
class HasOneAssociation < SingularAssociation
|
6
|
+
class HasOneAssociation < SingularAssociation # :nodoc:
|
7
7
|
include ForeignAssociation
|
8
8
|
|
9
9
|
def handle_dependency
|
@@ -70,7 +70,7 @@ module ActiveRecord
|
|
70
70
|
if save && !record.save
|
71
71
|
nullify_owner_attributes(record)
|
72
72
|
set_owner_attributes(target) if target
|
73
|
-
raise RecordNotSaved
|
73
|
+
raise RecordNotSaved.new("Failed to save the new associated #{reflection.name}.", record)
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -102,8 +102,11 @@ module ActiveRecord
|
|
102
102
|
|
103
103
|
if target.persisted? && owner.persisted? && !target.save
|
104
104
|
set_owner_attributes(target)
|
105
|
-
raise RecordNotSaved
|
106
|
-
|
105
|
+
raise RecordNotSaved.new(
|
106
|
+
"Failed to remove the existing associated #{reflection.name}. " \
|
107
|
+
"The record failed to save after its foreign key was set to nil.",
|
108
|
+
target
|
109
|
+
)
|
107
110
|
end
|
108
111
|
end
|
109
112
|
end
|
@@ -112,9 +115,9 @@ module ActiveRecord
|
|
112
115
|
record[reflection.foreign_key] = nil
|
113
116
|
end
|
114
117
|
|
115
|
-
def transaction_if(value)
|
118
|
+
def transaction_if(value, &block)
|
116
119
|
if value
|
117
|
-
reflection.klass.transaction
|
120
|
+
reflection.klass.transaction(&block)
|
118
121
|
else
|
119
122
|
yield
|
120
123
|
end
|
@@ -122,7 +125,7 @@ module ActiveRecord
|
|
122
125
|
|
123
126
|
def _create_record(attributes, raise_error = false, &block)
|
124
127
|
unless owner.persisted?
|
125
|
-
raise ActiveRecord::RecordNotSaved
|
128
|
+
raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner)
|
126
129
|
end
|
127
130
|
|
128
131
|
super
|
@@ -3,17 +3,89 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
5
|
class Preloader
|
6
|
-
class Association
|
7
|
-
|
6
|
+
class Association # :nodoc:
|
7
|
+
class LoaderQuery
|
8
|
+
attr_reader :scope, :association_key_name
|
9
|
+
|
10
|
+
def initialize(scope, association_key_name)
|
11
|
+
@scope = scope
|
12
|
+
@association_key_name = association_key_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def eql?(other)
|
16
|
+
association_key_name == other.association_key_name &&
|
17
|
+
scope.table_name == other.scope.table_name &&
|
18
|
+
scope.values_for_queries == other.scope.values_for_queries
|
19
|
+
end
|
20
|
+
|
21
|
+
def hash
|
22
|
+
[association_key_name, scope.table_name, scope.values_for_queries].hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def records_for(loaders)
|
26
|
+
ids = loaders.flat_map(&:owner_keys).uniq
|
27
|
+
|
28
|
+
scope.where(association_key_name => ids).load do |record|
|
29
|
+
loaders.each { |l| l.set_inverse(record) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def load_records_in_batch(loaders)
|
34
|
+
raw_records = records_for(loaders)
|
35
|
+
|
36
|
+
loaders.each do |loader|
|
37
|
+
loader.load_records(raw_records)
|
38
|
+
loader.run
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :klass
|
44
|
+
|
45
|
+
def initialize(klass, owners, reflection, preload_scope, reflection_scope, associate_by_default)
|
8
46
|
@klass = klass
|
9
47
|
@owners = owners.uniq(&:__id__)
|
10
48
|
@reflection = reflection
|
11
49
|
@preload_scope = preload_scope
|
50
|
+
@reflection_scope = reflection_scope
|
12
51
|
@associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
|
13
52
|
@model = owners.first && owners.first.class
|
53
|
+
@run = false
|
54
|
+
end
|
55
|
+
|
56
|
+
def table_name
|
57
|
+
@klass.table_name
|
58
|
+
end
|
59
|
+
|
60
|
+
def data_available?
|
61
|
+
already_loaded?
|
62
|
+
end
|
63
|
+
|
64
|
+
def future_classes
|
65
|
+
if run? || already_loaded?
|
66
|
+
[]
|
67
|
+
else
|
68
|
+
[@klass]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def runnable_loaders
|
73
|
+
[self]
|
74
|
+
end
|
75
|
+
|
76
|
+
def run?
|
77
|
+
@run
|
14
78
|
end
|
15
79
|
|
16
80
|
def run
|
81
|
+
return self if run?
|
82
|
+
@run = true
|
83
|
+
|
84
|
+
if already_loaded?
|
85
|
+
fetch_from_preloaded_records
|
86
|
+
return self
|
87
|
+
end
|
88
|
+
|
17
89
|
records = records_by_owner
|
18
90
|
|
19
91
|
owners.each do |owner|
|
@@ -24,45 +96,105 @@ module ActiveRecord
|
|
24
96
|
end
|
25
97
|
|
26
98
|
def records_by_owner
|
27
|
-
|
99
|
+
ensure_loaded unless defined?(@records_by_owner)
|
28
100
|
|
29
101
|
@records_by_owner
|
30
102
|
end
|
31
103
|
|
32
104
|
def preloaded_records
|
33
|
-
|
105
|
+
ensure_loaded unless defined?(@preloaded_records)
|
34
106
|
|
35
107
|
@preloaded_records
|
36
108
|
end
|
37
109
|
|
38
|
-
|
39
|
-
|
110
|
+
def ensure_loaded
|
111
|
+
if already_loaded?
|
112
|
+
fetch_from_preloaded_records
|
113
|
+
else
|
114
|
+
load_records
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# The name of the key on the associated records
|
119
|
+
def association_key_name
|
120
|
+
reflection.join_primary_key(klass)
|
121
|
+
end
|
40
122
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
123
|
+
def loader_query
|
124
|
+
LoaderQuery.new(scope, association_key_name)
|
125
|
+
end
|
126
|
+
|
127
|
+
def owner_keys
|
128
|
+
@owner_keys ||= owners_by_key.keys
|
129
|
+
end
|
130
|
+
|
131
|
+
def scope
|
132
|
+
@scope ||= build_scope
|
133
|
+
end
|
46
134
|
|
47
|
-
|
48
|
-
|
135
|
+
def set_inverse(record)
|
136
|
+
if owners = owners_by_key[convert_key(record[association_key_name])]
|
137
|
+
# Processing only the first owner
|
138
|
+
# because the record is modified but not an owner
|
139
|
+
association = owners.first.association(reflection.name)
|
140
|
+
association.set_inverse_instance(record)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def load_records(raw_records = nil)
|
145
|
+
# owners can be duplicated when a relation has a collection association join
|
146
|
+
# #compare_by_identity makes such owners different hash keys
|
147
|
+
@records_by_owner = {}.compare_by_identity
|
148
|
+
raw_records ||= loader_query.records_for([self])
|
49
149
|
|
50
|
-
|
51
|
-
|
150
|
+
@preloaded_records = raw_records.select do |record|
|
151
|
+
assignments = false
|
52
152
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
153
|
+
owners_by_key[convert_key(record[association_key_name])]&.each do |owner|
|
154
|
+
entries = (@records_by_owner[owner] ||= [])
|
155
|
+
|
156
|
+
if reflection.collection? || entries.empty?
|
157
|
+
entries << record
|
158
|
+
assignments = true
|
57
159
|
end
|
160
|
+
end
|
58
161
|
|
59
|
-
|
162
|
+
assignments
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def associate_records_from_unscoped(unscoped_records)
|
167
|
+
return if unscoped_records.nil? || unscoped_records.empty?
|
168
|
+
return if !reflection_scope.empty_scope?
|
169
|
+
return if preload_scope && !preload_scope.empty_scope?
|
170
|
+
return if reflection.collection?
|
171
|
+
|
172
|
+
unscoped_records.each do |record|
|
173
|
+
owners = owners_by_key[convert_key(record[association_key_name])]
|
174
|
+
owners&.each_with_index do |owner, i|
|
175
|
+
association = owner.association(reflection.name)
|
176
|
+
association.target = record
|
177
|
+
|
178
|
+
if i == 0 # Set inverse on first owner
|
179
|
+
association.set_inverse_instance(record)
|
180
|
+
end
|
60
181
|
end
|
61
182
|
end
|
183
|
+
end
|
62
184
|
|
63
|
-
|
64
|
-
|
65
|
-
|
185
|
+
private
|
186
|
+
attr_reader :owners, :reflection, :preload_scope, :model
|
187
|
+
|
188
|
+
def already_loaded?
|
189
|
+
@already_loaded ||= owners.all? { |o| o.association(reflection.name).loaded? }
|
190
|
+
end
|
191
|
+
|
192
|
+
def fetch_from_preloaded_records
|
193
|
+
@records_by_owner = owners.index_with do |owner|
|
194
|
+
Array(owner.association(reflection.name).target)
|
195
|
+
end
|
196
|
+
|
197
|
+
@preloaded_records = records_by_owner.flat_map(&:last)
|
66
198
|
end
|
67
199
|
|
68
200
|
# The name of the key on the model which declares the association
|
@@ -79,10 +211,6 @@ module ActiveRecord
|
|
79
211
|
end
|
80
212
|
end
|
81
213
|
|
82
|
-
def owner_keys
|
83
|
-
@owner_keys ||= owners_by_key.keys
|
84
|
-
end
|
85
|
-
|
86
214
|
def owners_by_key
|
87
215
|
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
88
216
|
key = convert_key(owner[owner_key_name])
|
@@ -114,22 +242,8 @@ module ActiveRecord
|
|
114
242
|
@model.type_for_attribute(owner_key_name).type
|
115
243
|
end
|
116
244
|
|
117
|
-
def records_for(ids)
|
118
|
-
scope.where(association_key_name => ids).load do |record|
|
119
|
-
# Processing only the first owner
|
120
|
-
# because the record is modified but not an owner
|
121
|
-
owner = owners_by_key[convert_key(record[association_key_name])].first
|
122
|
-
association = owner.association(reflection.name)
|
123
|
-
association.set_inverse_instance(record)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def scope
|
128
|
-
@scope ||= build_scope
|
129
|
-
end
|
130
|
-
|
131
245
|
def reflection_scope
|
132
|
-
@reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(
|
246
|
+
@reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
|
133
247
|
end
|
134
248
|
|
135
249
|
def build_scope
|
@@ -145,11 +259,11 @@ module ActiveRecord
|
|
145
259
|
scope.merge!(preload_scope)
|
146
260
|
end
|
147
261
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
262
|
+
cascade_strict_loading(scope)
|
263
|
+
end
|
264
|
+
|
265
|
+
def cascade_strict_loading(scope)
|
266
|
+
preload_scope&.strict_loading_value ? scope.strict_loading : scope
|
153
267
|
end
|
154
268
|
end
|
155
269
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
class Preloader
|
6
|
+
class Batch # :nodoc:
|
7
|
+
def initialize(preloaders, available_records:)
|
8
|
+
@preloaders = preloaders.reject(&:empty?)
|
9
|
+
@available_records = available_records.flatten.group_by(&:class)
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
branches = @preloaders.flat_map(&:branches)
|
14
|
+
until branches.empty?
|
15
|
+
loaders = branches.flat_map(&:runnable_loaders)
|
16
|
+
|
17
|
+
loaders.each { |loader| loader.associate_records_from_unscoped(@available_records[loader.klass]) }
|
18
|
+
|
19
|
+
already_loaded = loaders.select(&:data_available?)
|
20
|
+
if already_loaded.any?
|
21
|
+
already_loaded.each(&:run)
|
22
|
+
elsif loaders.any?
|
23
|
+
future_tables = branches.flat_map do |branch|
|
24
|
+
branch.future_classes - branch.runnable_loaders.map(&:klass)
|
25
|
+
end.map(&:table_name).uniq
|
26
|
+
|
27
|
+
target_loaders = loaders.reject { |l| future_tables.include?(l.table_name) }
|
28
|
+
target_loaders = loaders if target_loaders.empty?
|
29
|
+
|
30
|
+
group_and_load_similar(target_loaders)
|
31
|
+
target_loaders.each(&:run)
|
32
|
+
end
|
33
|
+
|
34
|
+
finished, in_progress = branches.partition(&:done?)
|
35
|
+
|
36
|
+
branches = in_progress + finished.flat_map(&:children)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
attr_reader :loaders
|
42
|
+
|
43
|
+
def group_and_load_similar(loaders)
|
44
|
+
loaders.grep_v(ThroughAssociation).group_by(&:loader_query).each_pair do |query, similar_loaders|
|
45
|
+
query.load_records_in_batch(similar_loaders)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
class Preloader
|
6
|
+
class Branch # :nodoc:
|
7
|
+
attr_reader :association, :children, :parent
|
8
|
+
attr_reader :scope, :associate_by_default
|
9
|
+
attr_writer :preloaded_records
|
10
|
+
|
11
|
+
def initialize(association:, children:, parent:, associate_by_default:, scope:)
|
12
|
+
@association = association
|
13
|
+
@parent = parent
|
14
|
+
@scope = scope
|
15
|
+
@associate_by_default = associate_by_default
|
16
|
+
|
17
|
+
@children = build_children(children)
|
18
|
+
@loaders = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def future_classes
|
22
|
+
(immediate_future_classes + children.flat_map(&:future_classes)).uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
def immediate_future_classes
|
26
|
+
if parent.done?
|
27
|
+
loaders.flat_map(&:future_classes).uniq
|
28
|
+
else
|
29
|
+
likely_reflections.reject(&:polymorphic?).flat_map do |reflection|
|
30
|
+
reflection.
|
31
|
+
chain.
|
32
|
+
map(&:klass)
|
33
|
+
end.uniq
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def target_classes
|
38
|
+
if done?
|
39
|
+
preloaded_records.map(&:klass).uniq
|
40
|
+
elsif parent.done?
|
41
|
+
loaders.map(&:klass).uniq
|
42
|
+
else
|
43
|
+
likely_reflections.reject(&:polymorphic?).map(&:klass).uniq
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def likely_reflections
|
48
|
+
parent_classes = parent.target_classes
|
49
|
+
parent_classes.filter_map do |parent_klass|
|
50
|
+
parent_klass._reflect_on_association(@association)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def root?
|
55
|
+
parent.nil?
|
56
|
+
end
|
57
|
+
|
58
|
+
def source_records
|
59
|
+
@parent.preloaded_records
|
60
|
+
end
|
61
|
+
|
62
|
+
def preloaded_records
|
63
|
+
@preloaded_records ||= loaders.flat_map(&:preloaded_records)
|
64
|
+
end
|
65
|
+
|
66
|
+
def done?
|
67
|
+
root? || (@loaders && @loaders.all?(&:run?))
|
68
|
+
end
|
69
|
+
|
70
|
+
def runnable_loaders
|
71
|
+
loaders.flat_map(&:runnable_loaders).reject(&:run?)
|
72
|
+
end
|
73
|
+
|
74
|
+
def grouped_records
|
75
|
+
h = {}
|
76
|
+
polymorphic_parent = !root? && parent.polymorphic?
|
77
|
+
source_records.each do |record|
|
78
|
+
reflection = record.class._reflect_on_association(association)
|
79
|
+
next if polymorphic_parent && !reflection || !record.association(association).klass
|
80
|
+
(h[reflection] ||= []) << record
|
81
|
+
end
|
82
|
+
h
|
83
|
+
end
|
84
|
+
|
85
|
+
def preloaders_for_reflection(reflection, reflection_records)
|
86
|
+
reflection_records.group_by do |record|
|
87
|
+
klass = record.association(association).klass
|
88
|
+
|
89
|
+
if reflection.scope && reflection.scope.arity != 0
|
90
|
+
# For instance dependent scopes, the scope is potentially
|
91
|
+
# different for each record. To allow this we'll group each
|
92
|
+
# object separately into its own preloader
|
93
|
+
reflection_scope = reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass, record).inject(&:merge!)
|
94
|
+
end
|
95
|
+
|
96
|
+
[klass, reflection_scope]
|
97
|
+
end.map do |(rhs_klass, reflection_scope), rs|
|
98
|
+
preloader_for(reflection).new(rhs_klass, rs, reflection, scope, reflection_scope, associate_by_default)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def polymorphic?
|
103
|
+
return false if root?
|
104
|
+
return @polymorphic if defined?(@polymorphic)
|
105
|
+
|
106
|
+
@polymorphic = source_records.any? do |record|
|
107
|
+
reflection = record.class._reflect_on_association(association)
|
108
|
+
reflection && reflection.options[:polymorphic]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def loaders
|
113
|
+
@loaders ||=
|
114
|
+
grouped_records.flat_map do |reflection, reflection_records|
|
115
|
+
preloaders_for_reflection(reflection, reflection_records)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
def build_children(children)
|
121
|
+
Array.wrap(children).flat_map { |association|
|
122
|
+
Array(association).flat_map { |parent, child|
|
123
|
+
Branch.new(
|
124
|
+
parent: self,
|
125
|
+
association: parent,
|
126
|
+
children: child,
|
127
|
+
associate_by_default: associate_by_default,
|
128
|
+
scope: scope
|
129
|
+
)
|
130
|
+
}
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns a class containing the logic needed to load preload the data
|
135
|
+
# and attach it to a relation. The class returned implements a `run` method
|
136
|
+
# that accepts a preloader.
|
137
|
+
def preloader_for(reflection)
|
138
|
+
if reflection.options[:through]
|
139
|
+
ThroughAssociation
|
140
|
+
else
|
141
|
+
Association
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -4,13 +4,6 @@ module ActiveRecord
|
|
4
4
|
module Associations
|
5
5
|
class Preloader
|
6
6
|
class ThroughAssociation < Association # :nodoc:
|
7
|
-
PRELOADER = ActiveRecord::Associations::Preloader.new(associate_by_default: false)
|
8
|
-
|
9
|
-
def initialize(*)
|
10
|
-
super
|
11
|
-
@already_loaded = owners.first.association(through_reflection.name).loaded?
|
12
|
-
end
|
13
|
-
|
14
7
|
def preloaded_records
|
15
8
|
@preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
|
16
9
|
end
|
@@ -23,7 +16,7 @@ module ActiveRecord
|
|
23
16
|
@records_by_owner = owners.each_with_object({}) do |owner, result|
|
24
17
|
through_records = through_records_by_owner[owner] || []
|
25
18
|
|
26
|
-
if
|
19
|
+
if owners.first.association(through_reflection.name).loaded?
|
27
20
|
if source_type = reflection.options[:source_type]
|
28
21
|
through_records = through_records.select do |record|
|
29
22
|
record[reflection.foreign_type] == source_type
|
@@ -42,9 +35,40 @@ module ActiveRecord
|
|
42
35
|
end
|
43
36
|
end
|
44
37
|
|
38
|
+
def data_available?
|
39
|
+
return true if super()
|
40
|
+
through_preloaders.all?(&:run?) &&
|
41
|
+
source_preloaders.all?(&:run?)
|
42
|
+
end
|
43
|
+
|
44
|
+
def runnable_loaders
|
45
|
+
if data_available?
|
46
|
+
[self]
|
47
|
+
elsif through_preloaders.all?(&:run?)
|
48
|
+
source_preloaders.flat_map(&:runnable_loaders)
|
49
|
+
else
|
50
|
+
through_preloaders.flat_map(&:runnable_loaders)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def future_classes
|
55
|
+
if run? || data_available?
|
56
|
+
[]
|
57
|
+
elsif through_preloaders.all?(&:run?)
|
58
|
+
source_preloaders.flat_map(&:future_classes).uniq
|
59
|
+
else
|
60
|
+
through_classes = through_preloaders.flat_map(&:future_classes)
|
61
|
+
source_classes = source_reflection.
|
62
|
+
chain.
|
63
|
+
reject { |reflection| reflection.respond_to?(:polymorphic?) && reflection.polymorphic? }.
|
64
|
+
map(&:klass)
|
65
|
+
(through_classes + source_classes).uniq
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
45
69
|
private
|
46
70
|
def source_preloaders
|
47
|
-
@source_preloaders ||=
|
71
|
+
@source_preloaders ||= ActiveRecord::Associations::Preloader.new(records: middle_records, associations: source_reflection.name, scope: scope, associate_by_default: false).loaders
|
48
72
|
end
|
49
73
|
|
50
74
|
def middle_records
|
@@ -52,7 +76,7 @@ module ActiveRecord
|
|
52
76
|
end
|
53
77
|
|
54
78
|
def through_preloaders
|
55
|
-
@through_preloaders ||=
|
79
|
+
@through_preloaders ||= ActiveRecord::Associations::Preloader.new(records: owners, associations: through_reflection.name, scope: through_scope, associate_by_default: false).loaders
|
56
80
|
end
|
57
81
|
|
58
82
|
def through_reflection
|
@@ -73,6 +97,8 @@ module ActiveRecord
|
|
73
97
|
scope = through_reflection.klass.unscoped
|
74
98
|
options = reflection.options
|
75
99
|
|
100
|
+
return scope if options[:disable_joins]
|
101
|
+
|
76
102
|
values = reflection_scope.values
|
77
103
|
if annotations = values[:annotate]
|
78
104
|
scope.annotate!(*annotations)
|
@@ -108,7 +134,7 @@ module ActiveRecord
|
|
108
134
|
end
|
109
135
|
end
|
110
136
|
|
111
|
-
scope
|
137
|
+
cascade_strict_loading(scope)
|
112
138
|
end
|
113
139
|
end
|
114
140
|
end
|