activerecord 7.0.5 → 7.1.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1624 -1338
- 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 +16 -10
- 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 -7
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +6 -8
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +313 -217
- 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 +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- 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 +74 -51
- 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 +41 -6
- 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 +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +290 -125
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +505 -102
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +214 -113
- 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 +21 -14
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
- 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 +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -41
- 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 +15 -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 +361 -60
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- 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 +211 -83
- 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 +262 -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 +175 -153
- data/lib/active_record/counter_cache.rb +46 -25
- 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 +86 -33
- 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/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 +21 -6
- 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 +19 -22
- data/lib/active_record/encryption.rb +1 -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/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 +31 -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 +32 -18
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- 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 +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +104 -5
- data/lib/active_record/migration/compatibility.rb +150 -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 +64 -44
- data/lib/active_record/nested_attributes.rb +24 -6
- 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 +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +109 -47
- data/lib/active_record/railties/controller_runtime.rb +14 -9
- data/lib/active_record/railties/databases.rake +142 -148
- 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 +182 -44
- 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 +187 -63
- 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 +27 -16
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +386 -70
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +91 -35
- 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 +46 -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 +8 -8
- 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 +127 -105
- 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 +113 -96
- 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/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +4 -0
- 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 +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- 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/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
@@ -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
|
@@ -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.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,
|
@@ -87,10 +92,6 @@ module ActiveRecord
|
|
87
92
|
replace(record, false)
|
88
93
|
end
|
89
94
|
|
90
|
-
def replace_keys(record, force: false)
|
91
|
-
# Has one association doesn't have foreign keys to replace.
|
92
|
-
end
|
93
|
-
|
94
95
|
def remove_target!(method)
|
95
96
|
case method
|
96
97
|
when :delete
|
@@ -116,7 +117,9 @@ module ActiveRecord
|
|
116
117
|
end
|
117
118
|
|
118
119
|
def nullify_owner_attributes(record)
|
119
|
-
|
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
|
120
123
|
end
|
121
124
|
|
122
125
|
def transaction_if(value, &block)
|
@@ -253,22 +253,24 @@ module ActiveRecord
|
|
253
253
|
end
|
254
254
|
|
255
255
|
if node.primary_key
|
256
|
-
|
257
|
-
|
256
|
+
keys = Array(node.primary_key).map { |column| aliases.column_alias(node, column) }
|
257
|
+
ids = keys.map { |key| row[key] }
|
258
258
|
else
|
259
|
-
|
260
|
-
|
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
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
|
267
267
|
end
|
268
268
|
|
269
|
-
|
270
|
-
model =
|
271
|
-
|
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
|
273
275
|
|
274
276
|
construct(model, node, row, seen, model_cache, strict_loading_value)
|
@@ -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 ]
|
@@ -34,7 +34,7 @@ module ActiveRecord
|
|
34
34
|
|
35
35
|
private
|
36
36
|
def scope_for_create
|
37
|
-
super.except!(klass.primary_key)
|
37
|
+
super.except!(*Array(klass.primary_key))
|
38
38
|
end
|
39
39
|
|
40
40
|
def find_target
|
@@ -54,13 +54,11 @@ module ActiveRecord
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def _create_record(attributes, raise_error = false, &block)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
record
|
63
|
-
end
|
57
|
+
record = build_record(attributes, &block)
|
58
|
+
saved = record.save
|
59
|
+
set_new_record(record)
|
60
|
+
raise RecordInvalid.new(record) if !saved && raise_error
|
61
|
+
record
|
64
62
|
end
|
65
63
|
end
|
66
64
|
end
|
@@ -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
|