activerecord 6.0.0 → 7.2.3
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 +996 -594
- data/MIT-LICENSE +1 -1
- data/README.rdoc +34 -34
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +22 -20
- data/lib/active_record/association_relation.rb +22 -12
- data/lib/active_record/associations/alias_tracker.rb +41 -30
- data/lib/active_record/associations/association.rb +106 -41
- data/lib/active_record/associations/association_scope.rb +30 -21
- data/lib/active_record/associations/belongs_to_association.rb +69 -14
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
- data/lib/active_record/associations/builder/association.rb +39 -6
- data/lib/active_record/associations/builder/belongs_to.rb +47 -17
- data/lib/active_record/associations/builder/collection_association.rb +14 -6
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
- data/lib/active_record/associations/builder/has_many.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +13 -16
- data/lib/active_record/associations/builder/singular_association.rb +7 -3
- data/lib/active_record/associations/collection_association.rb +90 -53
- data/lib/active_record/associations/collection_proxy.rb +54 -19
- 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 +21 -1
- data/lib/active_record/associations/has_many_association.rb +41 -10
- data/lib/active_record/associations/has_many_through_association.rb +29 -12
- data/lib/active_record/associations/has_one_association.rb +33 -9
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/join_dependency.rb +97 -54
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +237 -54
- 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 +51 -17
- data/lib/active_record/associations/preloader.rb +55 -121
- data/lib/active_record/associations/singular_association.rb +16 -4
- data/lib/active_record/associations/through_association.rb +26 -15
- data/lib/active_record/associations.rb +454 -440
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +11 -14
- data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +75 -34
- data/lib/active_record/attribute_methods/primary_key.rb +53 -31
- data/lib/active_record/attribute_methods/query.rb +31 -22
- data/lib/active_record/attribute_methods/read.rb +16 -17
- data/lib/active_record/attribute_methods/serialization.rb +177 -35
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
- data/lib/active_record/attribute_methods/write.rb +16 -28
- data/lib/active_record/attribute_methods.rb +227 -100
- data/lib/active_record/attributes.rb +94 -56
- data/lib/active_record/autosave_association.rb +119 -73
- data/lib/active_record/base.rb +31 -21
- data/lib/active_record/callbacks.rb +168 -55
- 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 -25
- 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 +367 -565
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
- data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
- data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
- data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
- data/lib/active_record/connection_adapters/column.rb +28 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
- data/lib/active_record/connection_adapters/pool_config.rb +83 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- 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 +23 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
- data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
- 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 +102 -24
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
- 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 +176 -0
- data/lib/active_record/connection_handling.rb +243 -115
- data/lib/active_record/core.rb +481 -199
- data/lib/active_record/counter_cache.rb +69 -32
- data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
- data/lib/active_record/database_configurations/database_config.rb +77 -10
- data/lib/active_record/database_configurations/hash_config.rb +148 -26
- data/lib/active_record/database_configurations/url_config.rb +44 -45
- data/lib/active_record/database_configurations.rb +190 -114
- data/lib/active_record/delegated_type.rb +279 -0
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +38 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +5 -6
- 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 +171 -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 +58 -0
- data/lib/active_record/enum.rb +224 -73
- data/lib/active_record/errors.rb +254 -36
- data/lib/active_record/explain.rb +30 -17
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +2 -2
- data/lib/active_record/fixture_set/file.rb +22 -15
- data/lib/active_record/fixture_set/model_metadata.rb +15 -6
- data/lib/active_record/fixture_set/render_context.rb +3 -1
- data/lib/active_record/fixture_set/table_row.rb +88 -16
- data/lib/active_record/fixture_set/table_rows.rb +4 -5
- data/lib/active_record/fixtures.rb +229 -116
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +121 -48
- data/lib/active_record/insert_all.rb +178 -29
- data/lib/active_record/integration.rb +16 -14
- data/lib/active_record/internal_metadata.rb +132 -21
- data/lib/active_record/legacy_yaml_adapter.rb +3 -36
- data/lib/active_record/locking/optimistic.rb +64 -33
- data/lib/active_record/locking/pessimistic.rb +21 -8
- data/lib/active_record/log_subscriber.rb +61 -30
- 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/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
- data/lib/active_record/middleware/database_selector.rb +25 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +160 -55
- data/lib/active_record/migration/compatibility.rb +286 -43
- 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 -2
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +421 -193
- data/lib/active_record/model_schema.rb +217 -125
- data/lib/active_record/nested_attributes.rb +62 -27
- data/lib/active_record/no_touching.rb +4 -4
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +322 -319
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -15
- data/lib/active_record/query_logs.rb +193 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +54 -14
- data/lib/active_record/railtie.rb +250 -72
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +312 -197
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +45 -3
- data/lib/active_record/reflection.rb +389 -146
- data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
- data/lib/active_record/relation/batches.rb +214 -73
- data/lib/active_record/relation/calculations.rb +379 -124
- data/lib/active_record/relation/delegation.rb +36 -23
- data/lib/active_record/relation/finder_methods.rb +159 -49
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +41 -33
- data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +79 -53
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +1156 -279
- data/lib/active_record/relation/record_fetch_warning.rb +12 -11
- data/lib/active_record/relation/spawn_methods.rb +10 -9
- data/lib/active_record/relation/where_clause.rb +100 -66
- data/lib/active_record/relation.rb +829 -194
- data/lib/active_record/result.rb +76 -56
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +86 -47
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +140 -33
- data/lib/active_record/schema_migration.rb +74 -29
- data/lib/active_record/scoping/default.rb +73 -19
- data/lib/active_record/scoping/named.rb +10 -28
- data/lib/active_record/scoping.rb +65 -35
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +34 -8
- data/lib/active_record/serialization.rb +11 -4
- data/lib/active_record/signed_id.rb +138 -0
- data/lib/active_record/statement_cache.rb +26 -10
- data/lib/active_record/store.rb +19 -14
- data/lib/active_record/suppressor.rb +15 -17
- data/lib/active_record/table_metadata.rb +46 -36
- data/lib/active_record/tasks/database_tasks.rb +371 -205
- data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
- data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +189 -104
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +35 -25
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +31 -27
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +131 -99
- data/lib/active_record/translation.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +33 -18
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +11 -6
- data/lib/active_record/type/time.rb +14 -0
- data/lib/active_record/type/type_map.rb +17 -21
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +7 -2
- data/lib/active_record/type_caster/connection.rb +4 -5
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +13 -8
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +88 -18
- data/lib/active_record/validations.rb +15 -8
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +446 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +4 -8
- data/lib/arel/collectors/bind.rb +8 -1
- data/lib/arel/collectors/composite.rb +15 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/crud.rb +30 -22
- data/lib/arel/delete_manager.rb +23 -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 +82 -9
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/casted.rb +22 -10
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +14 -13
- data/lib/arel/nodes/equality.rb +6 -9
- 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/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/join_source.rb +1 -1
- 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 +122 -11
- data/lib/arel/nodes/ordering.rb +27 -0
- 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 +16 -0
- data/lib/arel/nodes/table_alias.rb +11 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes/update_statement.rb +11 -4
- data/lib/arel/nodes.rb +10 -3
- data/lib/arel/predications.rb +31 -28
- data/lib/arel/select_manager.rb +18 -9
- data/lib/arel/table.rb +21 -10
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +25 -5
- data/lib/arel/visitors/dot.rb +94 -90
- data/lib/arel/visitors/mysql.rb +34 -6
- data/lib/arel/visitors/postgresql.rb +5 -16
- data/lib/arel/visitors/sqlite.rb +25 -1
- data/lib/arel/visitors/to_sql.rb +227 -81
- data/lib/arel/visitors/visitor.rb +2 -3
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel.rb +37 -15
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
- data/lib/rails/generators/active_record/migration.rb +9 -3
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- 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 +117 -30
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/null_relation.rb +0 -68
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -204
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -157
- data/lib/arel/visitors/oracle.rb +0 -159
- data/lib/arel/visitors/oracle12.rb +0 -66
- data/lib/arel/visitors/where_sql.rb +0 -23
|
@@ -6,12 +6,13 @@ module ActiveRecord
|
|
|
6
6
|
# See ActiveRecord::Attributes::ClassMethods for documentation
|
|
7
7
|
module Attributes
|
|
8
8
|
extend ActiveSupport::Concern
|
|
9
|
+
include ActiveModel::AttributeRegistration
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
|
|
12
|
-
end
|
|
13
|
-
|
|
11
|
+
# = Active Record \Attributes
|
|
14
12
|
module ClassMethods
|
|
13
|
+
# :method: attribute
|
|
14
|
+
# :call-seq: attribute(name, cast_type = nil, **options)
|
|
15
|
+
#
|
|
15
16
|
# Defines an attribute with a type on this model. It will override the
|
|
16
17
|
# type of existing attributes if needed. This allows control over how
|
|
17
18
|
# values are converted to and from SQL when assigned to a model. It also
|
|
@@ -20,26 +21,34 @@ module ActiveRecord
|
|
|
20
21
|
# your domain objects across much of Active Record, without having to
|
|
21
22
|
# rely on implementation details or monkey patching.
|
|
22
23
|
#
|
|
23
|
-
#
|
|
24
|
-
# column which this will persist to.
|
|
24
|
+
# ==== Parameters
|
|
25
25
|
#
|
|
26
|
-
# +
|
|
27
|
-
#
|
|
28
|
-
#
|
|
26
|
+
# [+name+]
|
|
27
|
+
# The name of the methods to define attribute methods for, and the
|
|
28
|
+
# column which this will persist to.
|
|
29
29
|
#
|
|
30
|
-
#
|
|
30
|
+
# [+cast_type+]
|
|
31
|
+
# A symbol such as +:string+ or +:integer+, or a type object to be used
|
|
32
|
+
# for this attribute. If this parameter is not passed, the previously
|
|
33
|
+
# defined type (if any) will be used. Otherwise, the type will be
|
|
34
|
+
# ActiveModel::Type::Value. See the examples below for more information
|
|
35
|
+
# about providing custom type objects.
|
|
31
36
|
#
|
|
32
|
-
#
|
|
37
|
+
# ==== Options
|
|
33
38
|
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
39
|
+
# [+:default+]
|
|
40
|
+
# The default value to use when no value is provided. If this option is
|
|
41
|
+
# not passed, the previously defined default value (if any) on the
|
|
42
|
+
# superclass or in the schema will be used. Otherwise, the default will
|
|
43
|
+
# be +nil+.
|
|
37
44
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
45
|
+
# [+:array+]
|
|
46
|
+
# (PostgreSQL only) Specifies that the type should be an array. See the
|
|
47
|
+
# examples below.
|
|
40
48
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
49
|
+
# [+:range+]
|
|
50
|
+
# (PostgreSQL only) Specifies that the type should be a range. See the
|
|
51
|
+
# examples below.
|
|
43
52
|
#
|
|
44
53
|
# When using a symbol for +cast_type+, extra options are forwarded to the
|
|
45
54
|
# constructor of the type object.
|
|
@@ -134,7 +143,7 @@ module ActiveRecord
|
|
|
134
143
|
# expected API. It is recommended that your type objects inherit from an
|
|
135
144
|
# existing type, or from ActiveRecord::Type::Value
|
|
136
145
|
#
|
|
137
|
-
# class
|
|
146
|
+
# class PriceType < ActiveRecord::Type::Integer
|
|
138
147
|
# def cast(value)
|
|
139
148
|
# if !value.kind_of?(Numeric) && value.include?('$')
|
|
140
149
|
# price_in_dollars = value.gsub(/\$/, '').to_f
|
|
@@ -146,11 +155,11 @@ module ActiveRecord
|
|
|
146
155
|
# end
|
|
147
156
|
#
|
|
148
157
|
# # config/initializers/types.rb
|
|
149
|
-
# ActiveRecord::Type.register(:
|
|
158
|
+
# ActiveRecord::Type.register(:price, PriceType)
|
|
150
159
|
#
|
|
151
160
|
# # app/models/store_listing.rb
|
|
152
161
|
# class StoreListing < ActiveRecord::Base
|
|
153
|
-
# attribute :price_in_cents, :
|
|
162
|
+
# attribute :price_in_cents, :price
|
|
154
163
|
# end
|
|
155
164
|
#
|
|
156
165
|
# store_listing = StoreListing.new(price_in_cents: '$10.00')
|
|
@@ -170,13 +179,13 @@ module ActiveRecord
|
|
|
170
179
|
# class Money < Struct.new(:amount, :currency)
|
|
171
180
|
# end
|
|
172
181
|
#
|
|
173
|
-
# class
|
|
182
|
+
# class PriceType < ActiveRecord::Type::Value
|
|
174
183
|
# def initialize(currency_converter:)
|
|
175
184
|
# @currency_converter = currency_converter
|
|
176
185
|
# end
|
|
177
186
|
#
|
|
178
|
-
# # value will be the result of
|
|
179
|
-
# #
|
|
187
|
+
# # value will be the result of #deserialize or
|
|
188
|
+
# # #cast. Assumed to be an instance of Money in
|
|
180
189
|
# # this case.
|
|
181
190
|
# def serialize(value)
|
|
182
191
|
# value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
|
|
@@ -185,19 +194,19 @@ module ActiveRecord
|
|
|
185
194
|
# end
|
|
186
195
|
#
|
|
187
196
|
# # config/initializers/types.rb
|
|
188
|
-
# ActiveRecord::Type.register(:
|
|
197
|
+
# ActiveRecord::Type.register(:price, PriceType)
|
|
189
198
|
#
|
|
190
199
|
# # app/models/product.rb
|
|
191
200
|
# class Product < ActiveRecord::Base
|
|
192
201
|
# currency_converter = ConversionRatesFromTheInternet.new
|
|
193
|
-
# attribute :price_in_bitcoins, :
|
|
202
|
+
# attribute :price_in_bitcoins, :price, currency_converter: currency_converter
|
|
194
203
|
# end
|
|
195
204
|
#
|
|
196
205
|
# Product.where(price_in_bitcoins: Money.new(5, "USD"))
|
|
197
|
-
# #
|
|
206
|
+
# # SELECT * FROM products WHERE price_in_bitcoins = 0.02230
|
|
198
207
|
#
|
|
199
208
|
# Product.where(price_in_bitcoins: Money.new(5, "GBP"))
|
|
200
|
-
# #
|
|
209
|
+
# # SELECT * FROM products WHERE price_in_bitcoins = 0.03412
|
|
201
210
|
#
|
|
202
211
|
# ==== Dirty Tracking
|
|
203
212
|
#
|
|
@@ -205,34 +214,31 @@ module ActiveRecord
|
|
|
205
214
|
# tracking is performed. The methods +changed?+ and +changed_in_place?+
|
|
206
215
|
# will be called from ActiveModel::Dirty. See the documentation for those
|
|
207
216
|
# methods in ActiveModel::Type::Value for more details.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
self.attributes_to_define_after_schema_loads =
|
|
213
|
-
attributes_to_define_after_schema_loads.merge(
|
|
214
|
-
name => [cast_type, options]
|
|
215
|
-
)
|
|
216
|
-
end
|
|
217
|
+
#
|
|
218
|
+
#--
|
|
219
|
+
# Implemented by ActiveModel::AttributeRegistration#attribute.
|
|
217
220
|
|
|
218
|
-
# This
|
|
219
|
-
#
|
|
220
|
-
# waiting for the schema to load. Automatic schema detection and
|
|
221
|
-
# ClassMethods#attribute both call this under the hood. While this method
|
|
221
|
+
# This API only accepts type objects, and will do its work immediately instead of
|
|
222
|
+
# waiting for the schema to load. While this method
|
|
222
223
|
# is provided so it can be used by plugin authors, application code
|
|
223
224
|
# should probably use ClassMethods#attribute.
|
|
224
225
|
#
|
|
225
|
-
#
|
|
226
|
+
# ==== Parameters
|
|
227
|
+
#
|
|
228
|
+
# [+name+]
|
|
229
|
+
# The name of the attribute being defined. Expected to be a +String+.
|
|
226
230
|
#
|
|
227
|
-
# +cast_type+
|
|
231
|
+
# [+cast_type+]
|
|
232
|
+
# The type object to use for this attribute.
|
|
228
233
|
#
|
|
229
|
-
# +default+
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
# will be
|
|
234
|
+
# [+default+]
|
|
235
|
+
# The default value to use when no value is provided. If this option
|
|
236
|
+
# is not passed, the previous default value (if any) will be used.
|
|
237
|
+
# Otherwise, the default will be +nil+. A proc can also be passed, and
|
|
238
|
+
# will be called once each time a new value is needed.
|
|
233
239
|
#
|
|
234
|
-
# +user_provided_default+
|
|
235
|
-
# +cast+ or +deserialize+.
|
|
240
|
+
# [+user_provided_default+]
|
|
241
|
+
# Whether the default value should be cast using +cast+ or +deserialize+.
|
|
236
242
|
def define_attribute(
|
|
237
243
|
name,
|
|
238
244
|
cast_type,
|
|
@@ -243,19 +249,39 @@ module ActiveRecord
|
|
|
243
249
|
define_default_attribute(name, default, cast_type, from_user: user_provided_default)
|
|
244
250
|
end
|
|
245
251
|
|
|
246
|
-
def
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
252
|
+
def _default_attributes # :nodoc:
|
|
253
|
+
@default_attributes ||= begin
|
|
254
|
+
attributes_hash = with_connection do |connection|
|
|
255
|
+
columns_hash.transform_values do |column|
|
|
256
|
+
ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
|
|
257
|
+
end
|
|
251
258
|
end
|
|
252
259
|
|
|
253
|
-
|
|
260
|
+
attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
|
|
261
|
+
apply_pending_attribute_modifications(attribute_set)
|
|
262
|
+
attribute_set
|
|
254
263
|
end
|
|
255
264
|
end
|
|
256
265
|
|
|
257
|
-
|
|
266
|
+
##
|
|
267
|
+
# :method: type_for_attribute
|
|
268
|
+
# :call-seq: type_for_attribute(attribute_name, &block)
|
|
269
|
+
#
|
|
270
|
+
# See ActiveModel::Attributes::ClassMethods#type_for_attribute.
|
|
271
|
+
#
|
|
272
|
+
# This method will access the database and load the model's schema if
|
|
273
|
+
# necessary.
|
|
274
|
+
#--
|
|
275
|
+
# Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
|
|
276
|
+
|
|
277
|
+
##
|
|
278
|
+
protected
|
|
279
|
+
def reload_schema_from_cache(*)
|
|
280
|
+
reset_default_attributes!
|
|
281
|
+
super
|
|
282
|
+
end
|
|
258
283
|
|
|
284
|
+
private
|
|
259
285
|
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
|
260
286
|
private_constant :NO_DEFAULT_PROVIDED
|
|
261
287
|
|
|
@@ -274,6 +300,18 @@ module ActiveRecord
|
|
|
274
300
|
end
|
|
275
301
|
_default_attributes[name] = default_attribute
|
|
276
302
|
end
|
|
303
|
+
|
|
304
|
+
def reset_default_attributes
|
|
305
|
+
reload_schema_from_cache
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def resolve_type_name(name, **options)
|
|
309
|
+
Type.lookup(name, **options, adapter: Type.adapter_name_from(self))
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def type_for_column(connection, column)
|
|
313
|
+
hook_attribute_type(column.name, super)
|
|
314
|
+
end
|
|
277
315
|
end
|
|
278
316
|
end
|
|
279
317
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_record/associations/nested_error"
|
|
4
|
+
|
|
3
5
|
module ActiveRecord
|
|
4
6
|
# = Active Record Autosave Association
|
|
5
7
|
#
|
|
@@ -26,12 +28,12 @@ module ActiveRecord
|
|
|
26
28
|
#
|
|
27
29
|
# Child records are validated unless <tt>:validate</tt> is +false+.
|
|
28
30
|
#
|
|
29
|
-
# == Callbacks
|
|
31
|
+
# == \Callbacks
|
|
30
32
|
#
|
|
31
33
|
# Association with autosave option defines several callbacks on your
|
|
32
|
-
# model (before_save, after_create, after_update). Please note that
|
|
34
|
+
# model (around_save, before_save, after_create, after_update). Please note that
|
|
33
35
|
# callbacks are executed in the order they were defined in
|
|
34
|
-
# model. You should avoid modifying the association content
|
|
36
|
+
# model. You should avoid modifying the association content before
|
|
35
37
|
# autosave callbacks are executed. Placing your callbacks after
|
|
36
38
|
# associations is usually a good practice.
|
|
37
39
|
#
|
|
@@ -91,8 +93,9 @@ module ActiveRecord
|
|
|
91
93
|
# post.save # => saves both post and comment
|
|
92
94
|
#
|
|
93
95
|
# post = Post.create(title: 'ruby rocks')
|
|
94
|
-
# post.comments.create(body: 'hello world')
|
|
95
|
-
#
|
|
96
|
+
# comment = post.comments.create(body: 'hello world')
|
|
97
|
+
# comment.body = 'hi everyone'
|
|
98
|
+
# post.save # => saves post, but not comment
|
|
96
99
|
#
|
|
97
100
|
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
|
|
98
101
|
# are new records or not:
|
|
@@ -102,11 +105,10 @@ module ActiveRecord
|
|
|
102
105
|
# end
|
|
103
106
|
#
|
|
104
107
|
# post = Post.create(title: 'ruby rocks')
|
|
105
|
-
# post.comments.create(body: 'hello world')
|
|
106
|
-
#
|
|
108
|
+
# comment = post.comments.create(body: 'hello world')
|
|
109
|
+
# comment.body = 'hi everyone'
|
|
107
110
|
# post.comments.build(body: "good morning.")
|
|
108
|
-
# post.
|
|
109
|
-
# post.save # => saves both post and comments.
|
|
111
|
+
# post.save # => saves post and both comments.
|
|
110
112
|
#
|
|
111
113
|
# Destroying one of the associated models as part of the parent's save action
|
|
112
114
|
# is as simple as marking it for destruction:
|
|
@@ -127,10 +129,18 @@ module ActiveRecord
|
|
|
127
129
|
# Now it _is_ removed from the database:
|
|
128
130
|
#
|
|
129
131
|
# Comment.find_by(id: id).nil? # => true
|
|
132
|
+
#
|
|
133
|
+
# === Caveats
|
|
134
|
+
#
|
|
135
|
+
# Note that autosave will only trigger for already-persisted association records
|
|
136
|
+
# if the records themselves have been changed. This is to protect against
|
|
137
|
+
# <tt>SystemStackError</tt> caused by circular association validations. The one
|
|
138
|
+
# exception is if a custom validation context is used, in which case the validations
|
|
139
|
+
# will always fire on the associated records.
|
|
130
140
|
module AutosaveAssociation
|
|
131
141
|
extend ActiveSupport::Concern
|
|
132
142
|
|
|
133
|
-
module AssociationBuilderExtension
|
|
143
|
+
module AssociationBuilderExtension # :nodoc:
|
|
134
144
|
def self.build(model, reflection)
|
|
135
145
|
model.send(:add_autosave_association_callbacks, reflection)
|
|
136
146
|
end
|
|
@@ -142,14 +152,13 @@ module ActiveRecord
|
|
|
142
152
|
|
|
143
153
|
included do
|
|
144
154
|
Associations::Builder::Association.extensions << AssociationBuilderExtension
|
|
145
|
-
mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
|
|
146
155
|
end
|
|
147
156
|
|
|
148
157
|
module ClassMethods # :nodoc:
|
|
149
158
|
private
|
|
150
|
-
|
|
151
159
|
def define_non_cyclic_method(name, &block)
|
|
152
|
-
return if
|
|
160
|
+
return if method_defined?(name, false)
|
|
161
|
+
|
|
153
162
|
define_method(name) do |*args|
|
|
154
163
|
result = true; @_already_called ||= {}
|
|
155
164
|
# Loop prevention for validation of associations
|
|
@@ -181,15 +190,14 @@ module ActiveRecord
|
|
|
181
190
|
save_method = :"autosave_associated_records_for_#{reflection.name}"
|
|
182
191
|
|
|
183
192
|
if reflection.collection?
|
|
184
|
-
|
|
185
|
-
after_save :after_save_collection_association
|
|
193
|
+
around_save :around_save_collection_association
|
|
186
194
|
|
|
187
195
|
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
|
|
188
196
|
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
|
189
197
|
after_create save_method
|
|
190
198
|
after_update save_method
|
|
191
199
|
elsif reflection.has_one?
|
|
192
|
-
|
|
200
|
+
define_non_cyclic_method(save_method) { save_has_one_association(reflection) }
|
|
193
201
|
# Configures two callbacks instead of a single after_save so that
|
|
194
202
|
# the model may rely on their execution order relative to its
|
|
195
203
|
# own callbacks.
|
|
@@ -267,12 +275,16 @@ module ActiveRecord
|
|
|
267
275
|
end
|
|
268
276
|
|
|
269
277
|
private
|
|
278
|
+
def init_internals
|
|
279
|
+
super
|
|
280
|
+
@_already_called = nil
|
|
281
|
+
end
|
|
270
282
|
|
|
271
283
|
# Returns the record for an association collection that should be validated
|
|
272
284
|
# or saved. If +autosave+ is +false+ only new records will be returned,
|
|
273
285
|
# unless the parent is/was a new record itself.
|
|
274
286
|
def associated_records_to_validate_or_save(association, new_record, autosave)
|
|
275
|
-
if new_record
|
|
287
|
+
if new_record || custom_validation_context?
|
|
276
288
|
association && association.target
|
|
277
289
|
elsif autosave
|
|
278
290
|
association.target.find_all(&:changed_for_autosave?)
|
|
@@ -281,8 +293,9 @@ module ActiveRecord
|
|
|
281
293
|
end
|
|
282
294
|
end
|
|
283
295
|
|
|
284
|
-
#
|
|
285
|
-
# any new ones), and return true if
|
|
296
|
+
# Go through nested autosave associations that are loaded in memory (without loading
|
|
297
|
+
# any new ones), and return true if any are changed for autosave.
|
|
298
|
+
# Returns false if already called to prevent an infinite loop.
|
|
286
299
|
def nested_records_changed_for_autosave?
|
|
287
300
|
@_nested_records_changed_for_autosave_already_called ||= false
|
|
288
301
|
return false if @_nested_records_changed_for_autosave_already_called
|
|
@@ -304,7 +317,7 @@ module ActiveRecord
|
|
|
304
317
|
def validate_single_association(reflection)
|
|
305
318
|
association = association_instance_get(reflection.name)
|
|
306
319
|
record = association && association.reader
|
|
307
|
-
association_valid?(
|
|
320
|
+
association_valid?(association, record) if record && (record.changed_for_autosave? || custom_validation_context?)
|
|
308
321
|
end
|
|
309
322
|
|
|
310
323
|
# Validate the associated records if <tt>:validate</tt> or
|
|
@@ -313,7 +326,7 @@ module ActiveRecord
|
|
|
313
326
|
def validate_collection_association(reflection)
|
|
314
327
|
if association = association_instance_get(reflection.name)
|
|
315
328
|
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
|
|
316
|
-
records.
|
|
329
|
+
records.each { |record| association_valid?(association, record) }
|
|
317
330
|
end
|
|
318
331
|
end
|
|
319
332
|
end
|
|
@@ -321,53 +334,44 @@ module ActiveRecord
|
|
|
321
334
|
# Returns whether or not the association is valid and applies any errors to
|
|
322
335
|
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
|
323
336
|
# enabled records if they're marked_for_destruction? or destroyed.
|
|
324
|
-
def association_valid?(
|
|
325
|
-
return true if record.destroyed? || (
|
|
337
|
+
def association_valid?(association, record)
|
|
338
|
+
return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
|
|
326
339
|
|
|
327
|
-
context = validation_context
|
|
340
|
+
context = validation_context if custom_validation_context?
|
|
341
|
+
return true if record.valid?(context)
|
|
328
342
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
errors[attribute].uniq!
|
|
337
|
-
end
|
|
343
|
+
if context || record.changed_for_autosave?
|
|
344
|
+
associated_errors = record.errors.objects
|
|
345
|
+
else
|
|
346
|
+
# If there are existing invalid records in the DB, we should still be able to reference them.
|
|
347
|
+
# Unless a record (no matter where in the association chain) is invalid and is being changed.
|
|
348
|
+
associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) }
|
|
349
|
+
end
|
|
338
350
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
|
|
351
|
+
if association.options[:autosave]
|
|
352
|
+
return if equal?(record)
|
|
342
353
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
end
|
|
354
|
+
associated_errors.each { |error|
|
|
355
|
+
errors.objects.append(
|
|
356
|
+
Associations::NestedError.new(association, error)
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
elsif associated_errors.any?
|
|
360
|
+
errors.add(association.reflection.name)
|
|
351
361
|
end
|
|
352
|
-
valid
|
|
353
|
-
end
|
|
354
362
|
|
|
355
|
-
|
|
356
|
-
if indexed_attribute
|
|
357
|
-
"#{reflection.name}[#{index}].#{attribute}"
|
|
358
|
-
else
|
|
359
|
-
"#{reflection.name}.#{attribute}"
|
|
360
|
-
end
|
|
363
|
+
errors.any?
|
|
361
364
|
end
|
|
362
365
|
|
|
363
|
-
# Is used as
|
|
366
|
+
# Is used as an around_save callback to check while saving a collection
|
|
364
367
|
# association whether or not the parent was a new record before saving.
|
|
365
|
-
def
|
|
366
|
-
@new_record_before_save
|
|
367
|
-
|
|
368
|
+
def around_save_collection_association
|
|
369
|
+
previously_new_record_before_save = (@new_record_before_save ||= false)
|
|
370
|
+
@new_record_before_save = !previously_new_record_before_save && new_record?
|
|
368
371
|
|
|
369
|
-
|
|
370
|
-
|
|
372
|
+
yield
|
|
373
|
+
ensure
|
|
374
|
+
@new_record_before_save = previously_new_record_before_save
|
|
371
375
|
end
|
|
372
376
|
|
|
373
377
|
# Saves any new associated records, or all loaded autosave associations if
|
|
@@ -402,6 +406,8 @@ module ActiveRecord
|
|
|
402
406
|
saved = true
|
|
403
407
|
|
|
404
408
|
if autosave != false && (new_record_before_save || record.new_record?)
|
|
409
|
+
association.set_inverse_instance(record)
|
|
410
|
+
|
|
405
411
|
if autosave
|
|
406
412
|
saved = association.insert_record(record, false)
|
|
407
413
|
elsif !reflection.nested?
|
|
@@ -432,7 +438,9 @@ module ActiveRecord
|
|
|
432
438
|
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
|
433
439
|
def save_has_one_association(reflection)
|
|
434
440
|
association = association_instance_get(reflection.name)
|
|
435
|
-
|
|
441
|
+
return unless association && association.loaded?
|
|
442
|
+
|
|
443
|
+
record = association.load_target
|
|
436
444
|
|
|
437
445
|
if record && !record.destroyed?
|
|
438
446
|
autosave = reflection.options[:autosave]
|
|
@@ -440,14 +448,19 @@ module ActiveRecord
|
|
|
440
448
|
if autosave && record.marked_for_destruction?
|
|
441
449
|
record.destroy
|
|
442
450
|
elsif autosave != false
|
|
443
|
-
|
|
451
|
+
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
452
|
+
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
444
453
|
|
|
445
|
-
if (autosave && record.changed_for_autosave?) ||
|
|
454
|
+
if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
|
|
446
455
|
unless reflection.through_reflection
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
456
|
+
foreign_key = Array(reflection.foreign_key)
|
|
457
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
458
|
+
|
|
459
|
+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
|
460
|
+
association_id = _read_attribute(primary_key)
|
|
461
|
+
record[foreign_key] = association_id unless record[foreign_key] == association_id
|
|
450
462
|
end
|
|
463
|
+
association.set_inverse_instance(record)
|
|
451
464
|
end
|
|
452
465
|
|
|
453
466
|
saved = record.save(validate: !autosave)
|
|
@@ -459,16 +472,28 @@ module ActiveRecord
|
|
|
459
472
|
end
|
|
460
473
|
|
|
461
474
|
# If the record is new or it has changed, returns true.
|
|
462
|
-
def
|
|
475
|
+
def _record_changed?(reflection, record, key)
|
|
463
476
|
record.new_record? ||
|
|
464
|
-
association_foreign_key_changed?(reflection, record, key) ||
|
|
477
|
+
(association_foreign_key_changed?(reflection, record, key) ||
|
|
478
|
+
inverse_polymorphic_association_changed?(reflection, record)) ||
|
|
465
479
|
record.will_save_change_to_attribute?(reflection.foreign_key)
|
|
466
480
|
end
|
|
467
481
|
|
|
468
482
|
def association_foreign_key_changed?(reflection, record, key)
|
|
469
483
|
return false if reflection.through_reflection?
|
|
470
484
|
|
|
471
|
-
|
|
485
|
+
foreign_key = Array(reflection.foreign_key)
|
|
486
|
+
return false unless foreign_key.all? { |key| record._has_attribute?(key) }
|
|
487
|
+
|
|
488
|
+
foreign_key.map { |key| record._read_attribute(key) } != Array(key)
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def inverse_polymorphic_association_changed?(reflection, record)
|
|
492
|
+
return false unless reflection.inverse_of&.polymorphic?
|
|
493
|
+
|
|
494
|
+
class_name = record._read_attribute(reflection.inverse_of.foreign_type)
|
|
495
|
+
|
|
496
|
+
reflection.active_record.polymorphic_name != class_name
|
|
472
497
|
end
|
|
473
498
|
|
|
474
499
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
|
@@ -483,14 +508,21 @@ module ActiveRecord
|
|
|
483
508
|
autosave = reflection.options[:autosave]
|
|
484
509
|
|
|
485
510
|
if autosave && record.marked_for_destruction?
|
|
486
|
-
|
|
511
|
+
foreign_key = Array(reflection.foreign_key)
|
|
512
|
+
foreign_key.each { |key| self[key] = nil }
|
|
487
513
|
record.destroy
|
|
488
514
|
elsif autosave != false
|
|
489
515
|
saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
|
|
490
516
|
|
|
491
517
|
if association.updated?
|
|
492
|
-
|
|
493
|
-
|
|
518
|
+
primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
|
|
519
|
+
foreign_key = Array(reflection.foreign_key)
|
|
520
|
+
|
|
521
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
522
|
+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
|
523
|
+
association_id = record._read_attribute(primary_key)
|
|
524
|
+
self[foreign_key] = association_id unless self[foreign_key] == association_id
|
|
525
|
+
end
|
|
494
526
|
association.loaded!
|
|
495
527
|
end
|
|
496
528
|
|
|
@@ -499,10 +531,24 @@ module ActiveRecord
|
|
|
499
531
|
end
|
|
500
532
|
end
|
|
501
533
|
|
|
502
|
-
def
|
|
503
|
-
|
|
504
|
-
|
|
534
|
+
def compute_primary_key(reflection, record)
|
|
535
|
+
if primary_key_options = reflection.options[:primary_key]
|
|
536
|
+
primary_key_options
|
|
537
|
+
elsif reflection.options[:query_constraints] && (query_constraints = record.class.query_constraints_list)
|
|
538
|
+
query_constraints
|
|
539
|
+
elsif record.class.has_query_constraints? && !reflection.options[:foreign_key]
|
|
540
|
+
record.class.query_constraints_list
|
|
541
|
+
elsif record.class.composite_primary_key?
|
|
542
|
+
# If record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
|
|
543
|
+
primary_key = record.class.primary_key
|
|
544
|
+
primary_key.include?("id") ? "id" : primary_key
|
|
545
|
+
else
|
|
546
|
+
record.class.primary_key
|
|
505
547
|
end
|
|
506
548
|
end
|
|
549
|
+
|
|
550
|
+
def _ensure_no_duplicate_errors
|
|
551
|
+
errors.uniq!
|
|
552
|
+
end
|
|
507
553
|
end
|
|
508
554
|
end
|