activerecord 7.0.4 → 7.1.5.1
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 +1971 -1243
- 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 +20 -4
- 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 +20 -14
- data/lib/active_record/associations/collection_proxy.rb +20 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- 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/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/preloader/association.rb +31 -7
- 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 +333 -222
- 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 +53 -35
- 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 +21 -8
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -26
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +59 -10
- data/lib/active_record/base.rb +7 -2
- 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 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- 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 +80 -50
- 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 +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
- 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 +155 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
- 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 -14
- 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 +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
- 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/cidr.rb +6 -0
- 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 +2 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- 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 +365 -61
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
- 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 +9 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
- 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 +258 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +181 -154
- data/lib/active_record/counter_cache.rb +52 -27
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +28 -14
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +15 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- 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 +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- 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_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- 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 +135 -71
- data/lib/active_record/future_result.rb +40 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +33 -19
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +59 -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 +9 -11
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +105 -7
- data/lib/active_record/migration/compatibility.rb +163 -58
- 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 +271 -114
- data/lib/active_record/model_schema.rb +69 -44
- data/lib/active_record/nested_attributes.rb +37 -8
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +195 -42
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +4 -22
- 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 +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +14 -9
- data/lib/active_record/railties/databases.rake +144 -150
- 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 +232 -81
- 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 +10 -7
- 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 +408 -76
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +103 -37
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +50 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- 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/signed_id.rb +7 -5
- data/lib/active_record/store.rb +9 -9
- 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 +152 -108
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +114 -96
- data/lib/active_record/timestamp.rb +30 -16
- 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/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/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +130 -17
- 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/tree_manager.rb +5 -1
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +83 -18
- 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
|
@@ -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,6 +1107,11 @@ 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,
|
@@ -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
|
39
|
+
ids = target.collect { |assoc| primary_key_column.map { |col| assoc.public_send(col) } }
|
40
|
+
else
|
41
|
+
primary_key_column = association_class.primary_key
|
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
|
@@ -124,7 +129,9 @@ module ActiveRecord
|
|
124
129
|
records.each(&:destroy!)
|
125
130
|
update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
|
126
131
|
else
|
127
|
-
|
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)
|
128
135
|
update_counter(-delete_count(method, scope))
|
129
136
|
end
|
130
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
|
38
|
+
id = primary_key_column.map { |col| target.public_send(col) }
|
39
|
+
else
|
40
|
+
primary_key_column = target.class.primary_key
|
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)
|
@@ -25,8 +25,9 @@ module ActiveRecord
|
|
25
25
|
joins = []
|
26
26
|
chain = []
|
27
27
|
|
28
|
-
reflection.chain
|
29
|
-
|
28
|
+
reflection_chain = reflection.chain
|
29
|
+
reflection_chain.each_with_index do |reflection, index|
|
30
|
+
table, terminated = yield reflection, reflection_chain[index..]
|
30
31
|
@table ||= table
|
31
32
|
|
32
33
|
if terminated
|
@@ -61,7 +61,7 @@ module ActiveRecord
|
|
61
61
|
when Hash
|
62
62
|
associations.each do |k, v|
|
63
63
|
cache = hash[k] ||= {}
|
64
|
-
walk_tree v, cache
|
64
|
+
walk_tree v, cache if v
|
65
65
|
end
|
66
66
|
else
|
67
67
|
raise ConfigurationError, associations.inspect
|
@@ -190,12 +190,12 @@ module ActiveRecord
|
|
190
190
|
def make_constraints(parent, child, join_type)
|
191
191
|
foreign_table = parent.table
|
192
192
|
foreign_klass = parent.base_klass
|
193
|
-
child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
|
194
|
-
table, terminated = @joined_tables[
|
193
|
+
child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection, remaining_reflection_chain|
|
194
|
+
table, terminated = @joined_tables[remaining_reflection_chain]
|
195
195
|
root = reflection == child.reflection
|
196
196
|
|
197
197
|
if table && (!root || !terminated)
|
198
|
-
@joined_tables[
|
198
|
+
@joined_tables[remaining_reflection_chain] = [table, root] if root
|
199
199
|
next table, true
|
200
200
|
end
|
201
201
|
|
@@ -206,7 +206,7 @@ module ActiveRecord
|
|
206
206
|
root ? name : "#{name}_join"
|
207
207
|
end
|
208
208
|
|
209
|
-
@joined_tables[
|
209
|
+
@joined_tables[remaining_reflection_chain] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
|
210
210
|
table
|
211
211
|
end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
|
212
212
|
end
|
@@ -253,14 +253,14 @@ module ActiveRecord
|
|
253
253
|
end
|
254
254
|
|
255
255
|
if node.primary_key
|
256
|
-
|
257
|
-
id = row[key]
|
256
|
+
keys = Array(node.primary_key).map { |column| aliases.column_alias(node, column) }
|
257
|
+
id = keys.map { |key| row[key] }
|
258
258
|
else
|
259
|
-
|
260
|
-
id = nil # Avoid id-based model caching.
|
259
|
+
keys = Array(node.reflection.join_primary_key).map { |column| aliases.column_alias(node, column.to_s) }
|
260
|
+
id = keys.map { nil } # Avoid id-based model caching.
|
261
261
|
end
|
262
262
|
|
263
|
-
if row[key].nil?
|
263
|
+
if keys.any? { |key| row[key].nil? }
|
264
264
|
nil_association = ar_parent.association(node.reflection.name)
|
265
265
|
nil_association.loaded!
|
266
266
|
next
|
@@ -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
|
@@ -15,11 +17,12 @@ module ActiveRecord
|
|
15
17
|
def eql?(other)
|
16
18
|
association_key_name == other.association_key_name &&
|
17
19
|
scope.table_name == other.scope.table_name &&
|
20
|
+
scope.connection_specification_name == other.scope.connection_specification_name &&
|
18
21
|
scope.values_for_queries == other.scope.values_for_queries
|
19
22
|
end
|
20
23
|
|
21
24
|
def hash
|
22
|
-
[association_key_name, scope.table_name, scope.values_for_queries].hash
|
25
|
+
[association_key_name, scope.table_name, scope.connection_specification_name, scope.values_for_queries].hash
|
23
26
|
end
|
24
27
|
|
25
28
|
def records_for(loaders)
|
@@ -36,7 +39,21 @@ module ActiveRecord
|
|
36
39
|
end
|
37
40
|
|
38
41
|
def load_records_for_keys(keys, &block)
|
39
|
-
|
42
|
+
return [] if keys.empty?
|
43
|
+
|
44
|
+
if association_key_name.is_a?(Array)
|
45
|
+
query_constraints = Hash.new { |hsh, key| hsh[key] = Set.new }
|
46
|
+
|
47
|
+
keys.each_with_object(query_constraints) do |values_set, constraints|
|
48
|
+
association_key_name.zip(values_set).each do |key_name, value|
|
49
|
+
constraints[key_name] << value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
scope.where(query_constraints)
|
54
|
+
else
|
55
|
+
scope.where(association_key_name => keys)
|
56
|
+
end.load(&block)
|
40
57
|
end
|
41
58
|
end
|
42
59
|
|
@@ -151,7 +168,7 @@ module ActiveRecord
|
|
151
168
|
|
152
169
|
def owners_by_key
|
153
170
|
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
154
|
-
key =
|
171
|
+
key = derive_key(owner, owner_key_name)
|
155
172
|
(result[key] ||= []) << owner if key
|
156
173
|
end
|
157
174
|
end
|
@@ -169,7 +186,7 @@ module ActiveRecord
|
|
169
186
|
end
|
170
187
|
|
171
188
|
def set_inverse(record)
|
172
|
-
if owners = owners_by_key[
|
189
|
+
if owners = owners_by_key[derive_key(record, association_key_name)]
|
173
190
|
# Processing only the first owner
|
174
191
|
# because the record is modified but not an owner
|
175
192
|
association = owners.first.association(reflection.name)
|
@@ -182,11 +199,10 @@ module ActiveRecord
|
|
182
199
|
# #compare_by_identity makes such owners different hash keys
|
183
200
|
@records_by_owner = {}.compare_by_identity
|
184
201
|
raw_records ||= loader_query.records_for([self])
|
185
|
-
|
186
202
|
@preloaded_records = raw_records.select do |record|
|
187
203
|
assignments = false
|
188
204
|
|
189
|
-
owners_by_key[
|
205
|
+
owners_by_key[derive_key(record, association_key_name)]&.each do |owner|
|
190
206
|
entries = (@records_by_owner[owner] ||= [])
|
191
207
|
|
192
208
|
if reflection.collection? || entries.empty?
|
@@ -206,7 +222,7 @@ module ActiveRecord
|
|
206
222
|
return if reflection.collection?
|
207
223
|
|
208
224
|
unscoped_records.select { |r| r[association_key_name].present? }.each do |record|
|
209
|
-
owners = owners_by_key[
|
225
|
+
owners = owners_by_key[derive_key(record, association_key_name)]
|
210
226
|
owners&.each_with_index do |owner, i|
|
211
227
|
association = owner.association(reflection.name)
|
212
228
|
association.target = record
|
@@ -246,6 +262,14 @@ module ActiveRecord
|
|
246
262
|
@key_conversion_required
|
247
263
|
end
|
248
264
|
|
265
|
+
def derive_key(owner, key)
|
266
|
+
if key.is_a?(Array)
|
267
|
+
key.map { |k| convert_key(owner._read_attribute(k)) }
|
268
|
+
else
|
269
|
+
convert_key(owner._read_attribute(key))
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
249
273
|
def convert_key(key)
|
250
274
|
if key_conversion_required?
|
251
275
|
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
|