activerecord 7.0.8.7 → 7.2.2.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 +631 -1944
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- 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 +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- 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 +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -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 +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- 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 +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- 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 +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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 +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- 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 +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
- 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 +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- 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 +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- 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/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- 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 +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- 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 +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- data/lib/active_record/migration/default_strategy.rb +22 -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 +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- 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 +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -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 +28 -16
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -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 +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- 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 +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +106 -24
- data/lib/active_record/translation.rb +0 -2
- 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 +1 -3
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +9 -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 +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -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/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- 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 +54 -12
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -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
|
@@ -73,7 +78,7 @@ module ActiveRecord
|
|
73
78
|
# If the collection is empty the target is set to an empty array and
|
74
79
|
# the loaded flag is set to true as well.
|
75
80
|
def count_records
|
76
|
-
count = if reflection.
|
81
|
+
count = if reflection.has_active_cached_counter?
|
77
82
|
owner.read_attribute(reflection.counter_cache_column).to_i
|
78
83
|
else
|
79
84
|
scope.count(: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)
|
@@ -89,7 +93,13 @@ module ActiveRecord
|
|
89
93
|
@through_scope = scope
|
90
94
|
record = super
|
91
95
|
|
92
|
-
inverse =
|
96
|
+
inverse =
|
97
|
+
if source_reflection.polymorphic?
|
98
|
+
source_reflection.polymorphic_inverse_of(record.class)
|
99
|
+
else
|
100
|
+
source_reflection.inverse_of
|
101
|
+
end
|
102
|
+
|
93
103
|
if inverse
|
94
104
|
if inverse.collection?
|
95
105
|
record.send(inverse.name) << build_through_record(record)
|
@@ -109,7 +119,7 @@ module ActiveRecord
|
|
109
119
|
end
|
110
120
|
|
111
121
|
def target_reflection_has_associated_record?
|
112
|
-
!(through_reflection.belongs_to? &&
|
122
|
+
!(through_reflection.belongs_to? && Array(through_reflection.foreign_key).all? { |foreign_key_column| owner[foreign_key_column].blank? })
|
113
123
|
end
|
114
124
|
|
115
125
|
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
|
@@ -37,39 +38,41 @@ module ActiveRecord
|
|
37
38
|
chain << [reflection, table]
|
38
39
|
end
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
base_klass.with_connection do |connection|
|
42
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
43
|
+
# more sense in this context), so we reverse
|
44
|
+
chain.reverse_each do |reflection, table|
|
45
|
+
klass = reflection.klass
|
44
46
|
|
45
|
-
|
47
|
+
scope = reflection.join_scope(table, foreign_table, foreign_klass)
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
+
unless scope.references_values.empty?
|
50
|
+
associations = scope.eager_load_values | scope.includes_values
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
+
unless associations.empty?
|
53
|
+
scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
|
54
|
+
end
|
52
55
|
end
|
53
|
-
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
+
arel = scope.arel(alias_tracker.aliases)
|
58
|
+
nodes = arel.constraints.first
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
if nodes.is_a?(Arel::Nodes::And)
|
61
|
+
others = nodes.children.extract! do |node|
|
62
|
+
!Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
|
63
|
+
end
|
61
64
|
end
|
62
|
-
end
|
63
65
|
|
64
|
-
|
66
|
+
joins << join_type.new(table, Arel::Nodes::On.new(nodes))
|
65
67
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
if others && !others.empty?
|
69
|
+
joins.concat arel.join_sources
|
70
|
+
append_constraints(connection, joins.last, others)
|
71
|
+
end
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
+
# The current table in this iteration becomes the foreign table in the next
|
74
|
+
foreign_table, foreign_klass = table, klass
|
75
|
+
end
|
73
76
|
end
|
74
77
|
|
75
78
|
joins
|
@@ -88,10 +91,10 @@ module ActiveRecord
|
|
88
91
|
end
|
89
92
|
|
90
93
|
private
|
91
|
-
def append_constraints(join, constraints)
|
94
|
+
def append_constraints(connection, join, constraints)
|
92
95
|
if join.is_a?(Arel::Nodes::StringJoin)
|
93
96
|
join_string = Arel::Nodes::And.new(constraints.unshift join.left)
|
94
|
-
join.left = Arel.sql(
|
97
|
+
join.left = Arel.sql(connection.visitor.compile(join_string))
|
95
98
|
else
|
96
99
|
right = join.right
|
97
100
|
right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
|
@@ -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
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Validation error class to wrap association records' errors,
|
4
|
+
# with index_errors support.
|
5
|
+
module ActiveRecord
|
6
|
+
module Associations
|
7
|
+
class NestedError < ::ActiveModel::NestedError
|
8
|
+
def initialize(association, inner_error)
|
9
|
+
@base = association.owner
|
10
|
+
@association = association
|
11
|
+
@inner_error = inner_error
|
12
|
+
super(@base, inner_error, { attribute: compute_attribute(inner_error) })
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
attr_reader :association
|
17
|
+
|
18
|
+
def compute_attribute(inner_error)
|
19
|
+
association_name = association.reflection.name
|
20
|
+
|
21
|
+
if association.collection? && index_errors_setting && index
|
22
|
+
"#{association_name}[#{index}].#{inner_error.attribute}".to_sym
|
23
|
+
else
|
24
|
+
"#{association_name}.#{inner_error.attribute}".to_sym
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def index_errors_setting
|
29
|
+
@index_errors_setting ||=
|
30
|
+
association.options.fetch(:index_errors, ActiveRecord.index_nested_attribute_errors)
|
31
|
+
end
|
32
|
+
|
33
|
+
def index
|
34
|
+
@index ||= ordered_records&.find_index(inner_error.base)
|
35
|
+
end
|
36
|
+
|
37
|
+
def ordered_records
|
38
|
+
case index_errors_setting
|
39
|
+
when true # default is association order
|
40
|
+
association.target
|
41
|
+
when :nested_attributes_order
|
42
|
+
association.nested_attributes_target
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -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
|
@@ -232,7 +248,8 @@ module ActiveRecord
|
|
232
248
|
association = owner.association(reflection.name)
|
233
249
|
|
234
250
|
if reflection.collection?
|
235
|
-
association.target
|
251
|
+
not_persisted_records = association.target.reject(&:persisted?)
|
252
|
+
association.target = records + not_persisted_records
|
236
253
|
else
|
237
254
|
association.target = records.first
|
238
255
|
end
|
@@ -246,6 +263,14 @@ module ActiveRecord
|
|
246
263
|
@key_conversion_required
|
247
264
|
end
|
248
265
|
|
266
|
+
def derive_key(owner, key)
|
267
|
+
if key.is_a?(Array)
|
268
|
+
key.map { |k| convert_key(owner._read_attribute(k)) }
|
269
|
+
else
|
270
|
+
convert_key(owner._read_attribute(key))
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
249
274
|
def convert_key(key)
|
250
275
|
if key_conversion_required?
|
251
276
|
key.to_s
|
@@ -9,7 +9,13 @@ module ActiveRecord
|
|
9
9
|
attr_writer :preloaded_records
|
10
10
|
|
11
11
|
def initialize(association:, children:, parent:, associate_by_default:, scope:)
|
12
|
-
@association = association
|
12
|
+
@association = if association
|
13
|
+
begin
|
14
|
+
@association = association.to_sym
|
15
|
+
rescue NoMethodError
|
16
|
+
raise ArgumentError, "Association names must be Symbol or String, got: #{association.class.name}"
|
17
|
+
end
|
18
|
+
end
|
13
19
|
@parent = parent
|
14
20
|
@scope = scope
|
15
21
|
@associate_by_default = associate_by_default
|
@@ -9,9 +9,7 @@ module ActiveRecord
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def records_by_owner
|
12
|
-
|
13
|
-
|
14
|
-
@records_by_owner = owners.each_with_object({}) do |owner, result|
|
12
|
+
@records_by_owner ||= owners.each_with_object({}) do |owner, result|
|
15
13
|
if loaded?(owner)
|
16
14
|
result[owner] = target_for(owner)
|
17
15
|
next
|
@@ -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 ]
|
@@ -14,6 +14,12 @@ module ActiveRecord
|
|
14
14
|
target
|
15
15
|
end
|
16
16
|
|
17
|
+
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
18
|
+
def reset
|
19
|
+
super
|
20
|
+
@target = nil
|
21
|
+
end
|
22
|
+
|
17
23
|
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
|
18
24
|
def writer(record)
|
19
25
|
replace(record)
|
@@ -34,7 +40,7 @@ module ActiveRecord
|
|
34
40
|
|
35
41
|
private
|
36
42
|
def scope_for_create
|
37
|
-
super.except!(klass.primary_key)
|
43
|
+
super.except!(*Array(klass.primary_key))
|
38
44
|
end
|
39
45
|
|
40
46
|
def find_target
|
@@ -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]
|
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
|