activerecord 7.0.0 → 7.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1701 -1039
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -12
- data/lib/active_record/associations/collection_proxy.rb +22 -12
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +27 -17
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +20 -14
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +362 -236
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +172 -69
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +110 -28
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +56 -10
- data/lib/active_record/base.rb +10 -5
- data/lib/active_record/callbacks.rb +16 -32
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +73 -96
- data/lib/active_record/core.rb +142 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +38 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/scheme.rb +20 -23
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +113 -29
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +121 -73
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +10 -10
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +32 -18
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +39 -17
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +108 -10
- data/lib/active_record/migration/compatibility.rb +158 -64
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +274 -117
- data/lib/active_record/model_schema.rb +86 -54
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +200 -47
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +16 -3
- data/lib/active_record/railtie.rb +128 -62
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +145 -146
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +189 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +208 -83
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +430 -77
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +98 -41
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +57 -16
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +65 -23
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +9 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +138 -107
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +50 -5
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +143 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +51 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
|
+
# = Active Record Collection Proxy
|
6
|
+
#
|
5
7
|
# Collection proxies in Active Record are middlemen between an
|
6
8
|
# <tt>association</tt>, and its <tt>target</tt> result set.
|
7
9
|
#
|
@@ -94,12 +96,12 @@ module ActiveRecord
|
|
94
96
|
# receive:
|
95
97
|
#
|
96
98
|
# person.pets.select(:name).first.person_id
|
97
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
99
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'person_id' for Pet
|
98
100
|
#
|
99
|
-
# *Second:* You can pass a block so it can be used just like Array#select
|
101
|
+
# *Second:* You can pass a block so it can be used just like <tt>Array#select</tt>.
|
100
102
|
# This builds an array of objects from the database for the scope,
|
101
103
|
# converting them into an array and iterating through them using
|
102
|
-
# Array#select
|
104
|
+
# <tt>Array#select</tt>.
|
103
105
|
#
|
104
106
|
# person.pets.select { |pet| /oo/.match?(pet.name) }
|
105
107
|
# # => [
|
@@ -108,7 +110,7 @@ module ActiveRecord
|
|
108
110
|
# # ]
|
109
111
|
|
110
112
|
# Finds an object in the collection responding to the +id+. Uses the same
|
111
|
-
# rules as ActiveRecord::
|
113
|
+
# rules as ActiveRecord::FinderMethods.find. Returns ActiveRecord::RecordNotFound
|
112
114
|
# error if the object cannot be found.
|
113
115
|
#
|
114
116
|
# class Person < ActiveRecord::Base
|
@@ -218,7 +220,7 @@ module ActiveRecord
|
|
218
220
|
# :call-seq:
|
219
221
|
# third_to_last()
|
220
222
|
#
|
221
|
-
# Same as #
|
223
|
+
# Same as #last except returns only the third-to-last record.
|
222
224
|
|
223
225
|
##
|
224
226
|
# :method: second_to_last
|
@@ -226,7 +228,7 @@ module ActiveRecord
|
|
226
228
|
# :call-seq:
|
227
229
|
# second_to_last()
|
228
230
|
#
|
229
|
-
# Same as #
|
231
|
+
# Same as #last except returns only the second-to-last record.
|
230
232
|
|
231
233
|
# Returns the last record, or the last +n+ records, from the collection.
|
232
234
|
# If the collection is empty, the first form returns +nil+, and the second
|
@@ -260,7 +262,7 @@ module ActiveRecord
|
|
260
262
|
end
|
261
263
|
|
262
264
|
# Gives a record (or N records if a parameter is supplied) from the collection
|
263
|
-
# using the same rules as
|
265
|
+
# using the same rules as ActiveRecord::FinderMethods.take.
|
264
266
|
#
|
265
267
|
# class Person < ActiveRecord::Base
|
266
268
|
# has_many :pets
|
@@ -382,7 +384,7 @@ module ActiveRecord
|
|
382
384
|
# # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
|
383
385
|
#
|
384
386
|
# If the supplied array has an incorrect association type, it raises
|
385
|
-
# an
|
387
|
+
# an ActiveRecord::AssociationTypeMismatch error:
|
386
388
|
#
|
387
389
|
# person.pets.replace(["doo", "ggie", "gaga"])
|
388
390
|
# # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
|
@@ -475,7 +477,7 @@ module ActiveRecord
|
|
475
477
|
|
476
478
|
# Deletes the records of the collection directly from the database
|
477
479
|
# ignoring the +:dependent+ option. Records are instantiated and it
|
478
|
-
# invokes +before_remove+, +after_remove
|
480
|
+
# invokes +before_remove+, +after_remove+, +before_destroy+, and
|
479
481
|
# +after_destroy+ callbacks.
|
480
482
|
#
|
481
483
|
# class Person < ActiveRecord::Base
|
@@ -930,7 +932,7 @@ module ActiveRecord
|
|
930
932
|
@association
|
931
933
|
end
|
932
934
|
|
933
|
-
# Returns a
|
935
|
+
# Returns a Relation object for the records in this association
|
934
936
|
def scope
|
935
937
|
@scope ||= @association.scope
|
936
938
|
end
|
@@ -955,10 +957,13 @@ module ActiveRecord
|
|
955
957
|
# person.pets == other
|
956
958
|
# # => true
|
957
959
|
#
|
960
|
+
#
|
961
|
+
# Note that unpersisted records can still be seen as equal:
|
962
|
+
#
|
958
963
|
# other = [Pet.new(id: 1), Pet.new(id: 2)]
|
959
964
|
#
|
960
965
|
# person.pets == other
|
961
|
-
# # =>
|
966
|
+
# # => true
|
962
967
|
def ==(other)
|
963
968
|
load_target == other
|
964
969
|
end
|
@@ -1102,13 +1107,18 @@ module ActiveRecord
|
|
1102
1107
|
super
|
1103
1108
|
end
|
1104
1109
|
|
1110
|
+
def pretty_print(pp) # :nodoc:
|
1111
|
+
load_target if find_from_target?
|
1112
|
+
super
|
1113
|
+
end
|
1114
|
+
|
1105
1115
|
delegate_methods = [
|
1106
1116
|
QueryMethods,
|
1107
1117
|
SpawnMethods,
|
1108
1118
|
].flat_map { |klass|
|
1109
1119
|
klass.public_instance_methods(false)
|
1110
1120
|
} - self.public_instance_methods(false) - [:select] + [
|
1111
|
-
:scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
|
1121
|
+
:scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all, :load_async
|
1112
1122
|
]
|
1113
1123
|
|
1114
1124
|
delegate(*delegate_methods, to: :scope)
|
@@ -12,7 +12,7 @@ module ActiveRecord::Associations
|
|
12
12
|
|
13
13
|
def nullified_owner_attributes
|
14
14
|
Hash.new.tap do |attrs|
|
15
|
-
|
15
|
+
Array(reflection.foreign_key).each { |foreign_key| attrs[foreign_key] = nil }
|
16
16
|
attrs[reflection.type] = nil if reflection.type.present?
|
17
17
|
end
|
18
18
|
end
|
@@ -22,8 +22,15 @@ module ActiveRecord::Associations
|
|
22
22
|
def set_owner_attributes(record)
|
23
23
|
return if options[:through]
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
primary_key_attribute_names = Array(reflection.join_primary_key)
|
26
|
+
foreign_key_attribute_names = Array(reflection.join_foreign_key)
|
27
|
+
|
28
|
+
primary_key_foreign_key_pairs = primary_key_attribute_names.zip(foreign_key_attribute_names)
|
29
|
+
|
30
|
+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
31
|
+
value = owner._read_attribute(foreign_key)
|
32
|
+
record._write_attribute(primary_key, value)
|
33
|
+
end
|
27
34
|
|
28
35
|
if reflection.type
|
29
36
|
record._write_attribute(reflection.type, owner.class.polymorphic_name)
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
5
|
# = Active Record Has Many Association
|
6
|
+
#
|
6
7
|
# This is the proxy that handles a has many association.
|
7
8
|
#
|
8
9
|
# If the association has a <tt>:through</tt> option further specialization
|
@@ -33,20 +34,24 @@ module ActiveRecord
|
|
33
34
|
|
34
35
|
unless target.empty?
|
35
36
|
association_class = target.first.class
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
if association_class.query_constraints_list
|
38
|
+
primary_key_column = association_class.query_constraints_list.map(&:to_sym)
|
39
|
+
ids = target.collect { |assoc| primary_key_column.map { |col| assoc.public_send(col) } }
|
40
|
+
else
|
41
|
+
primary_key_column = association_class.primary_key.to_sym
|
42
|
+
ids = target.collect { |assoc| assoc.public_send(primary_key_column) }
|
40
43
|
end
|
41
44
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
ids.each_slice(owner.class.destroy_association_async_batch_size || ids.size) do |ids_batch|
|
46
|
+
enqueue_destroy_association(
|
47
|
+
owner_model_name: owner.class.to_s,
|
48
|
+
owner_id: owner.id,
|
49
|
+
association_class: reflection.klass.to_s,
|
50
|
+
association_ids: ids_batch,
|
51
|
+
association_primary_key_column: primary_key_column,
|
52
|
+
ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
|
53
|
+
)
|
54
|
+
end
|
50
55
|
end
|
51
56
|
else
|
52
57
|
delete_all
|
@@ -79,10 +84,13 @@ module ActiveRecord
|
|
79
84
|
scope.count(:all)
|
80
85
|
end
|
81
86
|
|
82
|
-
# If there's nothing in the database
|
83
|
-
#
|
84
|
-
#
|
85
|
-
|
87
|
+
# If there's nothing in the database, @target should only contain new
|
88
|
+
# records or be an empty array. This is a documented side-effect of
|
89
|
+
# the method that may avoid an extra SELECT.
|
90
|
+
if count == 0
|
91
|
+
target.select!(&:new_record?)
|
92
|
+
loaded!
|
93
|
+
end
|
86
94
|
|
87
95
|
[association_scope.limit_value, count].compact.min
|
88
96
|
end
|
@@ -121,7 +129,9 @@ module ActiveRecord
|
|
121
129
|
records.each(&:destroy!)
|
122
130
|
update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
|
123
131
|
else
|
124
|
-
|
132
|
+
query_constraints = reflection.klass.composite_query_constraints_list
|
133
|
+
values = records.map { |r| query_constraints.map { |col| r._read_attribute(col) } }
|
134
|
+
scope = self.scope.where(query_constraints => values)
|
125
135
|
update_counter(-delete_count(method, scope))
|
126
136
|
end
|
127
137
|
end
|
@@ -59,9 +59,10 @@ module ActiveRecord
|
|
59
59
|
|
60
60
|
attributes = through_scope_attributes
|
61
61
|
attributes[source_reflection.name] = record
|
62
|
-
attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
|
63
62
|
|
64
|
-
through_association.build(attributes)
|
63
|
+
through_association.build(attributes).tap do |new_record|
|
64
|
+
new_record.send("#{source_reflection.foreign_type}=", options[:source_type]) if options[:source_type]
|
65
|
+
end
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
@@ -69,9 +70,12 @@ module ActiveRecord
|
|
69
70
|
|
70
71
|
def through_scope_attributes
|
71
72
|
scope = through_scope || self.scope
|
72
|
-
scope.where_values_hash(through_association.reflection.
|
73
|
-
|
74
|
-
|
73
|
+
attributes = scope.where_values_hash(through_association.reflection.klass.table_name)
|
74
|
+
except_keys = [
|
75
|
+
*Array(through_association.reflection.foreign_key),
|
76
|
+
through_association.reflection.klass.inheritance_column
|
77
|
+
]
|
78
|
+
attributes.except!(*except_keys)
|
75
79
|
end
|
76
80
|
|
77
81
|
def save_through_record(record)
|
@@ -109,7 +113,7 @@ module ActiveRecord
|
|
109
113
|
end
|
110
114
|
|
111
115
|
def target_reflection_has_associated_record?
|
112
|
-
!(through_reflection.belongs_to? &&
|
116
|
+
!(through_reflection.belongs_to? && Array(through_reflection.foreign_key).all? { |foreign_key_column| owner[foreign_key_column].blank? })
|
113
117
|
end
|
114
118
|
|
115
119
|
def update_through_counter?(method)
|
@@ -33,8 +33,13 @@ module ActiveRecord
|
|
33
33
|
target.destroy
|
34
34
|
throw(:abort) unless target.destroyed?
|
35
35
|
when :destroy_async
|
36
|
-
|
37
|
-
|
36
|
+
if target.class.query_constraints_list
|
37
|
+
primary_key_column = target.class.query_constraints_list.map(&:to_sym)
|
38
|
+
id = primary_key_column.map { |col| target.public_send(col) }
|
39
|
+
else
|
40
|
+
primary_key_column = target.class.primary_key.to_sym
|
41
|
+
id = target.public_send(primary_key_column)
|
42
|
+
end
|
38
43
|
|
39
44
|
enqueue_destroy_association(
|
40
45
|
owner_model_name: owner.class.to_s,
|
@@ -112,7 +117,9 @@ module ActiveRecord
|
|
112
117
|
end
|
113
118
|
|
114
119
|
def nullify_owner_attributes(record)
|
115
|
-
|
120
|
+
Array(reflection.foreign_key).each do |foreign_key_column|
|
121
|
+
record[foreign_key_column] = nil unless foreign_key_column.in?(Array(record.class.primary_key))
|
122
|
+
end
|
116
123
|
end
|
117
124
|
|
118
125
|
def transaction_if(value, &block)
|
@@ -252,35 +252,41 @@ module ActiveRecord
|
|
252
252
|
next
|
253
253
|
end
|
254
254
|
|
255
|
-
|
256
|
-
|
257
|
-
|
255
|
+
if node.primary_key
|
256
|
+
keys = Array(node.primary_key).map { |column| aliases.column_alias(node, column) }
|
257
|
+
ids = keys.map { |key| row[key] }
|
258
|
+
else
|
259
|
+
keys = Array(node.reflection.join_primary_key).map { |column| aliases.column_alias(node, column.to_s) }
|
260
|
+
ids = keys.map { nil } # Avoid id-based model caching.
|
261
|
+
end
|
262
|
+
|
263
|
+
if keys.any? { |key| row[key].nil? }
|
258
264
|
nil_association = ar_parent.association(node.reflection.name)
|
259
265
|
nil_association.loaded!
|
260
266
|
next
|
261
267
|
end
|
262
268
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
|
269
|
-
|
270
|
-
seen[ar_parent][node][id] = model
|
271
|
-
construct(model, node, row, seen, model_cache, strict_loading_value)
|
269
|
+
ids.each do |id|
|
270
|
+
unless model = seen[ar_parent][node][id]
|
271
|
+
model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
|
272
|
+
seen[ar_parent][node][id] = model if id
|
273
|
+
end
|
272
274
|
end
|
275
|
+
|
276
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
273
277
|
end
|
274
278
|
end
|
275
279
|
|
276
280
|
def construct_model(record, node, row, model_cache, id, strict_loading_value)
|
277
281
|
other = record.association(node.reflection.name)
|
278
282
|
|
279
|
-
model = model_cache[node][id]
|
280
|
-
node.instantiate(row, aliases.column_aliases(node)) do |m|
|
283
|
+
unless model = model_cache[node][id]
|
284
|
+
model = node.instantiate(row, aliases.column_aliases(node)) do |m|
|
281
285
|
m.strict_loading! if strict_loading_value
|
282
286
|
other.set_inverse_instance(m)
|
283
287
|
end
|
288
|
+
model_cache[node][id] = model if id
|
289
|
+
end
|
284
290
|
|
285
291
|
if node.reflection.collection?
|
286
292
|
other.target.push(model)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :enddoc:
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
module Associations
|
5
7
|
class Preloader
|
@@ -36,7 +38,19 @@ module ActiveRecord
|
|
36
38
|
end
|
37
39
|
|
38
40
|
def load_records_for_keys(keys, &block)
|
39
|
-
|
41
|
+
if association_key_name.is_a?(Array)
|
42
|
+
query_constraints = Hash.new { |hsh, key| hsh[key] = Set.new }
|
43
|
+
|
44
|
+
keys.each_with_object(query_constraints) do |values_set, constraints|
|
45
|
+
association_key_name.zip(values_set).each do |key_name, value|
|
46
|
+
constraints[key_name] << value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
scope.where(query_constraints)
|
51
|
+
else
|
52
|
+
scope.where(association_key_name => keys)
|
53
|
+
end.load(&block)
|
40
54
|
end
|
41
55
|
end
|
42
56
|
|
@@ -151,7 +165,7 @@ module ActiveRecord
|
|
151
165
|
|
152
166
|
def owners_by_key
|
153
167
|
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
154
|
-
key =
|
168
|
+
key = derive_key(owner, owner_key_name)
|
155
169
|
(result[key] ||= []) << owner if key
|
156
170
|
end
|
157
171
|
end
|
@@ -169,7 +183,7 @@ module ActiveRecord
|
|
169
183
|
end
|
170
184
|
|
171
185
|
def set_inverse(record)
|
172
|
-
if owners = owners_by_key[
|
186
|
+
if owners = owners_by_key[derive_key(record, association_key_name)]
|
173
187
|
# Processing only the first owner
|
174
188
|
# because the record is modified but not an owner
|
175
189
|
association = owners.first.association(reflection.name)
|
@@ -182,11 +196,10 @@ module ActiveRecord
|
|
182
196
|
# #compare_by_identity makes such owners different hash keys
|
183
197
|
@records_by_owner = {}.compare_by_identity
|
184
198
|
raw_records ||= loader_query.records_for([self])
|
185
|
-
|
186
199
|
@preloaded_records = raw_records.select do |record|
|
187
200
|
assignments = false
|
188
201
|
|
189
|
-
owners_by_key[
|
202
|
+
owners_by_key[derive_key(record, association_key_name)]&.each do |owner|
|
190
203
|
entries = (@records_by_owner[owner] ||= [])
|
191
204
|
|
192
205
|
if reflection.collection? || entries.empty?
|
@@ -206,7 +219,7 @@ module ActiveRecord
|
|
206
219
|
return if reflection.collection?
|
207
220
|
|
208
221
|
unscoped_records.select { |r| r[association_key_name].present? }.each do |record|
|
209
|
-
owners = owners_by_key[
|
222
|
+
owners = owners_by_key[derive_key(record, association_key_name)]
|
210
223
|
owners&.each_with_index do |owner, i|
|
211
224
|
association = owner.association(reflection.name)
|
212
225
|
association.target = record
|
@@ -246,6 +259,14 @@ module ActiveRecord
|
|
246
259
|
@key_conversion_required
|
247
260
|
end
|
248
261
|
|
262
|
+
def derive_key(owner, key)
|
263
|
+
if key.is_a?(Array)
|
264
|
+
key.map { |k| convert_key(owner._read_attribute(k)) }
|
265
|
+
else
|
266
|
+
convert_key(owner._read_attribute(key))
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
249
270
|
def convert_key(key)
|
250
271
|
if key_conversion_required?
|
251
272
|
key.to_s
|
@@ -4,6 +4,8 @@ require "active_support/core_ext/enumerable"
|
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
6
|
module Associations
|
7
|
+
# = Active Record \Preloader
|
8
|
+
#
|
7
9
|
# Implements the details of eager loading of Active Record associations.
|
8
10
|
#
|
9
11
|
# Suppose that you have the following two Active Record models:
|
@@ -22,8 +24,8 @@ module ActiveRecord
|
|
22
24
|
#
|
23
25
|
# Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
|
24
26
|
#
|
25
|
-
#
|
26
|
-
#
|
27
|
+
# # SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
|
28
|
+
# # SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
|
27
29
|
#
|
28
30
|
# Active Record saves the ids of the records from the first query to use in
|
29
31
|
# the second. Depending on the number of associations involved there can be
|
@@ -33,11 +35,11 @@ module ActiveRecord
|
|
33
35
|
# Record will fall back to a slightly more resource-intensive single query:
|
34
36
|
#
|
35
37
|
# Author.includes(:books).where(books: {title: 'Illiad'}).to_a
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
38
|
+
# # SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
|
39
|
+
# # `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
|
40
|
+
# # FROM `authors`
|
41
|
+
# # LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
|
42
|
+
# # WHERE `books`.`title` = 'Illiad'
|
41
43
|
#
|
42
44
|
# This could result in many rows that contain redundant data and it performs poorly at scale
|
43
45
|
# and is therefore only used when necessary.
|
@@ -73,15 +75,16 @@ module ActiveRecord
|
|
73
75
|
# for an Author.
|
74
76
|
# - an Array which specifies multiple association names. This array
|
75
77
|
# is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
|
76
|
-
# allows this method to preload an author's avatar as well as all of
|
78
|
+
# allows this method to preload an author's avatar as well as all of their
|
77
79
|
# books.
|
78
80
|
# - a Hash which specifies multiple association names, as well as
|
79
81
|
# association names for the to-be-preloaded association objects. For
|
80
82
|
# example, specifying <tt>{ author: :avatar }</tt> will preload a
|
81
83
|
# book's author, as well as that author's avatar.
|
82
84
|
#
|
83
|
-
# +:associations+ has the same format as the
|
84
|
-
#
|
85
|
+
# +:associations+ has the same format as the arguments to
|
86
|
+
# ActiveRecord::QueryMethods#includes. So +associations+ could look like
|
87
|
+
# this:
|
85
88
|
#
|
86
89
|
# :books
|
87
90
|
# [ :books, :author ]
|
@@ -7,6 +7,10 @@ module ActiveRecord
|
|
7
7
|
delegate :source_reflection, to: :reflection
|
8
8
|
|
9
9
|
private
|
10
|
+
def transaction(&block)
|
11
|
+
through_reflection.klass.transaction(&block)
|
12
|
+
end
|
13
|
+
|
10
14
|
def through_reflection
|
11
15
|
@through_reflection ||= begin
|
12
16
|
refl = reflection.through_reflection
|
@@ -55,12 +59,11 @@ module ActiveRecord
|
|
55
59
|
|
56
60
|
association_primary_key = source_reflection.association_primary_key(reflection.klass)
|
57
61
|
|
58
|
-
if association_primary_key == reflection.klass.
|
62
|
+
if Array(association_primary_key) == reflection.klass.composite_query_constraints_list && !options[:source_type]
|
59
63
|
join_attributes = { source_reflection.name => records }
|
60
64
|
else
|
61
|
-
|
62
|
-
|
63
|
-
}
|
65
|
+
assoc_pk_values = records.map { |record| record._read_attribute(association_primary_key) }
|
66
|
+
join_attributes = { source_reflection.foreign_key => assoc_pk_values }
|
64
67
|
end
|
65
68
|
|
66
69
|
if options[:source_type]
|
@@ -78,12 +81,16 @@ module ActiveRecord
|
|
78
81
|
# to try to properly support stale-checking for nested associations.
|
79
82
|
def stale_state
|
80
83
|
if through_reflection.belongs_to?
|
81
|
-
|
84
|
+
Array(through_reflection.foreign_key).filter_map do |foreign_key_column|
|
85
|
+
owner[foreign_key_column] && owner[foreign_key_column].to_s
|
86
|
+
end.presence
|
82
87
|
end
|
83
88
|
end
|
84
89
|
|
85
90
|
def foreign_key_present?
|
86
|
-
through_reflection.belongs_to? &&
|
91
|
+
through_reflection.belongs_to? && Array(through_reflection.foreign_key).all? do |foreign_key_column|
|
92
|
+
!owner[foreign_key_column].nil?
|
93
|
+
end
|
87
94
|
end
|
88
95
|
|
89
96
|
def ensure_mutable
|
@@ -107,11 +114,15 @@ module ActiveRecord
|
|
107
114
|
end
|
108
115
|
|
109
116
|
def build_record(attributes)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
117
|
+
if source_reflection.collection?
|
118
|
+
inverse = source_reflection.inverse_of
|
119
|
+
target = through_association.target
|
120
|
+
|
121
|
+
if inverse && target && !target.is_a?(Array)
|
122
|
+
Array(target.id).zip(Array(inverse.foreign_key)).map do |primary_key_value, foreign_key_column|
|
123
|
+
attributes[foreign_key_column] = primary_key_value
|
124
|
+
end
|
125
|
+
end
|
115
126
|
end
|
116
127
|
|
117
128
|
super
|