activerecord 5.2.3 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +898 -532
- data/MIT-LICENSE +3 -1
- data/README.rdoc +7 -5
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +5 -4
- data/lib/active_record/association_relation.rb +22 -12
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +95 -42
- data/lib/active_record/associations/association_scope.rb +21 -21
- data/lib/active_record/associations/belongs_to_association.rb +50 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -5
- data/lib/active_record/associations/builder/association.rb +23 -21
- data/lib/active_record/associations/builder/belongs_to.rb +29 -59
- data/lib/active_record/associations/builder/collection_association.rb +10 -19
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -2
- data/lib/active_record/associations/builder/has_one.rb +33 -2
- data/lib/active_record/associations/builder/singular_association.rb +3 -1
- data/lib/active_record/associations/collection_association.rb +31 -29
- data/lib/active_record/associations/collection_proxy.rb +25 -21
- data/lib/active_record/associations/foreign_association.rb +20 -0
- data/lib/active_record/associations/has_many_association.rb +26 -13
- data/lib/active_record/associations/has_many_through_association.rb +27 -28
- data/lib/active_record/associations/has_one_association.rb +43 -31
- data/lib/active_record/associations/has_one_through_association.rb +5 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +54 -12
- data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
- data/lib/active_record/associations/join_dependency.rb +91 -60
- data/lib/active_record/associations/preloader/association.rb +71 -43
- data/lib/active_record/associations/preloader/through_association.rb +49 -40
- data/lib/active_record/associations/preloader.rb +48 -35
- data/lib/active_record/associations/singular_association.rb +3 -17
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +133 -25
- data/lib/active_record/attribute_assignment.rb +17 -19
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -40
- data/lib/active_record/attribute_methods/primary_key.rb +20 -25
- data/lib/active_record/attribute_methods/query.rb +4 -8
- data/lib/active_record/attribute_methods/read.rb +14 -56
- data/lib/active_record/attribute_methods/serialization.rb +12 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
- data/lib/active_record/attribute_methods/write.rb +18 -34
- data/lib/active_record/attribute_methods.rb +81 -143
- data/lib/active_record/attributes.rb +45 -8
- data/lib/active_record/autosave_association.rb +76 -47
- data/lib/active_record/base.rb +4 -17
- data/lib/active_record/callbacks.rb +158 -43
- data/lib/active_record/coders/yaml_column.rb +1 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +293 -132
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +21 -17
- data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +203 -90
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +381 -146
- data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -98
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
- data/lib/active_record/connection_adapters/column.rb +30 -12
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -32
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
- data/lib/active_record/connection_adapters/pool_config.rb +63 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -54
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
- data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +222 -112
- data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +175 -187
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_handling.rb +285 -33
- data/lib/active_record/core.rb +308 -100
- data/lib/active_record/counter_cache.rb +8 -30
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
- data/lib/active_record/database_configurations/database_config.rb +80 -0
- data/lib/active_record/database_configurations/hash_config.rb +96 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/database_configurations.rb +272 -0
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +3 -4
- data/lib/active_record/enum.rb +71 -17
- data/lib/active_record/errors.rb +62 -19
- data/lib/active_record/explain.rb +10 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +10 -17
- data/lib/active_record/fixture_set/model_metadata.rb +32 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +152 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +197 -481
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +53 -24
- data/lib/active_record/insert_all.rb +208 -0
- data/lib/active_record/integration.rb +67 -17
- data/lib/active_record/internal_metadata.rb +26 -9
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +26 -22
- data/lib/active_record/locking/pessimistic.rb +9 -5
- data/lib/active_record/log_subscriber.rb +34 -35
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +77 -0
- data/lib/active_record/migration/command_recorder.rb +96 -44
- data/lib/active_record/migration/compatibility.rb +141 -64
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/migration.rb +205 -156
- data/lib/active_record/model_schema.rb +148 -22
- data/lib/active_record/nested_attributes.rb +4 -7
- data/lib/active_record/no_touching.rb +8 -1
- data/lib/active_record/null_relation.rb +0 -1
- data/lib/active_record/persistence.rb +267 -59
- data/lib/active_record/query_cache.rb +21 -4
- data/lib/active_record/querying.rb +40 -23
- data/lib/active_record/railtie.rb +115 -58
- data/lib/active_record/railties/controller_runtime.rb +30 -35
- data/lib/active_record/railties/databases.rake +402 -78
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +113 -101
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/batches.rb +44 -35
- data/lib/active_record/relation/calculations.rb +157 -93
- data/lib/active_record/relation/delegation.rb +35 -50
- data/lib/active_record/relation/finder_methods.rb +65 -40
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +32 -40
- data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -7
- data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +58 -40
- data/lib/active_record/relation/query_attribute.rb +13 -8
- data/lib/active_record/relation/query_methods.rb +487 -199
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +9 -9
- data/lib/active_record/relation/where_clause.rb +108 -58
- data/lib/active_record/relation.rb +375 -104
- data/lib/active_record/result.rb +64 -38
- data/lib/active_record/runtime_registry.rb +2 -2
- data/lib/active_record/sanitization.rb +22 -41
- data/lib/active_record/schema.rb +2 -11
- data/lib/active_record/schema_dumper.rb +54 -9
- data/lib/active_record/schema_migration.rb +7 -9
- data/lib/active_record/scoping/default.rb +6 -8
- data/lib/active_record/scoping/named.rb +17 -24
- data/lib/active_record/scoping.rb +8 -9
- data/lib/active_record/secure_token.rb +16 -8
- data/lib/active_record/serialization.rb +5 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +51 -8
- data/lib/active_record/store.rb +88 -9
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +39 -43
- data/lib/active_record/tasks/database_tasks.rb +276 -81
- data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
- data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +246 -0
- data/lib/active_record/timestamp.rb +43 -32
- data/lib/active_record/touch_later.rb +23 -22
- data/lib/active_record/transactions.rb +59 -117
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +3 -13
- data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
- data/lib/active_record/type/serialized.rb +6 -3
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/type/type_map.rb +0 -1
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +10 -5
- data/lib/active_record/type_caster/connection.rb +15 -15
- data/lib/active_record/type_caster/map.rb +8 -8
- data/lib/active_record/validations/associated.rb +1 -2
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +38 -30
- data/lib/active_record/validations.rb +4 -3
- data/lib/active_record.rb +13 -12
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +41 -0
- data/lib/arel/collectors/bind.rb +29 -0
- data/lib/arel/collectors/composite.rb +39 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -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 +126 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -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 +15 -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 +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +72 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -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 +51 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -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 +19 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +31 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -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 +70 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +250 -0
- data/lib/arel/select_manager.rb +270 -0
- data/lib/arel/table.rb +118 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors/dot.rb +308 -0
- data/lib/arel/visitors/mysql.rb +93 -0
- data/lib/arel/visitors/postgresql.rb +120 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +899 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +54 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
- data/lib/rails/generators/active_record/migration.rb +19 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
- 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 +10 -1
- metadata +117 -32
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
- data/lib/active_record/relation/where_clause_factory.rb +0 -34
| @@ -6,7 +6,7 @@ module ActiveRecord | |
| 6 6 | 
             
                class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
         | 
