activerecord 6.1.7 → 7.2.0
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 +520 -1385
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +17 -14
- data/lib/active_record/association_relation.rb +2 -12
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +60 -21
- data/lib/active_record/associations/association_scope.rb +17 -12
- data/lib/active_record/associations/belongs_to_association.rb +37 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
- data/lib/active_record/associations/builder/association.rb +11 -5
- data/lib/active_record/associations/builder/belongs_to.rb +41 -14
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +4 -4
- data/lib/active_record/associations/builder/singular_association.rb +6 -2
- data/lib/active_record/associations/collection_association.rb +46 -36
- data/lib/active_record/associations/collection_proxy.rb +44 -16
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- 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 +29 -19
- data/lib/active_record/associations/has_many_through_association.rb +12 -7
- data/lib/active_record/associations/has_one_association.rb +20 -10
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +23 -15
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +212 -53
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +50 -16
- data/lib/active_record/associations/preloader.rb +50 -121
- data/lib/active_record/associations/singular_association.rb +15 -3
- data/lib/active_record/associations/through_association.rb +25 -14
- data/lib/active_record/associations.rb +404 -509
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +2 -14
- data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +73 -22
- data/lib/active_record/attribute_methods/primary_key.rb +47 -27
- data/lib/active_record/attribute_methods/query.rb +31 -19
- data/lib/active_record/attribute_methods/read.rb +14 -11
- data/lib/active_record/attribute_methods/serialization.rb +174 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -9
- data/lib/active_record/attribute_methods/write.rb +12 -15
- data/lib/active_record/attribute_methods.rb +164 -52
- data/lib/active_record/attributes.rb +51 -49
- data/lib/active_record/autosave_association.rb +74 -57
- data/lib/active_record/base.rb +27 -5
- data/lib/active_record/callbacks.rb +18 -34
- 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 -46
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +327 -612
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -64
- data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +377 -142
- data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
- data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +345 -166
- data/lib/active_record/connection_adapters/column.rb +13 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
- data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
- data/lib/active_record/connection_adapters/pool_config.rb +26 -16
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
- 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/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/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 +12 -3
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +401 -77
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
- data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
- 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 +130 -6
- data/lib/active_record/connection_handling.rb +132 -146
- data/lib/active_record/core.rb +276 -251
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
- data/lib/active_record/database_configurations/database_config.rb +34 -10
- data/lib/active_record/database_configurations/hash_config.rb +107 -31
- data/lib/active_record/database_configurations/url_config.rb +38 -13
- data/lib/active_record/database_configurations.rb +96 -60
- data/lib/active_record/delegated_type.rb +90 -20
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +4 -2
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +3 -3
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +170 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +100 -0
- data/lib/active_record/encryption.rb +56 -0
- data/lib/active_record/enum.rb +163 -63
- data/lib/active_record/errors.rb +210 -27
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +15 -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 +70 -14
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +179 -112
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +85 -31
- data/lib/active_record/insert_all.rb +148 -32
- data/lib/active_record/integration.rb +14 -10
- data/lib/active_record/internal_metadata.rb +123 -23
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +43 -27
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +41 -29
- 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 +10 -10
- data/lib/active_record/middleware/database_selector.rb +23 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +113 -16
- data/lib/active_record/migration/compatibility.rb +235 -46
- 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/join_table.rb +1 -1
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +374 -177
- data/lib/active_record/model_schema.rb +143 -159
- data/lib/active_record/nested_attributes.rb +48 -21
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +282 -283
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +189 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +44 -9
- data/lib/active_record/railtie.rb +234 -71
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +189 -256
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +41 -3
- data/lib/active_record/reflection.rb +325 -103
- data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +300 -111
- data/lib/active_record/relation/delegation.rb +33 -22
- data/lib/active_record/relation/finder_methods.rb +123 -52
- data/lib/active_record/relation/merger.rb +26 -19
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
- 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 +29 -22
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +842 -150
- data/lib/active_record/relation/record_fetch_warning.rb +10 -9
- data/lib/active_record/relation/spawn_methods.rb +7 -6
- data/lib/active_record/relation/where_clause.rb +15 -36
- data/lib/active_record/relation.rb +736 -145
- data/lib/active_record/result.rb +67 -54
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +84 -34
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +90 -31
- data/lib/active_record/schema_migration.rb +74 -23
- data/lib/active_record/scoping/default.rb +72 -15
- data/lib/active_record/scoping/named.rb +5 -13
- data/lib/active_record/scoping.rb +65 -34
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +6 -1
- data/lib/active_record/signed_id.rb +30 -9
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +10 -10
- data/lib/active_record/suppressor.rb +13 -15
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +277 -149
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
- data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +173 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +32 -19
- 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 +118 -41
- data/lib/active_record/translation.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +32 -14
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +9 -7
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- 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 +13 -7
- 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 +64 -15
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +444 -32
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +0 -8
- 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/crud.rb +28 -22
- data/lib/arel/delete_manager.rb +18 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/casted.rb +1 -1
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +7 -2
- data/lib/arel/predications.rb +14 -4
- data/lib/arel/select_manager.rb +11 -5
- data/lib/arel/table.rb +9 -6
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +20 -5
- data/lib/arel/visitors/dot.rb +81 -90
- data/lib/arel/visitors/mysql.rb +23 -5
- data/lib/arel/visitors/postgresql.rb +1 -22
- data/lib/arel/visitors/to_sql.rb +170 -36
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +23 -4
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- 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
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +100 -14
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -67
@@ -3,7 +3,7 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
5
|
# = Active Record Has One Association
|
6
|
-
class HasOneAssociation < SingularAssociation
|
6
|
+
class HasOneAssociation < SingularAssociation # :nodoc:
|
7
7
|
include ForeignAssociation
|
8
8
|
|
9
9
|
def handle_dependency
|
@@ -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,
|
@@ -70,7 +75,7 @@ module ActiveRecord
|
|
70
75
|
if save && !record.save
|
71
76
|
nullify_owner_attributes(record)
|
72
77
|
set_owner_attributes(target) if target
|
73
|
-
raise RecordNotSaved
|
78
|
+
raise RecordNotSaved.new("Failed to save the new associated #{reflection.name}.", record)
|
74
79
|
end
|
75
80
|
end
|
76
81
|
end
|
@@ -102,19 +107,24 @@ module ActiveRecord
|
|
102
107
|
|
103
108
|
if target.persisted? && owner.persisted? && !target.save
|
104
109
|
set_owner_attributes(target)
|
105
|
-
raise RecordNotSaved
|
106
|
-
|
110
|
+
raise RecordNotSaved.new(
|
111
|
+
"Failed to remove the existing associated #{reflection.name}. " \
|
112
|
+
"The record failed to save after its foreign key was set to nil.",
|
113
|
+
target
|
114
|
+
)
|
107
115
|
end
|
108
116
|
end
|
109
117
|
end
|
110
118
|
|
111
119
|
def nullify_owner_attributes(record)
|
112
|
-
|
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
|
113
123
|
end
|
114
124
|
|
115
|
-
def transaction_if(value)
|
125
|
+
def transaction_if(value, &block)
|
116
126
|
if value
|
117
|
-
reflection.klass.transaction
|
127
|
+
reflection.klass.transaction(&block)
|
118
128
|
else
|
119
129
|
yield
|
120
130
|
end
|
@@ -122,7 +132,7 @@ module ActiveRecord
|
|
122
132
|
|
123
133
|
def _create_record(attributes, raise_error = false, &block)
|
124
134
|
unless owner.persisted?
|
125
|
-
raise ActiveRecord::RecordNotSaved
|
135
|
+
raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner)
|
126
136
|
end
|
127
137
|
|
128
138
|
super
|
@@ -37,39 +37,41 @@ module ActiveRecord
|
|
37
37
|
chain << [reflection, table]
|
38
38
|
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
base_klass.with_connection do |connection|
|
41
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
42
|
+
# more sense in this context), so we reverse
|
43
|
+
chain.reverse_each do |reflection, table|
|
44
|
+
klass = reflection.klass
|
44
45
|
|
45
|
-
|
46
|
+
scope = reflection.join_scope(table, foreign_table, foreign_klass)
|
46
47
|
|
47
|
-
|
48
|
-
|
48
|
+
unless scope.references_values.empty?
|
49
|
+
associations = scope.eager_load_values | scope.includes_values
|
49
50
|
|
50
|
-
|
51
|
-
|
51
|
+
unless associations.empty?
|
52
|
+
scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
|
53
|
+
end
|
52
54
|
end
|
53
|
-
end
|
54
55
|
|
55
|
-
|
56
|
-
|
56
|
+
arel = scope.arel(alias_tracker.aliases)
|
57
|
+
nodes = arel.constraints.first
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
59
|
+
if nodes.is_a?(Arel::Nodes::And)
|
60
|
+
others = nodes.children.extract! do |node|
|
61
|
+
!Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
|
62
|
+
end
|
61
63
|
end
|
62
|
-
end
|
63
64
|
|
64
|
-
|
65
|
+
joins << join_type.new(table, Arel::Nodes::On.new(nodes))
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
if others && !others.empty?
|
68
|
+
joins.concat arel.join_sources
|
69
|
+
append_constraints(connection, joins.last, others)
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
-
|
72
|
+
# The current table in this iteration becomes the foreign table in the next
|
73
|
+
foreign_table, foreign_klass = table, klass
|
74
|
+
end
|
73
75
|
end
|
74
76
|
|
75
77
|
joins
|
@@ -88,10 +90,10 @@ module ActiveRecord
|
|
88
90
|
end
|
89
91
|
|
90
92
|
private
|
91
|
-
def append_constraints(join, constraints)
|
93
|
+
def append_constraints(connection, join, constraints)
|
92
94
|
if join.is_a?(Arel::Nodes::StringJoin)
|
93
95
|
join_string = Arel::Nodes::And.new(constraints.unshift join.left)
|
94
|
-
join.left = Arel.sql(
|
96
|
+
join.left = Arel.sql(connection.visitor.compile(join_string))
|
95
97
|
else
|
96
98
|
right = join.right
|
97
99
|
right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
|
@@ -3,8 +3,12 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
5
|
class JoinDependency # :nodoc:
|
6
|
-
|
7
|
-
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
|
8
|
+
eager_autoload do
|
9
|
+
autoload :JoinBase
|
10
|
+
autoload :JoinAssociation
|
11
|
+
end
|
8
12
|
|
9
13
|
class Aliases # :nodoc:
|
10
14
|
def initialize(tables)
|
@@ -248,35 +252,39 @@ module ActiveRecord
|
|
248
252
|
next
|
249
253
|
end
|
250
254
|
|
251
|
-
|
252
|
-
|
253
|
-
|
255
|
+
if node.primary_key
|
256
|
+
keys = Array(node.primary_key).map { |column| aliases.column_alias(node, column) }
|
257
|
+
id = keys.map { |key| row[key] }
|
258
|
+
else
|
259
|
+
keys = Array(node.reflection.join_primary_key).map { |column| aliases.column_alias(node, column.to_s) }
|
260
|
+
id = keys.map { nil } # Avoid id-based model caching.
|
261
|
+
end
|
262
|
+
|
263
|
+
if keys.any? { |key| row[key].nil? }
|
254
264
|
nil_association = ar_parent.association(node.reflection.name)
|
255
265
|
nil_association.loaded!
|
256
266
|
next
|
257
267
|
end
|
258
268
|
|
259
|
-
model = seen[ar_parent][node][id]
|
260
|
-
|
261
|
-
if model
|
262
|
-
construct(model, node, row, seen, model_cache, strict_loading_value)
|
263
|
-
else
|
269
|
+
unless model = seen[ar_parent][node][id]
|
264
270
|
model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
|
265
|
-
|
266
|
-
seen[ar_parent][node][id] = model
|
267
|
-
construct(model, node, row, seen, model_cache, strict_loading_value)
|
271
|
+
seen[ar_parent][node][id] = model if id
|
268
272
|
end
|
273
|
+
|
274
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
269
275
|
end
|
270
276
|
end
|
271
277
|
|
272
278
|
def construct_model(record, node, row, model_cache, id, strict_loading_value)
|
273
279
|
other = record.association(node.reflection.name)
|
274
280
|
|
275
|
-
model = model_cache[node][id]
|
276
|
-
node.instantiate(row, aliases.column_aliases(node)) do |m|
|
281
|
+
unless model = model_cache[node][id]
|
282
|
+
model = node.instantiate(row, aliases.column_aliases(node)) do |m|
|
277
283
|
m.strict_loading! if strict_loading_value
|
278
284
|
other.set_inverse_instance(m)
|
279
285
|
end
|
286
|
+
model_cache[node][id] = model if id
|
287
|
+
end
|
280
288
|
|
281
289
|
if node.reflection.collection?
|
282
290
|
other.target.push(model)
|
@@ -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,19 +1,141 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :enddoc:
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
module Associations
|
5
7
|
class Preloader
|
6
|
-
class Association
|
7
|
-
|
8
|
+
class Association # :nodoc:
|
9
|
+
class LoaderQuery
|
10
|
+
attr_reader :scope, :association_key_name
|
11
|
+
|
12
|
+
def initialize(scope, association_key_name)
|
13
|
+
@scope = scope
|
14
|
+
@association_key_name = association_key_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def eql?(other)
|
18
|
+
association_key_name == other.association_key_name &&
|
19
|
+
scope.table_name == other.scope.table_name &&
|
20
|
+
scope.connection_specification_name == other.scope.connection_specification_name &&
|
21
|
+
scope.values_for_queries == other.scope.values_for_queries
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash
|
25
|
+
[association_key_name, scope.table_name, scope.connection_specification_name, scope.values_for_queries].hash
|
26
|
+
end
|
27
|
+
|
28
|
+
def records_for(loaders)
|
29
|
+
LoaderRecords.new(loaders, self).records
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_records_in_batch(loaders)
|
33
|
+
raw_records = records_for(loaders)
|
34
|
+
|
35
|
+
loaders.each do |loader|
|
36
|
+
loader.load_records(raw_records)
|
37
|
+
loader.run
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_records_for_keys(keys, &block)
|
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)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class LoaderRecords
|
61
|
+
def initialize(loaders, loader_query)
|
62
|
+
@loader_query = loader_query
|
63
|
+
@loaders = loaders
|
64
|
+
@keys_to_load = Set.new
|
65
|
+
@already_loaded_records_by_key = {}
|
66
|
+
|
67
|
+
populate_keys_to_load_and_already_loaded_records
|
68
|
+
end
|
69
|
+
|
70
|
+
def records
|
71
|
+
load_records + already_loaded_records
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
attr_reader :loader_query, :loaders, :keys_to_load, :already_loaded_records_by_key
|
76
|
+
|
77
|
+
def populate_keys_to_load_and_already_loaded_records
|
78
|
+
loaders.each do |loader|
|
79
|
+
loader.owners_by_key.each do |key, owners|
|
80
|
+
if loaded_owner = owners.find { |owner| loader.loaded?(owner) }
|
81
|
+
already_loaded_records_by_key[key] = loader.target_for(loaded_owner)
|
82
|
+
else
|
83
|
+
keys_to_load << key
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
@keys_to_load.subtract(already_loaded_records_by_key.keys)
|
89
|
+
end
|
90
|
+
|
91
|
+
def load_records
|
92
|
+
loader_query.load_records_for_keys(keys_to_load) do |record|
|
93
|
+
loaders.each { |l| l.set_inverse(record) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def already_loaded_records
|
98
|
+
already_loaded_records_by_key.values.flatten
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
attr_reader :klass
|
103
|
+
|
104
|
+
def initialize(klass, owners, reflection, preload_scope, reflection_scope, associate_by_default)
|
8
105
|
@klass = klass
|
9
106
|
@owners = owners.uniq(&:__id__)
|
10
107
|
@reflection = reflection
|
11
108
|
@preload_scope = preload_scope
|
109
|
+
@reflection_scope = reflection_scope
|
12
110
|
@associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
|
13
111
|
@model = owners.first && owners.first.class
|
112
|
+
@run = false
|
113
|
+
end
|
114
|
+
|
115
|
+
def table_name
|
116
|
+
@klass.table_name
|
117
|
+
end
|
118
|
+
|
119
|
+
def future_classes
|
120
|
+
if run?
|
121
|
+
[]
|
122
|
+
else
|
123
|
+
[@klass]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def runnable_loaders
|
128
|
+
[self]
|
129
|
+
end
|
130
|
+
|
131
|
+
def run?
|
132
|
+
@run
|
14
133
|
end
|
15
134
|
|
16
135
|
def run
|
136
|
+
return self if run?
|
137
|
+
@run = true
|
138
|
+
|
17
139
|
records = records_by_owner
|
18
140
|
|
19
141
|
owners.each do |owner|
|
@@ -35,35 +157,85 @@ module ActiveRecord
|
|
35
157
|
@preloaded_records
|
36
158
|
end
|
37
159
|
|
38
|
-
|
39
|
-
|
160
|
+
# The name of the key on the associated records
|
161
|
+
def association_key_name
|
162
|
+
reflection.join_primary_key(klass)
|
163
|
+
end
|
40
164
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@records_by_owner = {}.compare_by_identity
|
45
|
-
raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
|
165
|
+
def loader_query
|
166
|
+
LoaderQuery.new(scope, association_key_name)
|
167
|
+
end
|
46
168
|
|
47
|
-
|
48
|
-
|
169
|
+
def owners_by_key
|
170
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
171
|
+
key = derive_key(owner, owner_key_name)
|
172
|
+
(result[key] ||= []) << owner if key
|
173
|
+
end
|
174
|
+
end
|
49
175
|
|
50
|
-
|
51
|
-
|
176
|
+
def loaded?(owner)
|
177
|
+
owner.association(reflection.name).loaded?
|
178
|
+
end
|
52
179
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
end
|
180
|
+
def target_for(owner)
|
181
|
+
Array.wrap(owner.association(reflection.name).target)
|
182
|
+
end
|
58
183
|
|
59
|
-
|
184
|
+
def scope
|
185
|
+
@scope ||= build_scope
|
186
|
+
end
|
187
|
+
|
188
|
+
def set_inverse(record)
|
189
|
+
if owners = owners_by_key[derive_key(record, association_key_name)]
|
190
|
+
# Processing only the first owner
|
191
|
+
# because the record is modified but not an owner
|
192
|
+
association = owners.first.association(reflection.name)
|
193
|
+
association.set_inverse_instance(record)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def load_records(raw_records = nil)
|
198
|
+
# owners can be duplicated when a relation has a collection association join
|
199
|
+
# #compare_by_identity makes such owners different hash keys
|
200
|
+
@records_by_owner = {}.compare_by_identity
|
201
|
+
raw_records ||= loader_query.records_for([self])
|
202
|
+
@preloaded_records = raw_records.select do |record|
|
203
|
+
assignments = false
|
204
|
+
|
205
|
+
owners_by_key[derive_key(record, association_key_name)]&.each do |owner|
|
206
|
+
entries = (@records_by_owner[owner] ||= [])
|
207
|
+
|
208
|
+
if reflection.collection? || entries.empty?
|
209
|
+
entries << record
|
210
|
+
assignments = true
|
211
|
+
end
|
60
212
|
end
|
213
|
+
|
214
|
+
assignments
|
61
215
|
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def associate_records_from_unscoped(unscoped_records)
|
219
|
+
return if unscoped_records.nil? || unscoped_records.empty?
|
220
|
+
return if !reflection_scope.empty_scope?
|
221
|
+
return if preload_scope && !preload_scope.empty_scope?
|
222
|
+
return if reflection.collection?
|
223
|
+
|
224
|
+
unscoped_records.select { |r| r[association_key_name].present? }.each do |record|
|
225
|
+
owners = owners_by_key[derive_key(record, association_key_name)]
|
226
|
+
owners&.each_with_index do |owner, i|
|
227
|
+
association = owner.association(reflection.name)
|
228
|
+
association.target = record
|
62
229
|
|
63
|
-
|
64
|
-
|
65
|
-
|
230
|
+
if i == 0 # Set inverse on first owner
|
231
|
+
association.set_inverse_instance(record)
|
232
|
+
end
|
233
|
+
end
|
66
234
|
end
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
attr_reader :owners, :reflection, :preload_scope, :model
|
67
239
|
|
68
240
|
# The name of the key on the model which declares the association
|
69
241
|
def owner_key_name
|
@@ -71,25 +243,18 @@ module ActiveRecord
|
|
71
243
|
end
|
72
244
|
|
73
245
|
def associate_records_to_owner(owner, records)
|
246
|
+
return if loaded?(owner)
|
247
|
+
|
74
248
|
association = owner.association(reflection.name)
|
249
|
+
|
75
250
|
if reflection.collection?
|
76
|
-
association.target
|
251
|
+
not_persisted_records = association.target.reject(&:persisted?)
|
252
|
+
association.target = records + not_persisted_records
|
77
253
|
else
|
78
254
|
association.target = records.first
|
79
255
|
end
|
80
256
|
end
|
81
257
|
|
82
|
-
def owner_keys
|
83
|
-
@owner_keys ||= owners_by_key.keys
|
84
|
-
end
|
85
|
-
|
86
|
-
def owners_by_key
|
87
|
-
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
88
|
-
key = convert_key(owner[owner_key_name])
|
89
|
-
(result[key] ||= []) << owner if key
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
258
|
def key_conversion_required?
|
94
259
|
unless defined?(@key_conversion_required)
|
95
260
|
@key_conversion_required = (association_key_type != owner_key_type)
|
@@ -98,6 +263,14 @@ module ActiveRecord
|
|
98
263
|
@key_conversion_required
|
99
264
|
end
|
100
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
|
+
|
101
274
|
def convert_key(key)
|
102
275
|
if key_conversion_required?
|
103
276
|
key.to_s
|
@@ -114,20 +287,6 @@ module ActiveRecord
|
|
114
287
|
@model.type_for_attribute(owner_key_name).type
|
115
288
|
end
|
116
289
|
|
117
|
-
def records_for(ids)
|
118
|
-
scope.where(association_key_name => ids).load do |record|
|
119
|
-
# Processing only the first owner
|
120
|
-
# because the record is modified but not an owner
|
121
|
-
owner = owners_by_key[convert_key(record[association_key_name])].first
|
122
|
-
association = owner.association(reflection.name)
|
123
|
-
association.set_inverse_instance(record)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def scope
|
128
|
-
@scope ||= build_scope
|
129
|
-
end
|
130
|
-
|
131
290
|
def reflection_scope
|
132
291
|
@reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(klass.unscoped, &:merge!)
|
133
292
|
end
|
@@ -145,11 +304,11 @@ module ActiveRecord
|
|
145
304
|
scope.merge!(preload_scope)
|
146
305
|
end
|
147
306
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
307
|
+
cascade_strict_loading(scope)
|
308
|
+
end
|
309
|
+
|
310
|
+
def cascade_strict_loading(scope)
|
311
|
+
preload_scope&.strict_loading_value ? scope.strict_loading : scope
|
153
312
|
end
|
154
313
|
end
|
155
314
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
class Preloader
|
6
|
+
class Batch # :nodoc:
|
7
|
+
def initialize(preloaders, available_records:)
|
8
|
+
@preloaders = preloaders.reject(&:empty?)
|
9
|
+
@available_records = available_records.flatten.group_by { |r| r.class.base_class }
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
branches = @preloaders.flat_map(&:branches)
|
14
|
+
until branches.empty?
|
15
|
+
loaders = branches.flat_map(&:runnable_loaders)
|
16
|
+
|
17
|
+
loaders.each { |loader| loader.associate_records_from_unscoped(@available_records[loader.klass.base_class]) }
|
18
|
+
|
19
|
+
if loaders.any?
|
20
|
+
future_tables = branches.flat_map do |branch|
|
21
|
+
branch.future_classes - branch.runnable_loaders.map(&:klass)
|
22
|
+
end.map(&:table_name).uniq
|
23
|
+
|
24
|
+
target_loaders = loaders.reject { |l| future_tables.include?(l.table_name) }
|
25
|
+
target_loaders = loaders if target_loaders.empty?
|
26
|
+
|
27
|
+
group_and_load_similar(target_loaders)
|
28
|
+
target_loaders.each(&:run)
|
29
|
+
end
|
30
|
+
|
31
|
+
finished, in_progress = branches.partition(&:done?)
|
32
|
+
|
33
|
+
branches = in_progress + finished.flat_map(&:children)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
attr_reader :loaders
|
39
|
+
|
40
|
+
def group_and_load_similar(loaders)
|
41
|
+
loaders.grep_v(ThroughAssociation).group_by(&:loader_query).each_pair do |query, similar_loaders|
|
42
|
+
query.load_records_in_batch(similar_loaders)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|