activerecord 6.1.7 → 7.2.2
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 +616 -1290
- 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 +19 -8
- 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 +30 -27
- data/lib/active_record/associations/join_dependency.rb +28 -20
- 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 +429 -522
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -5
- 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 +15 -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 +57 -54
- data/lib/active_record/autosave_association.rb +74 -57
- data/lib/active_record/base.rb +27 -5
- data/lib/active_record/callbacks.rb +19 -35
- 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 +325 -604
- 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 +230 -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 +378 -143
- 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 +348 -165
- 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 +403 -77
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
- 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 +310 -253
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
- 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 +58 -0
- data/lib/active_record/enum.rb +170 -62
- 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 +59 -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 +145 -158
- data/lib/active_record/nested_attributes.rb +61 -23
- 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 +18 -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 +229 -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 +332 -103
- data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
- data/lib/active_record/relation/batches.rb +200 -65
- data/lib/active_record/relation/calculations.rb +301 -112
- 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 +870 -163
- 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 +6 -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 +288 -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 +65 -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/sqlite.rb +25 -0
- 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 +103 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -67
@@ -5,7 +5,7 @@ require "active_support/core_ext/module/redefine_method"
|
|
5
5
|
require "active_support/core_ext/hash/indifferent_access"
|
6
6
|
|
7
7
|
module ActiveRecord
|
8
|
-
module NestedAttributes
|
8
|
+
module NestedAttributes # :nodoc:
|
9
9
|
class TooManyRecords < ActiveRecordError
|
10
10
|
end
|
11
11
|
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
class_attribute :nested_attributes_options, instance_writer: false, default: {}
|
16
16
|
end
|
17
17
|
|
18
|
-
# = Active Record Nested Attributes
|
18
|
+
# = Active Record Nested \Attributes
|
19
19
|
#
|
20
20
|
# Nested attributes allow you to save attributes on associated records
|
21
21
|
# through the parent. By default nested attribute updating is turned off
|
@@ -180,7 +180,7 @@ module ActiveRecord
|
|
180
180
|
# member.posts.second.title # => '[UPDATED] other post'
|
181
181
|
#
|
182
182
|
# However, the above applies if the parent model is being updated as well.
|
183
|
-
# For example,
|
183
|
+
# For example, if you wanted to create a +member+ named _joe_ and wanted to
|
184
184
|
# update the +posts+ at the same time, that would give an
|
185
185
|
# ActiveRecord::RecordNotFound error.
|
186
186
|
#
|
@@ -245,18 +245,19 @@ module ActiveRecord
|
|
245
245
|
#
|
246
246
|
# === Validating the presence of a parent model
|
247
247
|
#
|
248
|
-
#
|
249
|
-
#
|
250
|
-
#
|
248
|
+
# The +belongs_to+ association validates the presence of the parent model
|
249
|
+
# by default. You can disable this behavior by specifying <code>optional: true</code>.
|
250
|
+
# This can be used, for example, when conditionally validating the presence
|
251
|
+
# of the parent model:
|
251
252
|
#
|
252
|
-
# class
|
253
|
-
# has_many :
|
254
|
-
# accepts_nested_attributes_for :
|
253
|
+
# class Veterinarian < ActiveRecord::Base
|
254
|
+
# has_many :patients, inverse_of: :veterinarian
|
255
|
+
# accepts_nested_attributes_for :patients
|
255
256
|
# end
|
256
257
|
#
|
257
|
-
# class
|
258
|
-
# belongs_to :
|
259
|
-
#
|
258
|
+
# class Patient < ActiveRecord::Base
|
259
|
+
# belongs_to :veterinarian, inverse_of: :patients, optional: true
|
260
|
+
# validates :veterinarian, presence: true, unless: -> { awaiting_intake }
|
260
261
|
# end
|
261
262
|
#
|
262
263
|
# Note that if you do not specify the +:inverse_of+ option, then
|
@@ -279,6 +280,24 @@ module ActiveRecord
|
|
279
280
|
# member = Member.new
|
280
281
|
# member.avatar_attributes = {icon: 'sad'}
|
281
282
|
# member.avatar.width # => 200
|
283
|
+
#
|
284
|
+
# === Creating forms with nested attributes
|
285
|
+
#
|
286
|
+
# Use ActionView::Helpers::FormHelper#fields_for to create form elements for
|
287
|
+
# nested attributes.
|
288
|
+
#
|
289
|
+
# Integration test params should reflect the structure of the form. For
|
290
|
+
# example:
|
291
|
+
#
|
292
|
+
# post members_path, params: {
|
293
|
+
# member: {
|
294
|
+
# name: 'joe',
|
295
|
+
# posts_attributes: {
|
296
|
+
# '0' => { title: 'Foo' },
|
297
|
+
# '1' => { title: 'Bar' }
|
298
|
+
# }
|
299
|
+
# }
|
300
|
+
# }
|
282
301
|
module ClassMethods
|
283
302
|
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
|
284
303
|
|
@@ -288,7 +307,7 @@ module ActiveRecord
|
|
288
307
|
# [:allow_destroy]
|
289
308
|
# If true, destroys any members from the attributes hash with a
|
290
309
|
# <tt>_destroy</tt> key and a value that evaluates to +true+
|
291
|
-
# (e.g. 1, '1', true, or 'true'). This option is
|
310
|
+
# (e.g. 1, '1', true, or 'true'). This option is false by default.
|
292
311
|
# [:reject_if]
|
293
312
|
# Allows you to specify a Proc or a Symbol pointing to a method
|
294
313
|
# that checks whether a record should be built for a certain attribute
|
@@ -313,11 +332,11 @@ module ActiveRecord
|
|
313
332
|
# nested attributes are going to be used when an associated record already
|
314
333
|
# exists. In general, an existing record may either be updated with the
|
315
334
|
# new set of attribute values or be replaced by a wholly new record
|
316
|
-
# containing those values. By default the +:update_only+ option is
|
335
|
+
# containing those values. By default the +:update_only+ option is false
|
317
336
|
# and the nested attributes are used to update the existing record only
|
318
337
|
# if they include the record's <tt>:id</tt> value. Otherwise a new
|
319
338
|
# record will be instantiated and used to replace the existing one.
|
320
|
-
# However if the +:update_only+ option is
|
339
|
+
# However if the +:update_only+ option is true, the nested attributes
|
321
340
|
# are used to update the record's attributes always, regardless of
|
322
341
|
# whether the <tt>:id</tt> is present. The option is ignored for collection
|
323
342
|
# associations.
|
@@ -374,11 +393,11 @@ module ActiveRecord
|
|
374
393
|
end
|
375
394
|
end
|
376
395
|
|
377
|
-
# Returns ActiveRecord::AutosaveAssociation
|
396
|
+
# Returns ActiveRecord::AutosaveAssociation#marked_for_destruction? It's
|
378
397
|
# used in conjunction with fields_for to build a form element for the
|
379
398
|
# destruction of this association.
|
380
399
|
#
|
381
|
-
# See ActionView::Helpers::FormHelper
|
400
|
+
# See ActionView::Helpers::FormHelper#fields_for for more info.
|
382
401
|
def _destroy
|
383
402
|
marked_for_destruction?
|
384
403
|
end
|
@@ -402,10 +421,15 @@ module ActiveRecord
|
|
402
421
|
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
|
403
422
|
# then the existing record will be marked for destruction.
|
404
423
|
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
|
405
|
-
options = nested_attributes_options[association_name]
|
406
424
|
if attributes.respond_to?(:permitted?)
|
407
425
|
attributes = attributes.to_h
|
408
426
|
end
|
427
|
+
|
428
|
+
unless attributes.is_a?(Hash)
|
429
|
+
raise ArgumentError, "Hash expected for `#{association_name}` attributes, got #{attributes.class.name}"
|
430
|
+
end
|
431
|
+
|
432
|
+
options = nested_attributes_options[association_name]
|
409
433
|
attributes = attributes.with_indifferent_access
|
410
434
|
existing_record = send(association_name)
|
411
435
|
|
@@ -467,7 +491,7 @@ module ActiveRecord
|
|
467
491
|
end
|
468
492
|
|
469
493
|
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
470
|
-
raise ArgumentError, "Hash or Array expected for
|
494
|
+
raise ArgumentError, "Hash or Array expected for `#{association_name}` attributes, got #{attributes_collection.class.name}"
|
471
495
|
end
|
472
496
|
|
473
497
|
check_record_limit!(options[:limit], attributes_collection)
|
@@ -486,11 +510,11 @@ module ActiveRecord
|
|
486
510
|
existing_records = if association.loaded?
|
487
511
|
association.target
|
488
512
|
else
|
489
|
-
attribute_ids = attributes_collection.
|
513
|
+
attribute_ids = attributes_collection.filter_map { |a| a["id"] || a[:id] }
|
490
514
|
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
|
491
515
|
end
|
492
516
|
|
493
|
-
attributes_collection.
|
517
|
+
records = attributes_collection.map do |attributes|
|
494
518
|
if attributes.respond_to?(:permitted?)
|
495
519
|
attributes = attributes.to_h
|
496
520
|
end
|
@@ -500,12 +524,12 @@ module ActiveRecord
|
|
500
524
|
unless reject_new_record?(association_name, attributes)
|
501
525
|
association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
|
502
526
|
end
|
503
|
-
elsif existing_record = existing_records
|
527
|
+
elsif existing_record = find_record_by_id(existing_records, attributes["id"])
|
504
528
|
unless call_reject_if(association_name, attributes)
|
505
529
|
# Make sure we are operating on the actual object which is in the association's
|
506
530
|
# proxy_target array (either by finding it, or adding it if not found)
|
507
531
|
# Take into account that the proxy_target may have changed due to callbacks
|
508
|
-
target_record = association.target
|
532
|
+
target_record = find_record_by_id(association.target, attributes["id"])
|
509
533
|
if target_record
|
510
534
|
existing_record = target_record
|
511
535
|
else
|
@@ -513,11 +537,14 @@ module ActiveRecord
|
|
513
537
|
end
|
514
538
|
|
515
539
|
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
540
|
+
existing_record
|
516
541
|
end
|
517
542
|
else
|
518
543
|
raise_nested_attributes_record_not_found!(association_name, attributes["id"])
|
519
544
|
end
|
520
545
|
end
|
546
|
+
|
547
|
+
association.nested_attributes_target = records
|
521
548
|
end
|
522
549
|
|
523
550
|
# Takes in a limit and checks if the attributes_collection has too many
|
@@ -593,5 +620,16 @@ module ActiveRecord
|
|
593
620
|
raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
|
594
621
|
model, "id", record_id)
|
595
622
|
end
|
623
|
+
|
624
|
+
def find_record_by_id(records, id)
|
625
|
+
return if records.empty?
|
626
|
+
|
627
|
+
if records.first.class.composite_primary_key?
|
628
|
+
id = Array(id).map(&:to_s)
|
629
|
+
records.find { |record| Array(record.id).map(&:to_s) == id }
|
630
|
+
else
|
631
|
+
records.find { |record| record.id.to_s == id.to_s }
|
632
|
+
end
|
633
|
+
end
|
596
634
|
end
|
597
635
|
end
|
@@ -26,20 +26,20 @@ module ActiveRecord
|
|
26
26
|
end
|
27
27
|
|
28
28
|
class << self
|
29
|
-
def apply_to(klass)
|
29
|
+
def apply_to(klass) # :nodoc:
|
30
30
|
klasses.push(klass)
|
31
31
|
yield
|
32
32
|
ensure
|
33
33
|
klasses.pop
|
34
34
|
end
|
35
35
|
|
36
|
-
def applied_to?(klass)
|
36
|
+
def applied_to?(klass) # :nodoc:
|
37
37
|
klasses.any? { |k| k >= klass }
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
41
41
|
def klasses
|
42
|
-
|
42
|
+
ActiveSupport::IsolatedExecutionState[:active_record_no_touching_classes] ||= []
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord # :nodoc:
|
4
|
+
module Normalization
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :normalized_attributes, default: Set.new
|
9
|
+
|
10
|
+
before_validation :normalize_changed_in_place_attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
# Normalizes a specified attribute using its declared normalizations.
|
14
|
+
#
|
15
|
+
# ==== Examples
|
16
|
+
#
|
17
|
+
# class User < ActiveRecord::Base
|
18
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# legacy_user = User.find(1)
|
22
|
+
# legacy_user.email # => " CRUISE-CONTROL@EXAMPLE.COM\n"
|
23
|
+
# legacy_user.normalize_attribute(:email)
|
24
|
+
# legacy_user.email # => "cruise-control@example.com"
|
25
|
+
# legacy_user.save
|
26
|
+
def normalize_attribute(name)
|
27
|
+
# Treat the value as a new, unnormalized value.
|
28
|
+
self[name] = self[name]
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
# Declares a normalization for one or more attributes. The normalization
|
33
|
+
# is applied when the attribute is assigned or updated, and the normalized
|
34
|
+
# value will be persisted to the database. The normalization is also
|
35
|
+
# applied to the corresponding keyword argument of query methods. This
|
36
|
+
# allows a record to be created and later queried using unnormalized
|
37
|
+
# values.
|
38
|
+
#
|
39
|
+
# However, to prevent confusion, the normalization will not be applied
|
40
|
+
# when the attribute is fetched from the database. This means that if a
|
41
|
+
# record was persisted before the normalization was declared, the record's
|
42
|
+
# attribute will not be normalized until either it is assigned a new
|
43
|
+
# value, or it is explicitly migrated via Normalization#normalize_attribute.
|
44
|
+
#
|
45
|
+
# Because the normalization may be applied multiple times, it should be
|
46
|
+
# _idempotent_. In other words, applying the normalization more than once
|
47
|
+
# should have the same result as applying it only once.
|
48
|
+
#
|
49
|
+
# By default, the normalization will not be applied to +nil+ values. This
|
50
|
+
# behavior can be changed with the +:apply_to_nil+ option.
|
51
|
+
#
|
52
|
+
# Be aware that if your app was created before Rails 7.1, and your app
|
53
|
+
# marshals instances of the targeted model (for example, when caching),
|
54
|
+
# then you should set ActiveRecord.marshalling_format_version to +7.1+ or
|
55
|
+
# higher via either <tt>config.load_defaults 7.1</tt> or
|
56
|
+
# <tt>config.active_record.marshalling_format_version = 7.1</tt>.
|
57
|
+
# Otherwise, +Marshal+ may attempt to serialize the normalization +Proc+
|
58
|
+
# and raise +TypeError+.
|
59
|
+
#
|
60
|
+
# ==== Options
|
61
|
+
#
|
62
|
+
# * +:with+ - Any callable object that accepts the attribute's value as
|
63
|
+
# its sole argument, and returns it normalized.
|
64
|
+
# * +:apply_to_nil+ - Whether to apply the normalization to +nil+ values.
|
65
|
+
# Defaults to +false+.
|
66
|
+
#
|
67
|
+
# ==== Examples
|
68
|
+
#
|
69
|
+
# class User < ActiveRecord::Base
|
70
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
71
|
+
# normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") }
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
|
75
|
+
# user.email # => "cruise-control@example.com"
|
76
|
+
#
|
77
|
+
# user = User.find_by(email: "\tCRUISE-CONTROL@EXAMPLE.COM ")
|
78
|
+
# user.email # => "cruise-control@example.com"
|
79
|
+
# user.email_before_type_cast # => "cruise-control@example.com"
|
80
|
+
#
|
81
|
+
# User.where(email: "\tCRUISE-CONTROL@EXAMPLE.COM ").count # => 1
|
82
|
+
# User.where(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]).count # => 0
|
83
|
+
#
|
84
|
+
# User.exists?(email: "\tCRUISE-CONTROL@EXAMPLE.COM ") # => true
|
85
|
+
# User.exists?(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]) # => false
|
86
|
+
#
|
87
|
+
# User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309"
|
88
|
+
def normalizes(*names, with:, apply_to_nil: false)
|
89
|
+
decorate_attributes(names) do |name, cast_type|
|
90
|
+
NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil)
|
91
|
+
end
|
92
|
+
|
93
|
+
self.normalized_attributes += names.map(&:to_sym)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Normalizes a given +value+ using normalizations declared for +name+.
|
97
|
+
#
|
98
|
+
# ==== Examples
|
99
|
+
#
|
100
|
+
# class User < ActiveRecord::Base
|
101
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# User.normalize_value_for(:email, " CRUISE-CONTROL@EXAMPLE.COM\n")
|
105
|
+
# # => "cruise-control@example.com"
|
106
|
+
def normalize_value_for(name, value)
|
107
|
+
type_for_attribute(name).cast(value)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
def normalize_changed_in_place_attributes
|
113
|
+
self.class.normalized_attributes.each do |name|
|
114
|
+
normalize_attribute(name) if attribute_changed_in_place?(name)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class NormalizedValueType < DelegateClass(ActiveModel::Type::Value) # :nodoc:
|
119
|
+
include ActiveModel::Type::SerializeCastValue
|
120
|
+
|
121
|
+
attr_reader :cast_type, :normalizer, :normalize_nil
|
122
|
+
alias :normalize_nil? :normalize_nil
|
123
|
+
|
124
|
+
def initialize(cast_type:, normalizer:, normalize_nil:)
|
125
|
+
@cast_type = cast_type
|
126
|
+
@normalizer = normalizer
|
127
|
+
@normalize_nil = normalize_nil
|
128
|
+
super(cast_type)
|
129
|
+
end
|
130
|
+
|
131
|
+
def cast(value)
|
132
|
+
normalize(super(value))
|
133
|
+
end
|
134
|
+
|
135
|
+
def serialize(value)
|
136
|
+
serialize_cast_value(cast(value))
|
137
|
+
end
|
138
|
+
|
139
|
+
def serialize_cast_value(value)
|
140
|
+
ActiveModel::Type::SerializeCastValue.serialize(cast_type, value)
|
141
|
+
end
|
142
|
+
|
143
|
+
def ==(other)
|
144
|
+
self.class == other.class &&
|
145
|
+
normalize_nil? == other.normalize_nil? &&
|
146
|
+
normalizer == other.normalizer &&
|
147
|
+
cast_type == other.cast_type
|
148
|
+
end
|
149
|
+
alias eql? ==
|
150
|
+
|
151
|
+
def hash
|
152
|
+
[self.class, cast_type, normalizer, normalize_nil?].hash
|
153
|
+
end
|
154
|
+
|
155
|
+
define_method(:inspect, Kernel.instance_method(:inspect))
|
156
|
+
|
157
|
+
private
|
158
|
+
def normalize(value)
|
159
|
+
normalizer.call(value) unless value.nil? && !normalize_nil?
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|