| 7 7 | 
             
                  def klass
         | 
| 8 8 | 
             
                    type = owner[reflection.foreign_type]
         | 
| 9 | 
            -
                    type.presence && type | 
| 9 | 
            +
                    type.presence && owner.class.polymorphic_class_for(type)
         | 
| 10 10 | 
             
                  end
         | 
| 11 11 |  | 
| 12 12 | 
             
                  def target_changed?
         | 
| @@ -19,10 +19,6 @@ module ActiveRecord | |
| 19 19 | 
             
                      owner[reflection.foreign_type] = record ? record.class.polymorphic_name : nil
         | 
| 20 20 | 
             
                    end
         | 
| 21 21 |  | 
| 22 | 
            -
                    def different_target?(record)
         | 
| 23 | 
            -
                      super || record.class != klass
         | 
| 24 | 
            -
                    end
         | 
| 25 | 
            -
             | 
| 26 22 | 
             
                    def inverse_reflection_for(record)
         | 
| 27 23 | 
             
                      reflection.polymorphic_inverse_of(record.class)
         | 
| 28 24 | 
             
                    end
         | 
| @@ -18,7 +18,9 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 18 18 | 
             
                end
         | 
| 19 19 | 
             
                self.extensions = []
         | 
| 20 20 |  | 
| 21 | 
            -
                VALID_OPTIONS = [ | 
| 21 | 
            +
                VALID_OPTIONS = [
         | 
| 22 | 
            +
                  :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading
         | 
| 23 | 
            +
                ].freeze # :nodoc:
         | 
| 22 24 |  | 
| 23 25 | 
             
                def self.build(model, name, scope, options, &block)
         | 
| 24 26 | 
             
                  if model.dangerous_attribute_method?(name)
         | 
| @@ -27,40 +29,32 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 27 29 | 
             
                                         "Please choose a different association name."
         | 
| 28 30 | 
             
                  end
         | 
| 29 31 |  | 
| 30 | 
            -
                   | 
| 31 | 
            -
                  reflection = create_reflection model, name, scope, options, extension
         | 
| 32 | 
            +
                  reflection = create_reflection(model, name, scope, options, &block)
         | 
| 32 33 | 
             
                  define_accessors model, reflection
         | 
| 33 34 | 
             
                  define_callbacks model, reflection
         | 
| 34 35 | 
             
                  define_validations model, reflection
         | 
| 35 36 | 
             
                  reflection
         | 
| 36 37 | 
             
                end
         | 
| 37 38 |  | 
| 38 | 
            -
                def self.create_reflection(model, name, scope, options,  | 
| 39 | 
            +
                def self.create_reflection(model, name, scope, options, &block)
         | 
| 39 40 | 
             
                  raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
         | 
| 40 41 |  | 
| 41 42 | 
             
                  validate_options(options)
         | 
| 42 43 |  | 
| 43 | 
            -
                   | 
| 44 | 
            +
                  extension = define_extensions(model, name, &block)
         | 
| 45 | 
            +
                  options[:extend] = [*options[:extend], extension] if extension
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  scope = build_scope(scope)
         | 
| 44 48 |  | 
| 45 49 | 
             
                  ActiveRecord::Reflection.create(macro, name, scope, options, model)
         | 
| 46 50 | 
             
                end
         | 
| 47 51 |  | 
| 48 | 
            -
                def self.build_scope(scope | 
| 49 | 
            -
                  new_scope = scope
         | 
| 50 | 
            -
             | 
| 52 | 
            +
                def self.build_scope(scope)
         | 
| 51 53 | 
             
                  if scope && scope.arity == 0
         | 
| 52 | 
            -
                     | 
| 54 | 
            +
                    proc { instance_exec(&scope) }
         | 
| 55 | 
            +
                  else
         | 
| 56 | 
            +
                    scope
         | 
| 53 57 | 
             
                  end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                  if extension
         | 
| 56 | 
            -
                    new_scope = wrap_scope new_scope, extension
         | 
| 57 | 
            -
                  end
         | 
| 58 | 
            -
             | 
| 59 | 
            -
                  new_scope
         | 
| 60 | 
            -
                end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
                def self.wrap_scope(scope, extension)
         | 
| 63 | 
            -
                  scope
         | 
| 64 58 | 
             
                end
         | 
| 65 59 |  | 
| 66 60 | 
             
                def self.macro
         | 
| @@ -80,7 +74,7 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 80 74 |  | 
| 81 75 | 
             
                def self.define_callbacks(model, reflection)
         | 
| 82 76 | 
             
                  if dependent = reflection.options[:dependent]
         | 
| 83 | 
            -
                    check_dependent_options(dependent)
         | 
| 77 | 
            +
                    check_dependent_options(dependent, model)
         | 
| 84 78 | 
             
                    add_destroy_callbacks(model, reflection)
         | 
| 85 79 | 
             
                  end
         | 
| 86 80 |  | 
| @@ -126,7 +120,11 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 126 120 | 
             
                  raise NotImplementedError
         | 
| 127 121 | 
             
                end
         | 
| 128 122 |  | 
| 129 | 
            -
                def self.check_dependent_options(dependent)
         | 
| 123 | 
            +
                def self.check_dependent_options(dependent, model)
         | 
| 124 | 
            +
                  if dependent == :destroy_async && !model.destroy_association_async_job
         | 
| 125 | 
            +
                    err_message = "ActiveJob is required to use destroy_async on associations"
         | 
| 126 | 
            +
                    raise ActiveRecord::ActiveJobRequiredError, err_message
         | 
| 127 | 
            +
                  end
         | 
| 130 128 | 
             
                  unless valid_dependent_options.include? dependent
         | 
| 131 129 | 
             
                    raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
         | 
| 132 130 | 
             
                  end
         | 
| @@ -136,5 +134,9 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 136 134 | 
             
                  name = reflection.name
         | 
| 137 135 | 
             
                  model.before_destroy lambda { |o| o.association(name).handle_dependency }
         | 
| 138 136 | 
             
                end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
         | 
| 139 | 
            +
                  :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
         | 
| 140 | 
            +
                  :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks
         | 
| 139 141 | 
             
              end
         | 
| 140 142 | 
             
            end
         | 
| @@ -7,11 +7,14 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 7 7 | 
             
                end
         | 
| 8 8 |  | 
| 9 9 | 
             
                def self.valid_options(options)
         | 
| 10 | 
            -
                  super + [: | 
| 10 | 
            +
                  valid = super + [:counter_cache, :optional, :default]
         | 
| 11 | 
            +
                  valid += [:polymorphic, :foreign_type] if options[:polymorphic]
         | 
| 12 | 
            +
                  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
         | 
| 13 | 
            +
                  valid
         | 
| 11 14 | 
             
                end
         | 
| 12 15 |  | 
| 13 16 | 
             
                def self.valid_dependent_options
         | 
| 14 | 
            -
                  [:destroy, :delete]
         | 
| 17 | 
            +
                  [:destroy, :delete, :destroy_async]
         | 
| 15 18 | 
             
                end
         | 
| 16 19 |  | 
| 17 20 | 
             
                def self.define_callbacks(model, reflection)
         | 
| @@ -21,58 +24,16 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 21 24 | 
             
                  add_default_callbacks(model, reflection)       if reflection.options[:default]
         | 
| 22 25 | 
             
                end
         | 
| 23 26 |  | 
| 24 | 
            -
                def self.define_accessors(mixin, reflection)
         | 
| 25 | 
            -
                  super
         | 
| 26 | 
            -
                  add_counter_cache_methods mixin
         | 
| 27 | 
            -
                end
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                def self.add_counter_cache_methods(mixin)
         | 
| 30 | 
            -
                  return if mixin.method_defined? :belongs_to_counter_cache_after_update
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                  mixin.class_eval do
         | 
| 33 | 
            -
                    def belongs_to_counter_cache_after_update(reflection)
         | 
| 34 | 
            -
                      foreign_key  = reflection.foreign_key
         | 
| 35 | 
            -
                      cache_column = reflection.counter_cache_column
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                      if (@_after_replace_counter_called ||= false)
         | 
| 38 | 
            -
                        @_after_replace_counter_called = false
         | 
| 39 | 
            -
                      elsif association(reflection.name).target_changed?
         | 
| 40 | 
            -
                        if reflection.polymorphic?
         | 
| 41 | 
            -
                          model     = attribute_in_database(reflection.foreign_type).try(:constantize)
         | 
| 42 | 
            -
                          model_was = attribute_before_last_save(reflection.foreign_type).try(:constantize)
         | 
| 43 | 
            -
                        else
         | 
| 44 | 
            -
                          model     = reflection.klass
         | 
| 45 | 
            -
                          model_was = reflection.klass
         | 
| 46 | 
            -
                        end
         | 
| 47 | 
            -
             | 
| 48 | 
            -
                        foreign_key_was = attribute_before_last_save foreign_key
         | 
| 49 | 
            -
                        foreign_key     = attribute_in_database foreign_key
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                        if foreign_key && model.respond_to?(:increment_counter)
         | 
| 52 | 
            -
                          foreign_key = counter_cache_target(reflection, model, foreign_key)
         | 
| 53 | 
            -
                          model.increment_counter(cache_column, foreign_key)
         | 
| 54 | 
            -
                        end
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                        if foreign_key_was && model_was.respond_to?(:decrement_counter)
         | 
| 57 | 
            -
                          foreign_key_was = counter_cache_target(reflection, model_was, foreign_key_was)
         | 
| 58 | 
            -
                          model_was.decrement_counter(cache_column, foreign_key_was)
         | 
| 59 | 
            -
                        end
         | 
| 60 | 
            -
                      end
         | 
| 61 | 
            -
                    end
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                    private
         | 
| 64 | 
            -
                      def counter_cache_target(reflection, model, foreign_key)
         | 
| 65 | 
            -
                        primary_key = reflection.association_primary_key(model)
         | 
| 66 | 
            -
                        model.unscoped.where!(primary_key => foreign_key)
         | 
| 67 | 
            -
                      end
         | 
| 68 | 
            -
                  end
         | 
| 69 | 
            -
                end
         | 
| 70 | 
            -
             | 
| 71 27 | 
             
                def self.add_counter_cache_callbacks(model, reflection)
         | 
| 72 28 | 
             
                  cache_column = reflection.counter_cache_column
         | 
| 73 29 |  | 
| 74 30 | 
             
                  model.after_update lambda { |record|
         | 
| 75 | 
            -
                     | 
| 31 | 
            +
                    association = association(reflection.name)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    if association.target_changed?
         | 
| 34 | 
            +
                      association.increment_counters
         | 
| 35 | 
            +
                      association.decrement_counters_before_last_save
         | 
| 36 | 
            +
                    end
         | 
| 76 37 | 
             
                  }
         | 
| 77 38 |  | 
| 78 39 | 
             
                  klass = reflection.class_name.safe_constantize
         | 
| @@ -97,38 +58,44 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 97 58 |  | 
| 98 59 | 
             
                    if old_record
         | 
| 99 60 | 
             
                      if touch != true
         | 
| 100 | 
            -
                        old_record. | 
| 61 | 
            +
                        old_record.public_send(touch_method, touch)
         | 
| 101 62 | 
             
                      else
         | 
| 102 | 
            -
                        old_record. | 
| 63 | 
            +
                        old_record.public_send(touch_method)
         | 
| 103 64 | 
             
                      end
         | 
| 104 65 | 
             
                    end
         | 
| 105 66 | 
             
                  end
         | 
| 106 67 |  | 
| 107 | 
            -
                  record = o. | 
| 68 | 
            +
                  record = o.public_send name
         | 
| 108 69 | 
             
                  if record && record.persisted?
         | 
| 109 70 | 
             
                    if touch != true
         | 
| 110 | 
            -
                      record. | 
| 71 | 
            +
                      record.public_send(touch_method, touch)
         | 
| 111 72 | 
             
                    else
         | 
| 112 | 
            -
                      record. | 
| 73 | 
            +
                      record.public_send(touch_method)
         | 
| 113 74 | 
             
                    end
         | 
| 114 75 | 
             
                  end
         | 
| 115 76 | 
             
                end
         | 
| 116 77 |  | 
| 117 78 | 
             
                def self.add_touch_callbacks(model, reflection)
         | 
| 118 79 | 
             
                  foreign_key = reflection.foreign_key
         | 
| 119 | 
            -
                   | 
| 80 | 
            +
                  name        = reflection.name
         | 
| 120 81 | 
             
                  touch       = reflection.options[:touch]
         | 
| 121 82 |  | 
| 122 83 | 
             
                  callback = lambda { |changes_method| lambda { |record|
         | 
| 123 | 
            -
                    BelongsTo.touch_record(record, record.send(changes_method), foreign_key,  | 
| 84 | 
            +
                    BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch, belongs_to_touch_method)
         | 
| 124 85 | 
             
                  }}
         | 
| 125 86 |  | 
| 126 | 
            -
                   | 
| 87 | 
            +
                  if reflection.counter_cache_column
         | 
| 88 | 
            +
                    touch_callback = callback.(:saved_changes)
         | 
| 89 | 
            +
                    update_callback = lambda { |record|
         | 
| 90 | 
            +
                      instance_exec(record, &touch_callback) unless association(reflection.name).target_changed?
         | 
| 91 | 
            +
                    }
         | 
| 92 | 
            +
                    model.after_update update_callback, if: :saved_changes?
         | 
| 93 | 
            +
                  else
         | 
| 127 94 | 
             
                    model.after_create callback.(:saved_changes), if: :saved_changes?
         | 
| 95 | 
            +
                    model.after_update callback.(:saved_changes), if: :saved_changes?
         | 
| 128 96 | 
             
                    model.after_destroy callback.(:changes_to_save)
         | 
| 129 97 | 
             
                  end
         | 
| 130 98 |  | 
| 131 | 
            -
                  model.after_update callback.(:saved_changes), if: :saved_changes?
         | 
| 132 99 | 
             
                  model.after_touch callback.(:changes_to_save)
         | 
| 133 100 | 
             
                end
         | 
| 134 101 |  | 
| @@ -159,5 +126,8 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 159 126 | 
             
                    model.validates_presence_of reflection.name, message: :required
         | 
| 160 127 | 
             
                  end
         | 
| 161 128 | 
             
                end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations,
         | 
| 131 | 
            +
                  :add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
         | 
| 162 132 | 
             
              end
         | 
| 163 133 | 
             
            end
         | 
| @@ -7,8 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 7 7 | 
             
                CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
         | 
| 8 8 |  | 
| 9 9 | 
             
                def self.valid_options(options)
         | 
| 10 | 
            -
                  super + [: | 
| 11 | 
            -
                           :after_add, :before_remove, :after_remove, :extend]
         | 
| 10 | 
            +
                  super + [:before_add, :after_add, :before_remove, :after_remove, :extend]
         | 
| 12 11 | 
             
                end
         | 
| 13 12 |  | 
| 14 13 | 
             
                def self.define_callbacks(model, reflection)
         | 
| @@ -20,19 +19,21 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 20 19 | 
             
                  }
         | 
| 21 20 | 
             
                end
         | 
| 22 21 |  | 
| 23 | 
            -
                def self.define_extensions(model, name)
         | 
| 22 | 
            +
                def self.define_extensions(model, name, &block)
         | 
| 24 23 | 
             
                  if block_given?
         | 
| 25 | 
            -
                    extension_module_name = "#{ | 
| 26 | 
            -
                    extension = Module.new(& | 
| 27 | 
            -
                    model. | 
| 24 | 
            +
                    extension_module_name = "#{name.to_s.camelize}AssociationExtension"
         | 
| 25 | 
            +
                    extension = Module.new(&block)
         | 
| 26 | 
            +
                    model.const_set(extension_module_name, extension)
         | 
| 28 27 | 
             
                  end
         | 
| 29 28 | 
             
                end
         | 
| 30 29 |  | 
| 31 30 | 
             
                def self.define_callback(model, callback_name, name, options)
         | 
| 32 31 | 
             
                  full_callback_name = "#{callback_name}_for_#{name}"
         | 
| 33 32 |  | 
| 34 | 
            -
                   | 
| 35 | 
            -
             | 
| 33 | 
            +
                  unless model.method_defined?(full_callback_name)
         | 
| 34 | 
            +
                    model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 36 37 | 
             
                  callbacks = Array(options[callback_name.to_sym]).map do |callback|
         | 
| 37 38 | 
             
                    case callback
         | 
| 38 39 | 
             
                    when Symbol
         | 
| @@ -67,16 +68,6 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 67 68 | 
             
                  CODE
         | 
| 68 69 | 
             
                end
         | 
| 69 70 |  | 
| 70 | 
            -
                 | 
| 71 | 
            -
                  if scope
         | 
| 72 | 
            -
                    if scope.arity > 0
         | 
| 73 | 
            -
                      proc { |owner| instance_exec(owner, &scope).extending(mod) }
         | 
| 74 | 
            -
                    else
         | 
| 75 | 
            -
                      proc { instance_exec(&scope).extending(mod) }
         | 
| 76 | 
            -
                    end
         | 
| 77 | 
            -
                  else
         | 
| 78 | 
            -
                    proc { extending(mod) }
         | 
| 79 | 
            -
                  end
         | 
| 80 | 
            -
                end
         | 
| 71 | 
            +
                private_class_method :valid_options, :define_callback, :define_extensions, :define_readers, :define_writers
         | 
| 81 72 | 
             
              end
         | 
| 82 73 | 
             
            end
         | 
| @@ -2,39 +2,6 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            module ActiveRecord::Associations::Builder # :nodoc:
         | 
| 4 4 | 
             
              class HasAndBelongsToMany # :nodoc:
         | 
| 5 | 
            -
                class JoinTableResolver # :nodoc:
         | 
| 6 | 
            -
                  KnownTable = Struct.new :join_table
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                  class KnownClass # :nodoc:
         | 
| 9 | 
            -
                    def initialize(lhs_class, rhs_class_name)
         | 
| 10 | 
            -
                      @lhs_class      = lhs_class
         | 
| 11 | 
            -
                      @rhs_class_name = rhs_class_name
         | 
| 12 | 
            -
                      @join_table     = nil
         | 
| 13 | 
            -
                    end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                    def join_table
         | 
| 16 | 
            -
                      @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
         | 
| 17 | 
            -
                    end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                    private
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                      def klass
         | 
| 22 | 
            -
                        @lhs_class.send(:compute_type, @rhs_class_name)
         | 
| 23 | 
            -
                      end
         | 
| 24 | 
            -
                  end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                  def self.build(lhs_class, name, options)
         | 
| 27 | 
            -
                    if options[:join_table]
         | 
| 28 | 
            -
                      KnownTable.new options[:join_table].to_s
         | 
| 29 | 
            -
                    else
         | 
| 30 | 
            -
                      class_name = options.fetch(:class_name) {
         | 
| 31 | 
            -
                        name.to_s.camelize.singularize
         | 
| 32 | 
            -
                      }
         | 
| 33 | 
            -
                      KnownClass.new lhs_class, class_name.to_s
         | 
| 34 | 
            -
                    end
         | 
| 35 | 
            -
                  end
         | 
| 36 | 
            -
                end
         | 
| 37 | 
            -
             | 
| 38 5 | 
             
                attr_reader :lhs_model, :association_name, :options
         | 
| 39 6 |  | 
| 40 7 | 
             
                def initialize(association_name, lhs_model, options)
         | 
| @@ -44,8 +11,6 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 44 11 | 
             
                end
         | 
| 45 12 |  | 
| 46 13 | 
             
                def through_model
         | 
| 47 | 
            -
                  habtm = JoinTableResolver.build lhs_model, association_name, options
         | 
| 48 | 
            -
             | 
| 49 14 | 
             
                  join_model = Class.new(ActiveRecord::Base) {
         | 
| 50 15 | 
             
                    class << self
         | 
| 51 16 | 
             
                      attr_accessor :left_model
         | 
| @@ -56,7 +21,9 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 56 21 | 
             
                    end
         | 
| 57 22 |  | 
| 58 23 | 
             
                    def self.table_name
         | 
| 59 | 
            -
                       | 
| 24 | 
            +
                      # Table name needs to be resolved lazily
         | 
| 25 | 
            +
                      # because RHS class might not have been loaded
         | 
| 26 | 
            +
                      @table_name ||= table_name_resolver.call
         | 
| 60 27 | 
             
                    end
         | 
| 61 28 |  | 
| 62 29 | 
             
                    def self.compute_type(class_name)
         | 
| @@ -79,14 +46,13 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 79 46 | 
             
                    end
         | 
| 80 47 |  | 
| 81 48 | 
             
                    private
         | 
| 82 | 
            -
             | 
| 83 49 | 
             
                      def self.suppress_composite_primary_key(pk)
         | 
| 84 50 | 
             
                        pk unless pk.is_a?(Array)
         | 
| 85 51 | 
             
                      end
         | 
| 86 52 | 
             
                  }
         | 
| 87 53 |  | 
| 88 54 | 
             
                  join_model.name                = "HABTM_#{association_name.to_s.camelize}"
         | 
| 89 | 
            -
                  join_model.table_name_resolver =  | 
| 55 | 
            +
                  join_model.table_name_resolver = -> { table_name }
         | 
| 90 56 | 
             
                  join_model.left_model          = lhs_model
         | 
| 91 57 |  | 
| 92 58 | 
             
                  join_model.add_left_association :left_side, anonymous_class: lhs_model
         | 
| @@ -96,7 +62,7 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 96 62 |  | 
| 97 63 | 
             
                def middle_reflection(join_model)
         | 
| 98 64 | 
             
                  middle_name = [lhs_model.name.downcase.pluralize,
         | 
| 99 | 
            -
                                 association_name].join("_" | 
| 65 | 
            +
                                 association_name.to_s].sort.join("_").gsub("::", "_").to_sym
         | 
| 100 66 | 
             
                  middle_options = middle_options join_model
         | 
| 101 67 |  | 
| 102 68 | 
             
                  HasMany.create_reflection(lhs_model,
         | 
| @@ -106,17 +72,27 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 106 72 | 
             
                end
         | 
| 107 73 |  | 
| 108 74 | 
             
                private
         | 
| 109 | 
            -
             | 
| 110 75 | 
             
                  def middle_options(join_model)
         | 
| 111 76 | 
             
                    middle_options = {}
         | 
| 112 77 | 
             
                    middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
         | 
| 113 | 
            -
                    middle_options[:source] = join_model.left_reflection.name
         | 
| 114 78 | 
             
                    if options.key? :foreign_key
         | 
| 115 79 | 
             
                      middle_options[:foreign_key] = options[:foreign_key]
         | 
| 116 80 | 
             
                    end
         | 
| 117 81 | 
             
                    middle_options
         | 
| 118 82 | 
             
                  end
         | 
| 119 83 |  | 
| 84 | 
            +
                  def table_name
         | 
| 85 | 
            +
                    if options[:join_table]
         | 
| 86 | 
            +
                      options[:join_table].to_s
         | 
| 87 | 
            +
                    else
         | 
| 88 | 
            +
                      class_name = options.fetch(:class_name) {
         | 
| 89 | 
            +
                        association_name.to_s.camelize.singularize
         | 
| 90 | 
            +
                      }
         | 
| 91 | 
            +
                      klass = lhs_model.send(:compute_type, class_name.to_s)
         | 
| 92 | 
            +
                      [lhs_model.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
             | 
| 120 96 | 
             
                  def belongs_to_options(options)
         | 
| 121 97 | 
             
                    rhs_options = {}
         | 
| 122 98 |  | 
| @@ -7,11 +7,17 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 7 7 | 
             
                end
         | 
| 8 8 |  | 
| 9 9 | 
             
                def self.valid_options(options)
         | 
| 10 | 
            -
                  super + [: | 
| 10 | 
            +
                  valid = super + [:counter_cache, :join_table, :index_errors, :ensuring_owner_was]
         | 
| 11 | 
            +
                  valid += [:as, :foreign_type] if options[:as]
         | 
| 12 | 
            +
                  valid += [:through, :source, :source_type] if options[:through]
         | 
| 13 | 
            +
                  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
         | 
| 14 | 
            +
                  valid
         | 
| 11 15 | 
             
                end
         | 
| 12 16 |  | 
| 13 17 | 
             
                def self.valid_dependent_options
         | 
| 14 | 
            -
                  [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
         | 
| 18 | 
            +
                  [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception, :destroy_async]
         | 
| 15 19 | 
             
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                private_class_method :macro, :valid_options, :valid_dependent_options
         | 
| 16 22 | 
             
              end
         | 
| 17 23 | 
             
            end
         | 
| @@ -7,13 +7,20 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 7 7 | 
             
                end
         | 
| 8 8 |  | 
| 9 9 | 
             
                def self.valid_options(options)
         | 
| 10 | 
            -
                  valid = super | 
| 10 | 
            +
                  valid = super
         | 
| 11 | 
            +
                  valid += [:as, :foreign_type] if options[:as]
         | 
| 12 | 
            +
                  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
         | 
| 11 13 | 
             
                  valid += [:through, :source, :source_type] if options[:through]
         | 
| 12 14 | 
             
                  valid
         | 
| 13 15 | 
             
                end
         | 
| 14 16 |  | 
| 15 17 | 
             
                def self.valid_dependent_options
         | 
| 16 | 
            -
                  [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
         | 
| 18 | 
            +
                  [:destroy, :destroy_async, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def self.define_callbacks(model, reflection)
         | 
| 22 | 
            +
                  super
         | 
| 23 | 
            +
                  add_touch_callbacks(model, reflection) if reflection.options[:touch]
         | 
| 17 24 | 
             
                end
         | 
| 18 25 |  | 
| 19 26 | 
             
                def self.add_destroy_callbacks(model, reflection)
         | 
| @@ -26,5 +33,29 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 26 33 | 
             
                    model.validates_presence_of reflection.name, message: :required
         | 
| 27 34 | 
             
                  end
         | 
| 28 35 | 
             
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def self.touch_record(record, name, touch)
         | 
| 38 | 
            +
                  instance = record.send(name)
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  if instance&.persisted?
         | 
| 41 | 
            +
                    touch != true ?
         | 
| 42 | 
            +
                      instance.touch(touch) : instance.touch
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def self.add_touch_callbacks(model, reflection)
         | 
| 47 | 
            +
                  name  = reflection.name
         | 
| 48 | 
            +
                  touch = reflection.options[:touch]
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  callback = -> (record) { HasOne.touch_record(record, name, touch) }
         | 
| 51 | 
            +
                  model.after_create callback, if: :saved_changes?
         | 
| 52 | 
            +
                  model.after_create_commit { association(name).reset_negative_cache }
         | 
| 53 | 
            +
                  model.after_update callback, if: :saved_changes?
         | 
| 54 | 
            +
                  model.after_destroy callback
         | 
| 55 | 
            +
                  model.after_touch callback
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks,
         | 
| 59 | 
            +
                  :define_callbacks, :define_validations, :add_touch_callbacks
         | 
| 29 60 | 
             
              end
         | 
| 30 61 | 
             
            end
         | 
| @@ -5,7 +5,7 @@ | |
| 5 5 | 
             
            module ActiveRecord::Associations::Builder # :nodoc:
         | 
| 6 6 | 
             
              class SingularAssociation < Association #:nodoc:
         | 
| 7 7 | 
             
                def self.valid_options(options)
         | 
| 8 | 
            -
                  super + [: | 
| 8 | 
            +
                  super + [:required, :touch]
         | 
| 9 9 | 
             
                end
         | 
| 10 10 |  | 
| 11 11 | 
             
                def self.define_accessors(model, reflection)
         | 
| @@ -38,5 +38,7 @@ module ActiveRecord::Associations::Builder # :nodoc: | |
| 38 38 | 
             
                    end
         | 
| 39 39 | 
             
                  CODE
         | 
| 40 40 | 
             
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                private_class_method :valid_options, :define_accessors, :define_constructors
         | 
| 41 43 | 
             
              end
         | 
| 42 44 | 
             
            end
         | 
| @@ -56,7 +56,7 @@ module ActiveRecord | |
| 56 56 | 
             
                  def ids_writer(ids)
         | 
| 57 57 | 
             
                    primary_key = reflection.association_primary_key
         | 
| 58 58 | 
             
                    pk_type = klass.type_for_attribute(primary_key)
         | 
| 59 | 
            -
                    ids = Array(ids). | 
| 59 | 
            +
                    ids = Array(ids).compact_blank
         | 
| 60 60 | 
             
                    ids.map! { |i| pk_type.cast(i) }
         | 
| 61 61 |  | 
| 62 62 | 
             
                    records = klass.where(primary_key => ids).index_by do |r|
         | 
| @@ -101,11 +101,11 @@ module ActiveRecord | |
| 101 101 | 
             
                    end
         | 
| 102 102 | 
             
                  end
         | 
| 103 103 |  | 
| 104 | 
            -
                  def build(attributes =  | 
| 104 | 
            +
                  def build(attributes = nil, &block)
         | 
| 105 105 | 
             
                    if attributes.is_a?(Array)
         | 
| 106 106 | 
             
                      attributes.collect { |attr| build(attr, &block) }
         | 
| 107 107 | 
             
                    else
         | 
| 108 | 
            -
                      add_to_target(build_record(attributes, &block))
         | 
| 108 | 
            +
                      add_to_target(build_record(attributes, &block), replace: true)
         | 
| 109 109 | 
             
                    end
         | 
| 110 110 | 
             
                  end
         | 
| 111 111 |  | 
| @@ -211,9 +211,11 @@ module ActiveRecord | |
| 211 211 | 
             
                  def size
         | 
| 212 212 | 
             
                    if !find_target? || loaded?
         | 
| 213 213 | 
             
                      target.size
         | 
| 214 | 
            +
                    elsif @association_ids
         | 
| 215 | 
            +
                      @association_ids.size
         | 
| 214 216 | 
             
                    elsif !association_scope.group_values.empty?
         | 
| 215 217 | 
             
                      load_target.size
         | 
| 216 | 
            -
                    elsif !association_scope.distinct_value && target. | 
| 218 | 
            +
                    elsif !association_scope.distinct_value && !target.empty?
         | 
| 217 219 | 
             
                      unsaved_records = target.select(&:new_record?)
         | 
| 218 220 | 
             
                      unsaved_records.size + count_records
         | 
| 219 221 | 
             
                    else
         | 
| @@ -226,14 +228,14 @@ module ActiveRecord | |
| 226 228 | 
             
                  # If the collection has been loaded
         | 
| 227 229 | 
             
                  # it is equivalent to <tt>collection.size.zero?</tt>. If the
         | 
| 228 230 | 
             
                  # collection has not been loaded, it is equivalent to
         | 
| 229 | 
            -
                  # <tt | 
| 231 | 
            +
                  # <tt>!collection.exists?</tt>. If the collection has not already been
         | 
| 230 232 | 
             
                  # loaded and you are going to fetch the records anyway it is better to
         | 
| 231 233 | 
             
                  # check <tt>collection.length.zero?</tt>.
         | 
| 232 234 | 
             
                  def empty?
         | 
| 233 | 
            -
                    if loaded?
         | 
| 235 | 
            +
                    if loaded? || @association_ids || reflection.has_cached_counter?
         | 
| 234 236 | 
             
                      size.zero?
         | 
| 235 237 | 
             
                    else
         | 
| 236 | 
            -
                       | 
| 238 | 
            +
                      target.empty? && !scope.exists?
         | 
| 237 239 | 
             
                    end
         | 
| 238 240 | 
             
                  end
         | 
| 239 241 |  | 
| @@ -276,13 +278,24 @@ module ActiveRecord | |
| 276 278 | 
             
                    target
         | 
| 277 279 | 
             
                  end
         | 
| 278 280 |  | 
| 279 | 
            -
                  def add_to_target(record, skip_callbacks  | 
| 280 | 
            -
                    if association_scope.distinct_value
         | 
| 281 | 
            +
                  def add_to_target(record, skip_callbacks: false, replace: false, &block)
         | 
| 282 | 
            +
                    if replace || association_scope.distinct_value
         | 
| 281 283 | 
             
                      index = @target.index(record)
         | 
| 282 284 | 
             
                    end
         | 
| 283 285 | 
             
                    replace_on_target(record, index, skip_callbacks, &block)
         | 
| 284 286 | 
             
                  end
         | 
| 285 287 |  | 
| 288 | 
            +
                  def target=(record)
         | 
| 289 | 
            +
                    return super unless ActiveRecord::Base.has_many_inversing
         | 
| 290 | 
            +
             | 
| 291 | 
            +
                    case record
         | 
| 292 | 
            +
                    when Array
         | 
| 293 | 
            +
                      super
         | 
| 294 | 
            +
                    else
         | 
| 295 | 
            +
                      add_to_target(record, skip_callbacks: true, replace: true)
         | 
| 296 | 
            +
                    end
         | 
| 297 | 
            +
                  end
         | 
| 298 | 
            +
             | 
| 286 299 | 
             
                  def scope
         | 
| 287 300 | 
             
                    scope = super
         | 
| 288 301 | 
             
                    scope.none! if null_scope?
         | 
| @@ -295,28 +308,13 @@ module ActiveRecord | |
| 295 308 |  | 
| 296 309 | 
             
                  def find_from_target?
         | 
| 297 310 | 
             
                    loaded? ||
         | 
| 311 | 
            +
                      owner.strict_loading? ||
         | 
| 312 | 
            +
                      reflection.strict_loading? ||
         | 
| 298 313 | 
             
                      owner.new_record? ||
         | 
| 299 314 | 
             
                      target.any? { |record| record.new_record? || record.changed? }
         | 
| 300 315 | 
             
                  end
         | 
| 301 316 |  | 
| 302 317 | 
             
                  private
         | 
| 303 | 
            -
             | 
| 304 | 
            -
                    def find_target
         | 
| 305 | 
            -
                      scope = self.scope
         | 
| 306 | 
            -
                      return scope.to_a if skip_statement_cache?(scope)
         | 
| 307 | 
            -
             | 
| 308 | 
            -
                      conn = klass.connection
         | 
| 309 | 
            -
                      sc = reflection.association_scope_cache(conn, owner) do |params|
         | 
| 310 | 
            -
                        as = AssociationScope.create { params.bind }
         | 
| 311 | 
            -
                        target_scope.merge!(as.scope(self))
         | 
| 312 | 
            -
                      end
         | 
| 313 | 
            -
             | 
| 314 | 
            -
                      binds = AssociationScope.get_bind_values(owner, reflection.chain)
         | 
| 315 | 
            -
                      sc.execute(binds, conn) do |record|
         | 
| 316 | 
            -
                        set_inverse_instance(record)
         | 
| 317 | 
            -
                      end
         | 
| 318 | 
            -
                    end
         | 
| 319 | 
            -
             | 
| 320 318 | 
             
                    # We have some records loaded from the database (persisted) and some that are
         | 
| 321 319 | 
             
                    # in-memory (memory). The same record may be represented in the persisted array
         | 
| 322 320 | 
             
                    # and in the memory array.
         | 
| @@ -393,10 +391,12 @@ module ActiveRecord | |
| 393 391 | 
             
                    end
         | 
| 394 392 |  | 
| 395 393 | 
             
                    def remove_records(existing_records, records, method)
         | 
| 396 | 
            -
                       | 
| 394 | 
            +
                      catch(:abort) do
         | 
| 395 | 
            +
                        records.each { |record| callback(:before_remove, record) }
         | 
| 396 | 
            +
                      end || return
         | 
| 397 397 |  | 
| 398 398 | 
             
                      delete_records(existing_records, method) if existing_records.any?
         | 
| 399 | 
            -
                       | 
| 399 | 
            +
                      @target -= records
         | 
| 400 400 | 
             
                      @association_ids = nil
         | 
| 401 401 |  | 
| 402 402 | 
             
                      records.each { |record| callback(:after_remove, record) }
         | 
| @@ -449,7 +449,9 @@ module ActiveRecord | |
| 449 449 | 
             
                    end
         | 
| 450 450 |  | 
| 451 451 | 
             
                    def replace_on_target(record, index, skip_callbacks)
         | 
| 452 | 
            -
                       | 
| 452 | 
            +
                      catch(:abort) do
         | 
| 453 | 
            +
                        callback(:before_add, record)
         | 
| 454 | 
            +
                      end || return unless skip_callbacks
         | 
| 453 455 |  | 
| 454 456 | 
             
                      set_inverse_instance(record)
         | 
| 455 457 |  |