activerecord 6.1.3.2 → 7.0.0.alpha2
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 +734 -1058
- 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 +35 -7
- data/lib/active_record/associations/association_scope.rb +1 -3
- data/lib/active_record/associations/belongs_to_association.rb +16 -6
- 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 +24 -25
- 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 -49
- 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 +11 -1
- 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 +14 -7
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -18
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -9
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +60 -16
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -69
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +96 -81
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +6 -2
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +33 -21
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -0
- 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/pool_manager.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
- 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 -6
- 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 +157 -100
- data/lib/active_record/connection_adapters/schema_cache.rb +35 -4
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +0 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +23 -17
- 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 +8 -5
- data/lib/active_record/connection_handling.rb +20 -38
- data/lib/active_record/core.rb +129 -117
- 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 +18 -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 +44 -46
- 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 +39 -6
- data/lib/active_record/integration.rb +1 -1
- data/lib/active_record/internal_metadata.rb +3 -5
- data/lib/active_record/legacy_yaml_adapter.rb +1 -1
- 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 +83 -1
- 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 +46 -32
- 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 +83 -58
- 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 +42 -25
- data/lib/active_record/relation/delegation.rb +6 -6
- data/lib/active_record/relation/finder_methods.rb +32 -23
- 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 +233 -50
- 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 +22 -15
- data/lib/active_record/relation.rb +170 -87
- 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 +62 -15
- 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/statement_cache.rb +2 -2
- data/lib/active_record/tasks/database_tasks.rb +107 -23
- 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 +45 -4
- 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/validations/numericality.rb +1 -1
- data/lib/active_record.rb +170 -2
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/composite.rb +3 -3
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- 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/homogeneous_in.rb +4 -0
- 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 +3 -3
- 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 +44 -3
- 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 +55 -16
@@ -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
|
@@ -41,15 +41,18 @@ module ActiveRecord
|
|
41
41
|
#
|
42
42
|
# This could result in many rows that contain redundant data and it performs poorly at scale
|
43
43
|
# and is therefore only used when necessary.
|
44
|
-
#
|
45
|
-
class Preloader #:nodoc:
|
44
|
+
class Preloader # :nodoc:
|
46
45
|
extend ActiveSupport::Autoload
|
47
46
|
|
48
47
|
eager_autoload do
|
49
48
|
autoload :Association, "active_record/associations/preloader/association"
|
49
|
+
autoload :Batch, "active_record/associations/preloader/batch"
|
50
|
+
autoload :Branch, "active_record/associations/preloader/branch"
|
50
51
|
autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
|
51
52
|
end
|
52
53
|
|
54
|
+
attr_reader :records, :associations, :scope, :associate_by_default
|
55
|
+
|
53
56
|
# Eager loads the named associations for the given Active Record record(s).
|
54
57
|
#
|
55
58
|
# In this description, 'association name' shall refer to the name passed
|
@@ -77,130 +80,63 @@ module ActiveRecord
|
|
77
80
|
# example, specifying <tt>{ author: :avatar }</tt> will preload a
|
78
81
|
# book's author, as well as that author's avatar.
|
79
82
|
#
|
80
|
-
# +:associations+ has the same format as the +:include+
|
81
|
-
# <tt>ActiveRecord::
|
83
|
+
# +:associations+ has the same format as the +:include+ method in
|
84
|
+
# <tt>ActiveRecord::QueryMethods</tt>. So +associations+ could look like this:
|
82
85
|
#
|
83
86
|
# :books
|
84
87
|
# [ :books, :author ]
|
85
88
|
# { author: :avatar }
|
86
89
|
# [ :books, { author: :avatar } ]
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
90
|
+
#
|
91
|
+
# +available_records+ is an array of ActiveRecord::Base. The Preloader
|
92
|
+
# will try to use the objects in this array to preload the requested
|
93
|
+
# associations before querying the database. This can save database
|
94
|
+
# queries by reusing in-memory objects. The optimization is only applied
|
95
|
+
# to single associations (i.e. :belongs_to, :has_one) with no scopes.
|
96
|
+
def initialize(associate_by_default: true, **kwargs)
|
97
|
+
if kwargs.empty?
|
98
|
+
ActiveSupport::Deprecation.warn("Calling `Preloader#initialize` without arguments is deprecated and will be removed in Rails 7.0.")
|
92
99
|
else
|
93
|
-
|
94
|
-
|
95
|
-
|
100
|
+
@records = kwargs[:records]
|
101
|
+
@associations = kwargs[:associations]
|
102
|
+
@scope = kwargs[:scope]
|
103
|
+
@available_records = kwargs[:available_records] || []
|
104
|
+
@associate_by_default = associate_by_default
|
105
|
+
|
106
|
+
@tree = Branch.new(
|
107
|
+
parent: nil,
|
108
|
+
association: nil,
|
109
|
+
children: associations,
|
110
|
+
associate_by_default: @associate_by_default,
|
111
|
+
scope: @scope
|
112
|
+
)
|
113
|
+
@tree.preloaded_records = records
|
96
114
|
end
|
97
115
|
end
|
98
116
|
|
99
|
-
def
|
100
|
-
|
117
|
+
def empty?
|
118
|
+
associations.nil? || records.length == 0
|
101
119
|
end
|
102
120
|
|
103
|
-
|
104
|
-
|
105
|
-
def preloaders_on(association, records, scope, polymorphic_parent = false)
|
106
|
-
case association
|
107
|
-
when Hash
|
108
|
-
preloaders_for_hash(association, records, scope, polymorphic_parent)
|
109
|
-
when Symbol, String
|
110
|
-
preloaders_for_one(association, records, scope, polymorphic_parent)
|
111
|
-
else
|
112
|
-
raise ArgumentError, "#{association.inspect} was not recognized for preload"
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def preloaders_for_hash(association, records, scope, polymorphic_parent)
|
117
|
-
association.flat_map { |parent, child|
|
118
|
-
grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
|
119
|
-
loaders = preloaders_for_reflection(reflection, reflection_records, scope)
|
120
|
-
recs = loaders.flat_map(&:preloaded_records).uniq
|
121
|
-
child_polymorphic_parent = reflection && reflection.options[:polymorphic]
|
122
|
-
loaders.concat Array.wrap(child).flat_map { |assoc|
|
123
|
-
preloaders_on assoc, recs, scope, child_polymorphic_parent
|
124
|
-
}
|
125
|
-
loaders
|
126
|
-
end
|
127
|
-
}
|
128
|
-
end
|
129
|
-
|
130
|
-
# Loads all the given data into +records+ for a singular +association+.
|
131
|
-
#
|
132
|
-
# Functions by instantiating a preloader class such as Preloader::Association and
|
133
|
-
# call the +run+ method for each passed in class in the +records+ argument.
|
134
|
-
#
|
135
|
-
# Not all records have the same class, so group then preload group on the reflection
|
136
|
-
# itself so that if various subclass share the same association then we do not split
|
137
|
-
# them unnecessarily
|
138
|
-
#
|
139
|
-
# Additionally, polymorphic belongs_to associations can have multiple associated
|
140
|
-
# classes, depending on the polymorphic_type field. So we group by the classes as
|
141
|
-
# well.
|
142
|
-
def preloaders_for_one(association, records, scope, polymorphic_parent)
|
143
|
-
grouped_records(association, records, polymorphic_parent)
|
144
|
-
.flat_map do |reflection, reflection_records|
|
145
|
-
preloaders_for_reflection reflection, reflection_records, scope
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def preloaders_for_reflection(reflection, records, scope)
|
150
|
-
records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
|
151
|
-
preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run
|
152
|
-
end
|
153
|
-
end
|
121
|
+
def call
|
122
|
+
Batch.new([self], available_records: @available_records).call
|
154
123
|
|
155
|
-
|
156
|
-
|
157
|
-
records.each do |record|
|
158
|
-
reflection = record.class._reflect_on_association(association)
|
159
|
-
next if polymorphic_parent && !reflection || !record.association(association).klass
|
160
|
-
(h[reflection] ||= []) << record
|
161
|
-
end
|
162
|
-
h
|
163
|
-
end
|
164
|
-
|
165
|
-
class AlreadyLoaded # :nodoc:
|
166
|
-
def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
|
167
|
-
@owners = owners
|
168
|
-
@reflection = reflection
|
169
|
-
end
|
170
|
-
|
171
|
-
def run
|
172
|
-
self
|
173
|
-
end
|
174
|
-
|
175
|
-
def preloaded_records
|
176
|
-
@preloaded_records ||= records_by_owner.flat_map(&:last)
|
177
|
-
end
|
124
|
+
loaders
|
125
|
+
end
|
178
126
|
|
179
|
-
|
180
|
-
|
181
|
-
Array(owner.association(reflection.name).target)
|
182
|
-
end
|
183
|
-
end
|
127
|
+
def preload(records, associations, preload_scope = nil)
|
128
|
+
ActiveSupport::Deprecation.warn("`preload` is deprecated and will be removed in Rails 7.0. Call `Preloader.new(kwargs).call` instead.")
|
184
129
|
|
185
|
-
|
186
|
-
|
187
|
-
end
|
130
|
+
Preloader.new(records: records, associations: associations, scope: preload_scope).call
|
131
|
+
end
|
188
132
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
def preloader_for(reflection, owners)
|
193
|
-
if owners.all? { |o| o.association(reflection.name).loaded? }
|
194
|
-
return AlreadyLoaded
|
195
|
-
end
|
196
|
-
reflection.check_preloadable!
|
133
|
+
def branches
|
134
|
+
@tree.children
|
135
|
+
end
|
197
136
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
Association
|
202
|
-
end
|
203
|
-
end
|
137
|
+
def loaders
|
138
|
+
branches.flat_map(&:loaders)
|
139
|
+
end
|
204
140
|
end
|
205
141
|
end
|
206
142
|
end
|