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
@@ -27,7 +27,7 @@ module ActiveRecord
|
|
27
27
|
# is computed directly through SQL and does not trigger by itself the
|
28
28
|
# instantiation of the actual post records.
|
29
29
|
class CollectionProxy < Relation
|
30
|
-
def initialize(klass, association, **)
|
30
|
+
def initialize(klass, association, **) # :nodoc:
|
31
31
|
@association = association
|
32
32
|
super klass
|
33
33
|
|
@@ -46,7 +46,7 @@ module ActiveRecord
|
|
46
46
|
# Returns +true+ if the association has been loaded, otherwise +false+.
|
47
47
|
#
|
48
48
|
# person.pets.loaded? # => false
|
49
|
-
# person.pets
|
49
|
+
# person.pets.records
|
50
50
|
# person.pets.loaded? # => true
|
51
51
|
def loaded?
|
52
52
|
@association.loaded?
|
@@ -813,7 +813,7 @@ module ActiveRecord
|
|
813
813
|
# to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
|
814
814
|
# it is equivalent to <tt>!collection.exists?</tt>. If the collection has
|
815
815
|
# not already been loaded and you are going to fetch the records anyway it
|
816
|
-
# is better to check <tt>collection.
|
816
|
+
# is better to check <tt>collection.load.empty?</tt>.
|
817
817
|
#
|
818
818
|
# class Person < ActiveRecord::Base
|
819
819
|
# has_many :pets
|
@@ -849,6 +849,11 @@ module ActiveRecord
|
|
849
849
|
# person.pets.count # => 1
|
850
850
|
# person.pets.any? # => true
|
851
851
|
#
|
852
|
+
# Calling it without a block when the collection is not yet
|
853
|
+
# loaded is equivalent to <tt>collection.exists?</tt>.
|
854
|
+
# If you're going to load the collection anyway, it is better
|
855
|
+
# to call <tt>collection.load.any?</tt> to avoid an extra query.
|
856
|
+
#
|
852
857
|
# You can also pass a +block+ to define criteria. The behavior
|
853
858
|
# is the same, it returns true if the collection based on the
|
854
859
|
# criteria is not empty.
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
class DisableJoinsAssociationScope < AssociationScope # :nodoc:
|
6
|
+
def scope(association)
|
7
|
+
source_reflection = association.reflection
|
8
|
+
owner = association.owner
|
9
|
+
unscoped = association.klass.unscoped
|
10
|
+
reverse_chain = get_chain(source_reflection, association, unscoped.alias_tracker).reverse
|
11
|
+
|
12
|
+
last_reflection, last_ordered, last_join_ids = last_scope_chain(reverse_chain, owner)
|
13
|
+
|
14
|
+
add_constraints(last_reflection, last_reflection.join_primary_key, last_join_ids, owner, last_ordered)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def last_scope_chain(reverse_chain, owner)
|
19
|
+
first_item = reverse_chain.shift
|
20
|
+
first_scope = [first_item, false, [owner._read_attribute(first_item.join_foreign_key)]]
|
21
|
+
|
22
|
+
reverse_chain.inject(first_scope) do |(reflection, ordered, join_ids), next_reflection|
|
23
|
+
key = reflection.join_primary_key
|
24
|
+
records = add_constraints(reflection, key, join_ids, owner, ordered)
|
25
|
+
foreign_key = next_reflection.join_foreign_key
|
26
|
+
record_ids = records.pluck(foreign_key)
|
27
|
+
records_ordered = records && records.order_values.any?
|
28
|
+
|
29
|
+
[next_reflection, records_ordered, record_ids]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_constraints(reflection, key, join_ids, owner, ordered)
|
34
|
+
scope = reflection.build_scope(reflection.aliased_table).where(key => join_ids)
|
35
|
+
|
36
|
+
relation = reflection.klass.scope_for_association
|
37
|
+
scope.merge!(
|
38
|
+
relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
|
39
|
+
)
|
40
|
+
|
41
|
+
scope = reflection.constraints.inject(scope) do |memo, scope_chain_item|
|
42
|
+
item = eval_scope(reflection, scope_chain_item, owner)
|
43
|
+
scope.unscope!(*item.unscope_values)
|
44
|
+
scope.where_clause += item.where_clause
|
45
|
+
scope.order_values = item.order_values | scope.order_values
|
46
|
+
scope
|
47
|
+
end
|
48
|
+
|
49
|
+
if scope.order_values.empty? && ordered
|
50
|
+
split_scope = DisableJoinsAssociationRelation.create(scope.klass, key, join_ids)
|
51
|
+
split_scope.where_clause += scope.where_clause
|
52
|
+
split_scope
|
53
|
+
else
|
54
|
+
scope
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -7,7 +7,7 @@ module ActiveRecord
|
|
7
7
|
#
|
8
8
|
# If the association has a <tt>:through</tt> option further specialization
|
9
9
|
# is provided by its child HasManyThroughAssociation.
|
10
|
-
class HasManyAssociation < CollectionAssociation
|
10
|
+
class HasManyAssociation < CollectionAssociation # :nodoc:
|
11
11
|
include ForeignAssociation
|
12
12
|
|
13
13
|
def handle_dependency
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
5
|
# = Active Record Has Many Through Association
|
6
|
-
class HasManyThroughAssociation < HasManyAssociation
|
6
|
+
class HasManyThroughAssociation < HasManyAssociation # :nodoc:
|
7
7
|
include ThroughAssociation
|
8
8
|
|
9
9
|
def initialize(owner, reflection)
|
@@ -214,6 +214,7 @@ module ActiveRecord
|
|
214
214
|
|
215
215
|
def find_target
|
216
216
|
return [] unless target_reflection_has_associated_record?
|
217
|
+
return scope.to_a if disable_joins
|
217
218
|
super
|
218
219
|
end
|
219
220
|
|
@@ -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
|
40
117
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
|
118
|
+
# The name of the key on the associated records
|
119
|
+
def association_key_name
|
120
|
+
reflection.join_primary_key(klass)
|
121
|
+
end
|
46
122
|
|
47
|
-
|
48
|
-
|
123
|
+
def loader_query
|
124
|
+
LoaderQuery.new(scope, association_key_name)
|
125
|
+
end
|
49
126
|
|
50
|
-
|
51
|
-
|
127
|
+
def owner_keys
|
128
|
+
@owner_keys ||= owners_by_key.keys
|
129
|
+
end
|
52
130
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
131
|
+
def scope
|
132
|
+
@scope ||= build_scope
|
133
|
+
end
|
134
|
+
|
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])
|
149
|
+
|
150
|
+
@preloaded_records = raw_records.select do |record|
|
151
|
+
assignments = false
|
152
|
+
|
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
|
184
|
+
|
185
|
+
private
|
186
|
+
attr_reader :owners, :reflection, :preload_scope, :model
|
62
187
|
|
63
|
-
|
64
|
-
|
65
|
-
|
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,24 +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 ||=
|
133
|
-
reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
|
134
|
-
end
|
246
|
+
@reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
|
135
247
|
end
|
136
248
|
|
137
249
|
def build_scope
|
@@ -147,11 +259,11 @@ module ActiveRecord
|
|
147
259
|
scope.merge!(preload_scope)
|
148
260
|
end
|
149
261
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
262
|
+
cascade_strict_loading(scope)
|
263
|
+
end
|
264
|
+
|
265
|
+
def cascade_strict_loading(scope)
|
266
|
+
preload_scope&.strict_loading_value ? scope.strict_loading : scope
|
155
267
|
end
|
156
268
|
end
|
157
269
|
end
|