activerecord 3.2.6 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +611 -6417
- data/MIT-LICENSE +4 -2
- data/README.rdoc +44 -47
- data/examples/performance.rb +79 -71
- data/examples/simple.rb +6 -5
- data/lib/active_record/aggregations.rb +268 -238
- data/lib/active_record/association_relation.rb +40 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -42
- data/lib/active_record/associations/association.rb +173 -81
- data/lib/active_record/associations/association_scope.rb +124 -92
- data/lib/active_record/associations/belongs_to_association.rb +83 -38
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +11 -9
- data/lib/active_record/associations/builder/association.rb +113 -32
- data/lib/active_record/associations/builder/belongs_to.rb +105 -60
- data/lib/active_record/associations/builder/collection_association.rb +53 -56
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +98 -41
- data/lib/active_record/associations/builder/has_many.rb +11 -63
- data/lib/active_record/associations/builder/has_one.rb +47 -45
- data/lib/active_record/associations/builder/singular_association.rb +30 -18
- data/lib/active_record/associations/collection_association.rb +217 -295
- data/lib/active_record/associations/collection_proxy.rb +1074 -77
- data/lib/active_record/associations/foreign_association.rb +20 -0
- data/lib/active_record/associations/has_many_association.rb +78 -50
- data/lib/active_record/associations/has_many_through_association.rb +99 -61
- data/lib/active_record/associations/has_one_association.rb +75 -30
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +45 -119
- data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
- data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
- data/lib/active_record/associations/join_dependency.rb +208 -164
- data/lib/active_record/associations/preloader/association.rb +93 -87
- data/lib/active_record/associations/preloader/through_association.rb +87 -38
- data/lib/active_record/associations/preloader.rb +134 -110
- data/lib/active_record/associations/singular_association.rb +19 -24
- data/lib/active_record/associations/through_association.rb +61 -27
- data/lib/active_record/associations.rb +1766 -1505
- data/lib/active_record/attribute_assignment.rb +57 -193
- data/lib/active_record/attribute_decorators.rb +90 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +58 -8
- data/lib/active_record/attribute_methods/dirty.rb +187 -67
- data/lib/active_record/attribute_methods/primary_key.rb +100 -78
- data/lib/active_record/attribute_methods/query.rb +10 -8
- data/lib/active_record/attribute_methods/read.rb +29 -118
- data/lib/active_record/attribute_methods/serialization.rb +60 -72
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -42
- data/lib/active_record/attribute_methods/write.rb +36 -44
- data/lib/active_record/attribute_methods.rb +306 -161
- data/lib/active_record/attributes.rb +279 -0
- data/lib/active_record/autosave_association.rb +324 -238
- data/lib/active_record/base.rb +114 -507
- data/lib/active_record/callbacks.rb +147 -83
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +32 -23
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +962 -279
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +32 -5
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +331 -209
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +201 -65
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +510 -289
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +93 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1182 -313
- data/lib/active_record/connection_adapters/abstract/transaction.rb +323 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +585 -120
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +610 -463
- data/lib/active_record/connection_adapters/column.rb +58 -233
- data/lib/active_record/connection_adapters/connection_specification.rb +297 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +75 -207
- data/lib/active_record/connection_adapters/postgresql/column.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +182 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +113 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +205 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +222 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +776 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +695 -1052
- data/lib/active_record/connection_adapters/schema_cache.rb +115 -24
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +528 -26
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +267 -0
- data/lib/active_record/core.rb +599 -0
- data/lib/active_record/counter_cache.rb +177 -103
- data/lib/active_record/database_configurations/database_config.rb +37 -0
- data/lib/active_record/database_configurations/hash_config.rb +50 -0
- data/lib/active_record/database_configurations/url_config.rb +79 -0
- data/lib/active_record/database_configurations.rb +233 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +107 -64
- data/lib/active_record/enum.rb +274 -0
- data/lib/active_record/errors.rb +254 -61
- data/lib/active_record/explain.rb +35 -70
- data/lib/active_record/explain_registry.rb +32 -0
- data/lib/active_record/explain_subscriber.rb +18 -8
- data/lib/active_record/fixture_set/file.rb +82 -0
- data/lib/active_record/fixture_set/model_metadata.rb +33 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +153 -0
- data/lib/active_record/fixture_set/table_rows.rb +47 -0
- data/lib/active_record/fixtures.rb +291 -475
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +219 -100
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +175 -17
- data/lib/active_record/internal_metadata.rb +53 -0
- data/lib/active_record/legacy_yaml_adapter.rb +48 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +106 -92
- data/lib/active_record/locking/pessimistic.rb +23 -11
- data/lib/active_record/log_subscriber.rb +80 -30
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/migration/command_recorder.rb +235 -56
- data/lib/active_record/migration/compatibility.rb +244 -0
- data/lib/active_record/migration/join_table.rb +17 -0
- data/lib/active_record/migration.rb +917 -301
- data/lib/active_record/model_schema.rb +351 -175
- data/lib/active_record/nested_attributes.rb +366 -235
- data/lib/active_record/no_touching.rb +65 -0
- data/lib/active_record/null_relation.rb +68 -0
- data/lib/active_record/persistence.rb +761 -166
- data/lib/active_record/query_cache.rb +22 -44
- data/lib/active_record/querying.rb +55 -31
- data/lib/active_record/railtie.rb +185 -47
- data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -4
- data/lib/active_record/railties/controller_runtime.rb +35 -33
- data/lib/active_record/railties/databases.rake +366 -463
- data/lib/active_record/readonly_attributes.rb +4 -6
- data/lib/active_record/reflection.rb +736 -228
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +252 -52
- data/lib/active_record/relation/calculations.rb +340 -270
- data/lib/active_record/relation/delegation.rb +117 -36
- data/lib/active_record/relation/finder_methods.rb +439 -286
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +184 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +49 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder.rb +131 -39
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +1163 -221
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +49 -120
- data/lib/active_record/relation/where_clause.rb +190 -0
- data/lib/active_record/relation/where_clause_factory.rb +33 -0
- data/lib/active_record/relation.rb +671 -349
- data/lib/active_record/result.rb +149 -15
- data/lib/active_record/runtime_registry.rb +24 -0
- data/lib/active_record/sanitization.rb +153 -133
- data/lib/active_record/schema.rb +22 -19
- data/lib/active_record/schema_dumper.rb +178 -112
- data/lib/active_record/schema_migration.rb +60 -0
- data/lib/active_record/scoping/default.rb +107 -98
- data/lib/active_record/scoping/named.rb +130 -115
- data/lib/active_record/scoping.rb +77 -123
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +10 -6
- data/lib/active_record/statement_cache.rb +148 -0
- data/lib/active_record/store.rb +256 -16
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +75 -0
- data/lib/active_record/tasks/database_tasks.rb +506 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +141 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +77 -0
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +224 -0
- data/lib/active_record/timestamp.rb +93 -39
- data/lib/active_record/touch_later.rb +66 -0
- data/lib/active_record/transactions.rb +260 -129
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type/adapter_specific_registry.rb +129 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +71 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +21 -0
- data/lib/active_record/type/type_map.rb +62 -0
- data/lib/active_record/type/unsigned_integer.rb +17 -0
- data/lib/active_record/type.rb +78 -0
- data/lib/active_record/type_caster/connection.rb +34 -0
- data/lib/active_record/type_caster/map.rb +20 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +35 -18
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +68 -0
- data/lib/active_record/validations/uniqueness.rb +123 -77
- data/lib/active_record/validations.rb +54 -43
- data/lib/active_record/version.rb +7 -7
- data/lib/active_record.rb +97 -49
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +37 -0
- data/lib/arel/attributes.rb +22 -0
- data/lib/arel/collectors/bind.rb +24 -0
- data/lib/arel/collectors/composite.rb +31 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +20 -0
- data/lib/arel/collectors/substitute_binds.rb +28 -0
- data/lib/arel/crud.rb +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +52 -0
- data/lib/arel/nodes/bind_param.rb +36 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +50 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/delete_statement.rb +45 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +18 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +8 -0
- data/lib/arel/nodes/in.rb +8 -0
- data/lib/arel/nodes/infix_operation.rb +80 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +50 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +27 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +45 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +41 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +68 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +257 -0
- data/lib/arel/select_manager.rb +271 -0
- data/lib/arel/table.rb +110 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors/depth_first.rb +204 -0
- data/lib/arel/visitors/dot.rb +297 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +157 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +159 -0
- data/lib/arel/visitors/oracle12.rb +66 -0
- data/lib/arel/visitors/postgresql.rb +110 -0
- data/lib/arel/visitors/sqlite.rb +39 -0
- data/lib/arel/visitors/to_sql.rb +889 -0
- data/lib/arel/visitors/visitor.rb +46 -0
- data/lib/arel/visitors/where_sql.rb +23 -0
- data/lib/arel/visitors.rb +20 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +51 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +59 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
- data/lib/rails/generators/active_record/migration.rb +41 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +24 -22
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
- data/lib/rails/generators/active_record.rb +10 -16
- metadata +285 -149
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -188
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -426
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -579
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,8 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/except"
|
4
|
+
require "active_support/core_ext/module/redefine_method"
|
5
|
+
require "active_support/core_ext/object/try"
|
6
|
+
require "active_support/core_ext/hash/indifferent_access"
|
6
7
|
|
7
8
|
module ActiveRecord
|
8
9
|
module NestedAttributes #:nodoc:
|
@@ -12,17 +13,16 @@ module ActiveRecord
|
|
12
13
|
extend ActiveSupport::Concern
|
13
14
|
|
14
15
|
included do
|
15
|
-
class_attribute :nested_attributes_options, :
|
16
|
-
self.nested_attributes_options = {}
|
16
|
+
class_attribute :nested_attributes_options, instance_writer: false, default: {}
|
17
17
|
end
|
18
18
|
|
19
19
|
# = Active Record Nested Attributes
|
20
20
|
#
|
21
21
|
# Nested attributes allow you to save attributes on associated records
|
22
|
-
# through the parent. By default nested attribute updating is turned off
|
23
|
-
# you can enable it using the accepts_nested_attributes_for class
|
24
|
-
# When you enable nested attributes an attribute writer is
|
25
|
-
# the model.
|
22
|
+
# through the parent. By default nested attribute updating is turned off
|
23
|
+
# and you can enable it using the accepts_nested_attributes_for class
|
24
|
+
# method. When you enable nested attributes an attribute writer is
|
25
|
+
# defined on the model.
|
26
26
|
#
|
27
27
|
# The attribute writer is named after the association, which means that
|
28
28
|
# in the following example, two new methods are added to your model:
|
@@ -52,15 +52,27 @@ module ActiveRecord
|
|
52
52
|
# Enabling nested attributes on a one-to-one association allows you to
|
53
53
|
# create the member and avatar in one go:
|
54
54
|
#
|
55
|
-
# params = { :
|
55
|
+
# params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
|
56
56
|
# member = Member.create(params[:member])
|
57
57
|
# member.avatar.id # => 2
|
58
58
|
# member.avatar.icon # => 'smiling'
|
59
59
|
#
|
60
60
|
# It also allows you to update the avatar through the member:
|
61
61
|
#
|
62
|
-
# params = { :
|
63
|
-
# member.
|
62
|
+
# params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
|
63
|
+
# member.update params[:member]
|
64
|
+
# member.avatar.icon # => 'sad'
|
65
|
+
#
|
66
|
+
# If you want to update the current avatar without providing the id, you must add <tt>:update_only</tt> option.
|
67
|
+
#
|
68
|
+
# class Member < ActiveRecord::Base
|
69
|
+
# has_one :avatar
|
70
|
+
# accepts_nested_attributes_for :avatar, update_only: true
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# params = { member: { avatar_attributes: { icon: 'sad' } } }
|
74
|
+
# member.update params[:member]
|
75
|
+
# member.avatar.id # => 2
|
64
76
|
# member.avatar.icon # => 'sad'
|
65
77
|
#
|
66
78
|
# By default you will only be able to set and update attributes on the
|
@@ -70,19 +82,22 @@ module ActiveRecord
|
|
70
82
|
#
|
71
83
|
# class Member < ActiveRecord::Base
|
72
84
|
# has_one :avatar
|
73
|
-
# accepts_nested_attributes_for :avatar, :
|
85
|
+
# accepts_nested_attributes_for :avatar, allow_destroy: true
|
74
86
|
# end
|
75
87
|
#
|
76
88
|
# Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
|
77
89
|
# value that evaluates to +true+, you will destroy the associated model:
|
78
90
|
#
|
79
|
-
# member.avatar_attributes = { :
|
91
|
+
# member.avatar_attributes = { id: '2', _destroy: '1' }
|
80
92
|
# member.avatar.marked_for_destruction? # => true
|
81
93
|
# member.save
|
82
94
|
# member.reload.avatar # => nil
|
83
95
|
#
|
84
96
|
# Note that the model will _not_ be destroyed until the parent is saved.
|
85
97
|
#
|
98
|
+
# Also note that the model will not be destroyed unless you also specify
|
99
|
+
# its id in the updated hash.
|
100
|
+
#
|
86
101
|
# === One-to-many
|
87
102
|
#
|
88
103
|
# Consider a member that has a number of posts:
|
@@ -92,78 +107,84 @@ module ActiveRecord
|
|
92
107
|
# accepts_nested_attributes_for :posts
|
93
108
|
# end
|
94
109
|
#
|
95
|
-
# You can now set or update attributes on
|
96
|
-
#
|
110
|
+
# You can now set or update attributes on the associated posts through
|
111
|
+
# an attribute hash for a member: include the key +:posts_attributes+
|
112
|
+
# with an array of hashes of post attributes as a value.
|
97
113
|
#
|
98
114
|
# For each hash that does _not_ have an <tt>id</tt> key a new record will
|
99
115
|
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
|
100
116
|
# that evaluates to +true+.
|
101
117
|
#
|
102
|
-
# params = { :
|
103
|
-
# :
|
104
|
-
# { :
|
105
|
-
# { :
|
106
|
-
# { :
|
118
|
+
# params = { member: {
|
119
|
+
# name: 'joe', posts_attributes: [
|
120
|
+
# { title: 'Kari, the awesome Ruby documentation browser!' },
|
121
|
+
# { title: 'The egalitarian assumption of the modern citizen' },
|
122
|
+
# { title: '', _destroy: '1' } # this will be ignored
|
107
123
|
# ]
|
108
124
|
# }}
|
109
125
|
#
|
110
|
-
# member = Member.create(params[
|
126
|
+
# member = Member.create(params[:member])
|
111
127
|
# member.posts.length # => 2
|
112
128
|
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
|
113
129
|
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
|
114
130
|
#
|
115
|
-
# You may also set a
|
131
|
+
# You may also set a +:reject_if+ proc to silently ignore any new record
|
116
132
|
# hashes if they fail to pass your criteria. For example, the previous
|
117
133
|
# example could be rewritten as:
|
118
134
|
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
135
|
+
# class Member < ActiveRecord::Base
|
136
|
+
# has_many :posts
|
137
|
+
# accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
|
138
|
+
# end
|
123
139
|
#
|
124
|
-
# params = { :
|
125
|
-
# :
|
126
|
-
# { :
|
127
|
-
# { :
|
128
|
-
# { :
|
140
|
+
# params = { member: {
|
141
|
+
# name: 'joe', posts_attributes: [
|
142
|
+
# { title: 'Kari, the awesome Ruby documentation browser!' },
|
143
|
+
# { title: 'The egalitarian assumption of the modern citizen' },
|
144
|
+
# { title: '' } # this will be ignored because of the :reject_if proc
|
129
145
|
# ]
|
130
146
|
# }}
|
131
147
|
#
|
132
|
-
# member = Member.create(params[
|
148
|
+
# member = Member.create(params[:member])
|
133
149
|
# member.posts.length # => 2
|
134
150
|
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
|
135
151
|
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
|
136
152
|
#
|
137
|
-
# Alternatively,
|
153
|
+
# Alternatively, +:reject_if+ also accepts a symbol for using methods:
|
138
154
|
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
155
|
+
# class Member < ActiveRecord::Base
|
156
|
+
# has_many :posts
|
157
|
+
# accepts_nested_attributes_for :posts, reject_if: :new_record?
|
158
|
+
# end
|
143
159
|
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
160
|
+
# class Member < ActiveRecord::Base
|
161
|
+
# has_many :posts
|
162
|
+
# accepts_nested_attributes_for :posts, reject_if: :reject_posts
|
147
163
|
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
164
|
+
# def reject_posts(attributes)
|
165
|
+
# attributes['title'].blank?
|
166
|
+
# end
|
167
|
+
# end
|
152
168
|
#
|
153
169
|
# If the hash contains an <tt>id</tt> key that matches an already
|
154
170
|
# associated record, the matching record will be modified:
|
155
171
|
#
|
156
172
|
# member.attributes = {
|
157
|
-
# :
|
158
|
-
# :
|
159
|
-
# { :
|
160
|
-
# { :
|
173
|
+
# name: 'Joe',
|
174
|
+
# posts_attributes: [
|
175
|
+
# { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
|
176
|
+
# { id: 2, title: '[UPDATED] other post' }
|
161
177
|
# ]
|
162
178
|
# }
|
163
179
|
#
|
164
180
|
# member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
|
165
181
|
# member.posts.second.title # => '[UPDATED] other post'
|
166
182
|
#
|
183
|
+
# However, the above applies if the parent model is being updated as well.
|
184
|
+
# For example, If you wanted to create a +member+ named _joe_ and wanted to
|
185
|
+
# update the +posts+ at the same time, that would give an
|
186
|
+
# ActiveRecord::RecordNotFound error.
|
187
|
+
#
|
167
188
|
# By default the associated records are protected from being destroyed. If
|
168
189
|
# you want to destroy any of the associated records through the attributes
|
169
190
|
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
|
@@ -172,59 +193,97 @@ module ActiveRecord
|
|
172
193
|
#
|
173
194
|
# class Member < ActiveRecord::Base
|
174
195
|
# has_many :posts
|
175
|
-
# accepts_nested_attributes_for :posts, :
|
196
|
+
# accepts_nested_attributes_for :posts, allow_destroy: true
|
176
197
|
# end
|
177
198
|
#
|
178
|
-
# params = { :
|
179
|
-
# :
|
199
|
+
# params = { member: {
|
200
|
+
# posts_attributes: [{ id: '2', _destroy: '1' }]
|
180
201
|
# }}
|
181
202
|
#
|
182
|
-
# member.attributes = params[
|
203
|
+
# member.attributes = params[:member]
|
183
204
|
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
|
184
205
|
# member.posts.length # => 2
|
185
206
|
# member.save
|
186
207
|
# member.reload.posts.length # => 1
|
187
208
|
#
|
188
|
-
#
|
209
|
+
# Nested attributes for an associated collection can also be passed in
|
210
|
+
# the form of a hash of hashes instead of an array of hashes:
|
189
211
|
#
|
190
|
-
#
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
212
|
+
# Member.create(
|
213
|
+
# name: 'joe',
|
214
|
+
# posts_attributes: {
|
215
|
+
# first: { title: 'Foo' },
|
216
|
+
# second: { title: 'Bar' }
|
217
|
+
# }
|
218
|
+
# )
|
194
219
|
#
|
195
|
-
#
|
220
|
+
# has the same effect as
|
196
221
|
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
222
|
+
# Member.create(
|
223
|
+
# name: 'joe',
|
224
|
+
# posts_attributes: [
|
225
|
+
# { title: 'Foo' },
|
226
|
+
# { title: 'Bar' }
|
227
|
+
# ]
|
228
|
+
# )
|
200
229
|
#
|
201
|
-
#
|
230
|
+
# The keys of the hash which is the value for +:posts_attributes+ are
|
231
|
+
# ignored in this case.
|
232
|
+
# However, it is not allowed to use <tt>'id'</tt> or <tt>:id</tt> for one of
|
233
|
+
# such keys, otherwise the hash will be wrapped in an array and
|
234
|
+
# interpreted as an attribute hash for a single post.
|
202
235
|
#
|
203
|
-
#
|
236
|
+
# Passing attributes for an associated collection in the form of a hash
|
237
|
+
# of hashes can be used with hashes generated from HTTP/HTML parameters,
|
238
|
+
# where there may be no natural way to submit an array of hashes.
|
204
239
|
#
|
205
|
-
#
|
240
|
+
# === Saving
|
241
|
+
#
|
242
|
+
# All changes to models, including the destruction of those marked for
|
243
|
+
# destruction, are saved and destroyed automatically and atomically when
|
244
|
+
# the parent model is saved. This happens inside the transaction initiated
|
245
|
+
# by the parent's save method. See ActiveRecord::AutosaveAssociation.
|
206
246
|
#
|
207
247
|
# === Validating the presence of a parent model
|
208
248
|
#
|
209
249
|
# If you want to validate that a child record is associated with a parent
|
210
|
-
# record, you can use
|
211
|
-
#
|
250
|
+
# record, you can use the +validates_presence_of+ method and the +:inverse_of+
|
251
|
+
# key as this example illustrates:
|
212
252
|
#
|
213
253
|
# class Member < ActiveRecord::Base
|
214
|
-
# has_many :posts, :
|
254
|
+
# has_many :posts, inverse_of: :member
|
215
255
|
# accepts_nested_attributes_for :posts
|
216
256
|
# end
|
217
257
|
#
|
218
258
|
# class Post < ActiveRecord::Base
|
219
|
-
# belongs_to :member, :
|
259
|
+
# belongs_to :member, inverse_of: :posts
|
220
260
|
# validates_presence_of :member
|
221
261
|
# end
|
262
|
+
#
|
263
|
+
# Note that if you do not specify the +:inverse_of+ option, then
|
264
|
+
# Active Record will try to automatically guess the inverse association
|
265
|
+
# based on heuristics.
|
266
|
+
#
|
267
|
+
# For one-to-one nested associations, if you build the new (in-memory)
|
268
|
+
# child object yourself before assignment, then this module will not
|
269
|
+
# overwrite it, e.g.:
|
270
|
+
#
|
271
|
+
# class Member < ActiveRecord::Base
|
272
|
+
# has_one :avatar
|
273
|
+
# accepts_nested_attributes_for :avatar
|
274
|
+
#
|
275
|
+
# def avatar
|
276
|
+
# super || build_avatar(width: 200)
|
277
|
+
# end
|
278
|
+
# end
|
279
|
+
#
|
280
|
+
# member = Member.new
|
281
|
+
# member.avatar_attributes = {icon: 'sad'}
|
282
|
+
# member.avatar.width # => 200
|
222
283
|
module ClassMethods
|
223
|
-
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key ==
|
284
|
+
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
|
224
285
|
|
225
|
-
# Defines an attributes writer for the specified association(s).
|
226
|
-
# are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you
|
227
|
-
# will need to add the attribute writer to the allowed list.
|
286
|
+
# Defines an attributes writer for the specified association(s).
|
228
287
|
#
|
229
288
|
# Supported options:
|
230
289
|
# [:allow_destroy]
|
@@ -235,64 +294,86 @@ module ActiveRecord
|
|
235
294
|
# Allows you to specify a Proc or a Symbol pointing to a method
|
236
295
|
# that checks whether a record should be built for a certain attribute
|
237
296
|
# hash. The hash is passed to the supplied Proc or the method
|
238
|
-
# and it should return either +true+ or +false+. When no
|
297
|
+
# and it should return either +true+ or +false+. When no +:reject_if+
|
239
298
|
# is specified, a record will be built for all attribute hashes that
|
240
299
|
# do not have a <tt>_destroy</tt> value that evaluates to true.
|
241
300
|
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
|
242
301
|
# that will reject a record where all the attributes are blank excluding
|
243
|
-
# any value for _destroy
|
302
|
+
# any value for +_destroy+.
|
244
303
|
# [:limit]
|
245
|
-
# Allows you to specify the maximum number of
|
246
|
-
# can be processed with the nested attributes.
|
247
|
-
#
|
248
|
-
#
|
249
|
-
#
|
304
|
+
# Allows you to specify the maximum number of associated records that
|
305
|
+
# can be processed with the nested attributes. Limit also can be specified
|
306
|
+
# as a Proc or a Symbol pointing to a method that should return a number.
|
307
|
+
# If the size of the nested attributes array exceeds the specified limit,
|
308
|
+
# NestedAttributes::TooManyRecords exception is raised. If omitted, any
|
309
|
+
# number of associations can be processed.
|
310
|
+
# Note that the +:limit+ option is only applicable to one-to-many
|
311
|
+
# associations.
|
250
312
|
# [:update_only]
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
313
|
+
# For a one-to-one association, this option allows you to specify how
|
314
|
+
# nested attributes are going to be used when an associated record already
|
315
|
+
# exists. In general, an existing record may either be updated with the
|
316
|
+
# new set of attribute values or be replaced by a wholly new record
|
317
|
+
# containing those values. By default the +:update_only+ option is +false+
|
318
|
+
# and the nested attributes are used to update the existing record only
|
319
|
+
# if they include the record's <tt>:id</tt> value. Otherwise a new
|
320
|
+
# record will be instantiated and used to replace the existing one.
|
321
|
+
# However if the +:update_only+ option is +true+, the nested attributes
|
322
|
+
# are used to update the record's attributes always, regardless of
|
323
|
+
# whether the <tt>:id</tt> is present. The option is ignored for collection
|
324
|
+
# associations.
|
255
325
|
#
|
256
326
|
# Examples:
|
257
327
|
# # creates avatar_attributes=
|
258
|
-
# accepts_nested_attributes_for :avatar, :
|
328
|
+
# accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
|
259
329
|
# # creates avatar_attributes=
|
260
|
-
# accepts_nested_attributes_for :avatar, :
|
330
|
+
# accepts_nested_attributes_for :avatar, reject_if: :all_blank
|
261
331
|
# # creates avatar_attributes= and posts_attributes=
|
262
|
-
# accepts_nested_attributes_for :avatar, :posts, :
|
332
|
+
# accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
|
263
333
|
def accepts_nested_attributes_for(*attr_names)
|
264
|
-
options = { :
|
334
|
+
options = { allow_destroy: false, update_only: false }
|
265
335
|
options.update(attr_names.extract_options!)
|
266
336
|
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
|
267
337
|
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
|
268
338
|
|
269
339
|
attr_names.each do |association_name|
|
270
|
-
if reflection =
|
271
|
-
reflection.
|
272
|
-
|
340
|
+
if reflection = _reflect_on_association(association_name)
|
341
|
+
reflection.autosave = true
|
342
|
+
define_autosave_validation_callbacks(reflection)
|
273
343
|
|
274
344
|
nested_attributes_options = self.nested_attributes_options.dup
|
275
345
|
nested_attributes_options[association_name.to_sym] = options
|
276
346
|
self.nested_attributes_options = nested_attributes_options
|
277
347
|
|
278
348
|
type = (reflection.collection? ? :collection : :one_to_one)
|
279
|
-
|
280
|
-
# def pirate_attributes=(attributes)
|
281
|
-
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
|
282
|
-
# end
|
283
|
-
class_eval <<-eoruby, __FILE__, __LINE__ + 1
|
284
|
-
if method_defined?(:#{association_name}_attributes=)
|
285
|
-
remove_method(:#{association_name}_attributes=)
|
286
|
-
end
|
287
|
-
def #{association_name}_attributes=(attributes)
|
288
|
-
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options)
|
289
|
-
end
|
290
|
-
eoruby
|
349
|
+
generate_association_writer(association_name, type)
|
291
350
|
else
|
292
351
|
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
|
293
352
|
end
|
294
353
|
end
|
295
354
|
end
|
355
|
+
|
356
|
+
private
|
357
|
+
|
358
|
+
# Generates a writer method for this association. Serves as a point for
|
359
|
+
# accessing the objects in the association. For example, this method
|
360
|
+
# could generate the following:
|
361
|
+
#
|
362
|
+
# def pirate_attributes=(attributes)
|
363
|
+
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
|
364
|
+
# end
|
365
|
+
#
|
366
|
+
# This redirects the attempts to write objects in an association through
|
367
|
+
# the helper methods defined below. Makes it seem like the nested
|
368
|
+
# associations are just regular associations.
|
369
|
+
def generate_association_writer(association_name, type)
|
370
|
+
generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
|
371
|
+
silence_redefinition_of_method :#{association_name}_attributes=
|
372
|
+
def #{association_name}_attributes=(attributes)
|
373
|
+
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
|
374
|
+
end
|
375
|
+
eoruby
|
376
|
+
end
|
296
377
|
end
|
297
378
|
|
298
379
|
# Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
|
@@ -306,164 +387,214 @@ module ActiveRecord
|
|
306
387
|
|
307
388
|
private
|
308
389
|
|
309
|
-
|
310
|
-
|
311
|
-
|
390
|
+
# Attribute hash keys that should not be assigned as normal attributes.
|
391
|
+
# These hash keys are nested attributes implementation details.
|
392
|
+
UNASSIGNABLE_KEYS = %w( id _destroy )
|
312
393
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
394
|
+
# Assigns the given attributes to the association.
|
395
|
+
#
|
396
|
+
# If an associated record does not yet exist, one will be instantiated. If
|
397
|
+
# an associated record already exists, the method's behavior depends on
|
398
|
+
# the value of the update_only option. If update_only is +false+ and the
|
399
|
+
# given attributes include an <tt>:id</tt> that matches the existing record's
|
400
|
+
# id, then the existing record will be modified. If no <tt>:id</tt> is provided
|
401
|
+
# it will be replaced with a new record. If update_only is +true+ the existing
|
402
|
+
# record will be modified regardless of whether an <tt>:id</tt> is provided.
|
403
|
+
#
|
404
|
+
# If the given attributes include a matching <tt>:id</tt> attribute, or
|
405
|
+
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
|
406
|
+
# then the existing record will be marked for destruction.
|
407
|
+
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
|
408
|
+
options = nested_attributes_options[association_name]
|
409
|
+
if attributes.respond_to?(:permitted?)
|
410
|
+
attributes = attributes.to_h
|
411
|
+
end
|
412
|
+
attributes = attributes.with_indifferent_access
|
413
|
+
existing_record = send(association_name)
|
326
414
|
|
327
|
-
|
328
|
-
|
329
|
-
|
415
|
+
if (options[:update_only] || !attributes["id"].blank?) && existing_record &&
|
416
|
+
(options[:update_only] || existing_record.id.to_s == attributes["id"].to_s)
|
417
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
|
330
418
|
|
331
|
-
|
332
|
-
|
419
|
+
elsif attributes["id"].present?
|
420
|
+
raise_nested_attributes_record_not_found!(association_name, attributes["id"])
|
333
421
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
422
|
+
elsif !reject_new_record?(association_name, attributes)
|
423
|
+
assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
|
424
|
+
|
425
|
+
if existing_record && existing_record.new_record?
|
426
|
+
existing_record.assign_attributes(assignable_attributes)
|
427
|
+
association(association_name).initialize_attributes(existing_record)
|
428
|
+
else
|
429
|
+
method = :"build_#{association_name}"
|
430
|
+
if respond_to?(method)
|
431
|
+
send(method, assignable_attributes)
|
432
|
+
else
|
433
|
+
raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
|
434
|
+
end
|
435
|
+
end
|
340
436
|
end
|
341
437
|
end
|
342
|
-
end
|
343
438
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
439
|
+
# Assigns the given attributes to the collection association.
|
440
|
+
#
|
441
|
+
# Hashes with an <tt>:id</tt> value matching an existing associated record
|
442
|
+
# will update that record. Hashes without an <tt>:id</tt> value will build
|
443
|
+
# a new record for the association. Hashes with a matching <tt>:id</tt>
|
444
|
+
# value and a <tt>:_destroy</tt> key set to a truthy value will mark the
|
445
|
+
# matched record for destruction.
|
446
|
+
#
|
447
|
+
# For example:
|
448
|
+
#
|
449
|
+
# assign_nested_attributes_for_collection_association(:people, {
|
450
|
+
# '1' => { id: '1', name: 'Peter' },
|
451
|
+
# '2' => { name: 'John' },
|
452
|
+
# '3' => { id: '2', _destroy: true }
|
453
|
+
# })
|
454
|
+
#
|
455
|
+
# Will update the name of the Person with ID 1, build a new associated
|
456
|
+
# person with the name 'John', and mark the associated Person with ID 2
|
457
|
+
# for destruction.
|
458
|
+
#
|
459
|
+
# Also accepts an Array of attribute hashes:
|
460
|
+
#
|
461
|
+
# assign_nested_attributes_for_collection_association(:people, [
|
462
|
+
# { id: '1', name: 'Peter' },
|
463
|
+
# { name: 'John' },
|
464
|
+
# { id: '2', _destroy: true }
|
465
|
+
# ])
|
466
|
+
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
|
467
|
+
options = nested_attributes_options[association_name]
|
468
|
+
if attributes_collection.respond_to?(:permitted?)
|
469
|
+
attributes_collection = attributes_collection.to_h
|
470
|
+
end
|
373
471
|
|
374
|
-
|
375
|
-
|
376
|
-
|
472
|
+
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
473
|
+
raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
474
|
+
end
|
377
475
|
|
378
|
-
|
379
|
-
raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead."
|
380
|
-
end
|
476
|
+
check_record_limit!(options[:limit], attributes_collection)
|
381
477
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
478
|
+
if attributes_collection.is_a? Hash
|
479
|
+
keys = attributes_collection.keys
|
480
|
+
attributes_collection = if keys.include?("id") || keys.include?(:id)
|
481
|
+
[attributes_collection]
|
482
|
+
else
|
483
|
+
attributes_collection.values
|
484
|
+
end
|
388
485
|
end
|
389
|
-
end
|
390
486
|
|
391
|
-
|
392
|
-
|
393
|
-
existing_records = if association.loaded?
|
394
|
-
association.target
|
395
|
-
else
|
396
|
-
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
|
397
|
-
attribute_ids.empty? ? [] : association.scoped.where(association.klass.primary_key => attribute_ids)
|
398
|
-
end
|
487
|
+
association = association(association_name)
|
399
488
|
|
400
|
-
|
401
|
-
|
489
|
+
existing_records = if association.loaded?
|
490
|
+
association.target
|
491
|
+
else
|
492
|
+
attribute_ids = attributes_collection.map { |a| a["id"] || a[:id] }.compact
|
493
|
+
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
|
494
|
+
end
|
402
495
|
|
403
|
-
|
404
|
-
|
405
|
-
|
496
|
+
attributes_collection.each do |attributes|
|
497
|
+
if attributes.respond_to?(:permitted?)
|
498
|
+
attributes = attributes.to_h
|
406
499
|
end
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
if target_record
|
414
|
-
existing_record = target_record
|
415
|
-
else
|
416
|
-
association.add_to_target(existing_record)
|
500
|
+
attributes = attributes.with_indifferent_access
|
501
|
+
|
502
|
+
if attributes["id"].blank?
|
503
|
+
unless reject_new_record?(association_name, attributes)
|
504
|
+
association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
|
417
505
|
end
|
506
|
+
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s }
|
507
|
+
unless call_reject_if(association_name, attributes)
|
508
|
+
# Make sure we are operating on the actual object which is in the association's
|
509
|
+
# proxy_target array (either by finding it, or adding it if not found)
|
510
|
+
# Take into account that the proxy_target may have changed due to callbacks
|
511
|
+
target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s }
|
512
|
+
if target_record
|
513
|
+
existing_record = target_record
|
514
|
+
else
|
515
|
+
association.add_to_target(existing_record, :skip_callbacks)
|
516
|
+
end
|
418
517
|
|
518
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
519
|
+
end
|
520
|
+
else
|
521
|
+
raise_nested_attributes_record_not_found!(association_name, attributes["id"])
|
419
522
|
end
|
523
|
+
end
|
524
|
+
end
|
420
525
|
|
421
|
-
|
422
|
-
|
526
|
+
# Takes in a limit and checks if the attributes_collection has too many
|
527
|
+
# records. It accepts limit in the form of symbol, proc, or
|
528
|
+
# number-like object (anything that can be compared with an integer).
|
529
|
+
#
|
530
|
+
# Raises TooManyRecords error if the attributes_collection is
|
531
|
+
# larger than the limit.
|
532
|
+
def check_record_limit!(limit, attributes_collection)
|
533
|
+
if limit
|
534
|
+
limit = \
|
535
|
+
case limit
|
536
|
+
when Symbol
|
537
|
+
send(limit)
|
538
|
+
when Proc
|
539
|
+
limit.call
|
540
|
+
else
|
541
|
+
limit
|
542
|
+
end
|
543
|
+
|
544
|
+
if limit && attributes_collection.size > limit
|
545
|
+
raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
|
423
546
|
end
|
424
|
-
elsif assignment_opts[:without_protection]
|
425
|
-
association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
|
426
|
-
else
|
427
|
-
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
428
547
|
end
|
429
548
|
end
|
430
|
-
end
|
431
549
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
550
|
+
# Updates a record with the +attributes+ or marks it for destruction if
|
551
|
+
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
|
552
|
+
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
|
553
|
+
record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
|
554
|
+
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
|
555
|
+
end
|
438
556
|
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
557
|
+
# Determines if a hash contains a truthy _destroy key.
|
558
|
+
def has_destroy_flag?(hash)
|
559
|
+
Type::Boolean.new.cast(hash["_destroy"])
|
560
|
+
end
|
443
561
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
562
|
+
# Determines if a new record should be rejected by checking
|
563
|
+
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
|
564
|
+
# association and evaluates to +true+.
|
565
|
+
def reject_new_record?(association_name, attributes)
|
566
|
+
will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
|
567
|
+
end
|
568
|
+
|
569
|
+
# Determines if a record with the particular +attributes+ should be
|
570
|
+
# rejected by calling the reject_if Symbol or Proc (if defined).
|
571
|
+
# The reject_if option is defined by +accepts_nested_attributes_for+.
|
572
|
+
#
|
573
|
+
# Returns false if there is a +destroy_flag+ on the attributes.
|
574
|
+
def call_reject_if(association_name, attributes)
|
575
|
+
return false if will_be_destroyed?(association_name, attributes)
|
576
|
+
|
577
|
+
case callback = nested_attributes_options[association_name][:reject_if]
|
578
|
+
when Symbol
|
579
|
+
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
|
580
|
+
when Proc
|
581
|
+
callback.call(attributes)
|
582
|
+
end
|
583
|
+
end
|
450
584
|
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
when Symbol
|
455
|
-
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
|
456
|
-
when Proc
|
457
|
-
callback.call(attributes)
|
585
|
+
# Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
|
586
|
+
def will_be_destroyed?(association_name, attributes)
|
587
|
+
allow_destroy?(association_name) && has_destroy_flag?(attributes)
|
458
588
|
end
|
459
|
-
end
|
460
589
|
|
461
|
-
|
462
|
-
|
463
|
-
|
590
|
+
def allow_destroy?(association_name)
|
591
|
+
nested_attributes_options[association_name][:allow_destroy]
|
592
|
+
end
|
464
593
|
|
465
|
-
|
466
|
-
|
467
|
-
|
594
|
+
def raise_nested_attributes_record_not_found!(association_name, record_id)
|
595
|
+
model = self.class._reflect_on_association(association_name).klass.name
|
596
|
+
raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
|
597
|
+
model, "id", record_id)
|
598
|
+
end
|
468
599
|
end
|
469
600
|
end
|