activerecord 5.2.3 → 6.1.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 +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
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
module Associations
|
5
7
|
# Implements the details of eager loading of Active Record associations.
|
@@ -58,7 +60,7 @@ module ActiveRecord
|
|
58
60
|
# == Parameters
|
59
61
|
# +records+ is an array of ActiveRecord::Base. This array needs not be flat,
|
60
62
|
# i.e. +records+ itself may also contain arrays of records. In any case,
|
61
|
-
# +preload_associations+ will preload
|
63
|
+
# +preload_associations+ will preload all associations records by
|
62
64
|
# flattening +records+.
|
63
65
|
#
|
64
66
|
# +associations+ specifies one or more associations that you want to
|
@@ -88,44 +90,46 @@ module ActiveRecord
|
|
88
90
|
if records.empty?
|
89
91
|
[]
|
90
92
|
else
|
91
|
-
records.uniq!
|
92
93
|
Array.wrap(associations).flat_map { |association|
|
93
94
|
preloaders_on association, records, preload_scope
|
94
95
|
}
|
95
96
|
end
|
96
97
|
end
|
97
98
|
|
98
|
-
|
99
|
+
def initialize(associate_by_default: true)
|
100
|
+
@associate_by_default = associate_by_default
|
101
|
+
end
|
99
102
|
|
103
|
+
private
|
100
104
|
# Loads all the given data into +records+ for the +association+.
|
101
|
-
def preloaders_on(association, records, scope)
|
105
|
+
def preloaders_on(association, records, scope, polymorphic_parent = false)
|
102
106
|
case association
|
103
107
|
when Hash
|
104
|
-
preloaders_for_hash(association, records, scope)
|
105
|
-
when Symbol
|
106
|
-
preloaders_for_one(association, records, scope)
|
107
|
-
when String
|
108
|
-
preloaders_for_one(association.to_sym, records, scope)
|
108
|
+
preloaders_for_hash(association, records, scope, polymorphic_parent)
|
109
|
+
when Symbol, String
|
110
|
+
preloaders_for_one(association, records, scope, polymorphic_parent)
|
109
111
|
else
|
110
112
|
raise ArgumentError, "#{association.inspect} was not recognized for preload"
|
111
113
|
end
|
112
114
|
end
|
113
115
|
|
114
|
-
def preloaders_for_hash(association, records, scope)
|
116
|
+
def preloaders_for_hash(association, records, scope, polymorphic_parent)
|
115
117
|
association.flat_map { |parent, child|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
118
|
+
grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
|
119
|
+
loaders = preloaders_for_reflection(reflection, reflection_records, scope)
|
120
|
+
recs = loaders.flat_map(&:preloaded_records).uniq
|
121
|
+
child_polymorphic_parent = reflection && reflection.options[:polymorphic]
|
122
|
+
loaders.concat Array.wrap(child).flat_map { |assoc|
|
123
|
+
preloaders_on assoc, recs, scope, child_polymorphic_parent
|
124
|
+
}
|
125
|
+
loaders
|
126
|
+
end
|
123
127
|
}
|
124
128
|
end
|
125
129
|
|
126
130
|
# Loads all the given data into +records+ for a singular +association+.
|
127
131
|
#
|
128
|
-
# Functions by instantiating a preloader class such as Preloader::
|
132
|
+
# Functions by instantiating a preloader class such as Preloader::Association and
|
129
133
|
# call the +run+ method for each passed in class in the +records+ argument.
|
130
134
|
#
|
131
135
|
# Not all records have the same class, so group then preload group on the reflection
|
@@ -135,41 +139,50 @@ module ActiveRecord
|
|
135
139
|
# Additionally, polymorphic belongs_to associations can have multiple associated
|
136
140
|
# classes, depending on the polymorphic_type field. So we group by the classes as
|
137
141
|
# well.
|
138
|
-
def preloaders_for_one(association, records, scope)
|
139
|
-
grouped_records(association, records
|
140
|
-
|
141
|
-
|
142
|
-
loader.run self
|
143
|
-
loader
|
142
|
+
def preloaders_for_one(association, records, scope, polymorphic_parent)
|
143
|
+
grouped_records(association, records, polymorphic_parent)
|
144
|
+
.flat_map do |reflection, reflection_records|
|
145
|
+
preloaders_for_reflection reflection, reflection_records, scope
|
144
146
|
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def preloaders_for_reflection(reflection, records, scope)
|
150
|
+
records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
|
151
|
+
preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run
|
145
152
|
end
|
146
153
|
end
|
147
154
|
|
148
|
-
def grouped_records(association, records)
|
155
|
+
def grouped_records(association, records, polymorphic_parent)
|
149
156
|
h = {}
|
150
157
|
records.each do |record|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
klasses = h[assoc.reflection] ||= {}
|
155
|
-
(klasses[assoc.klass] ||= []) << record
|
158
|
+
reflection = record.class._reflect_on_association(association)
|
159
|
+
next if polymorphic_parent && !reflection || !record.association(association).klass
|
160
|
+
(h[reflection] ||= []) << record
|
156
161
|
end
|
157
162
|
h
|
158
163
|
end
|
159
164
|
|
160
165
|
class AlreadyLoaded # :nodoc:
|
161
|
-
def initialize(klass, owners, reflection, preload_scope)
|
166
|
+
def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
|
162
167
|
@owners = owners
|
163
168
|
@reflection = reflection
|
164
169
|
end
|
165
170
|
|
166
|
-
def run
|
171
|
+
def run
|
172
|
+
self
|
173
|
+
end
|
167
174
|
|
168
175
|
def preloaded_records
|
169
|
-
|
176
|
+
@preloaded_records ||= records_by_owner.flat_map(&:last)
|
177
|
+
end
|
178
|
+
|
179
|
+
def records_by_owner
|
180
|
+
@records_by_owner ||= owners.index_with do |owner|
|
181
|
+
Array(owner.association(reflection.name).target)
|
182
|
+
end
|
170
183
|
end
|
171
184
|
|
172
|
-
|
185
|
+
private
|
173
186
|
attr_reader :owners, :reflection
|
174
187
|
end
|
175
188
|
|
@@ -177,7 +190,7 @@ module ActiveRecord
|
|
177
190
|
# and attach it to a relation. The class returned implements a `run` method
|
178
191
|
# that accepts a preloader.
|
179
192
|
def preloader_for(reflection, owners)
|
180
|
-
if owners.
|
193
|
+
if owners.all? { |o| o.association(reflection.name).loaded? }
|
181
194
|
return AlreadyLoaded
|
182
195
|
end
|
183
196
|
reflection.check_preloadable!
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
17
17
|
replace(record)
|
18
18
|
end
|
19
19
|
|
20
|
-
def build(attributes =
|
20
|
+
def build(attributes = nil, &block)
|
21
21
|
record = build_record(attributes, &block)
|
22
22
|
set_new_record(record)
|
23
23
|
record
|
@@ -26,7 +26,7 @@ module ActiveRecord
|
|
26
26
|
# Implements the reload reader method, e.g. foo.reload_bar for
|
27
27
|
# Foo.has_one :bar
|
28
28
|
def force_reload_reader
|
29
|
-
|
29
|
+
reload(true)
|
30
30
|
target
|
31
31
|
end
|
32
32
|
|
@@ -36,21 +36,7 @@ module ActiveRecord
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def find_target
|
39
|
-
|
40
|
-
return scope.take if skip_statement_cache?(scope)
|
41
|
-
|
42
|
-
conn = klass.connection
|
43
|
-
sc = reflection.association_scope_cache(conn, owner) do |params|
|
44
|
-
as = AssociationScope.create { params.bind }
|
45
|
-
target_scope.merge!(as.scope(self)).limit(1)
|
46
|
-
end
|
47
|
-
|
48
|
-
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
49
|
-
sc.execute(binds, conn) do |record|
|
50
|
-
set_inverse_instance record
|
51
|
-
end.first
|
52
|
-
rescue ::RangeError
|
53
|
-
nil
|
39
|
+
super.first
|
54
40
|
end
|
55
41
|
|
56
42
|
def replace(record)
|
@@ -32,7 +32,7 @@ module ActiveRecord
|
|
32
32
|
reflection.chain.drop(1).each do |reflection|
|
33
33
|
relation = reflection.klass.scope_for_association
|
34
34
|
scope.merge!(
|
35
|
-
relation.except(:select, :create_with, :includes, :preload, :joins, :
|
35
|
+
relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
|
36
36
|
)
|
37
37
|
end
|
38
38
|
scope
|
@@ -2,38 +2,116 @@
|
|
2
2
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
4
4
|
require "active_support/core_ext/string/conversions"
|
5
|
-
require "active_support/core_ext/module/remove_method"
|
6
|
-
require "active_record/errors"
|
7
5
|
|
8
6
|
module ActiveRecord
|
9
7
|
class AssociationNotFoundError < ConfigurationError #:nodoc:
|
8
|
+
attr_reader :record, :association_name
|
10
9
|
def initialize(record = nil, association_name = nil)
|
10
|
+
@record = record
|
11
|
+
@association_name = association_name
|
11
12
|
if record && association_name
|
12
13
|
super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
|
13
14
|
else
|
14
15
|
super("Association was not found.")
|
15
16
|
end
|
16
17
|
end
|
18
|
+
|
19
|
+
class Correction
|
20
|
+
def initialize(error)
|
21
|
+
@error = error
|
22
|
+
end
|
23
|
+
|
24
|
+
def corrections
|
25
|
+
if @error.association_name
|
26
|
+
maybe_these = @error.record.class.reflections.keys
|
27
|
+
|
28
|
+
maybe_these.sort_by { |n|
|
29
|
+
DidYouMean::Jaro.distance(@error.association_name.to_s, n)
|
30
|
+
}.reverse.first(4)
|
31
|
+
else
|
32
|
+
[]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# We may not have DYM, and DYM might not let us register error handlers
|
38
|
+
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
|
39
|
+
DidYouMean.correct_error(self, Correction)
|
40
|
+
end
|
17
41
|
end
|
18
42
|
|
19
43
|
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
|
44
|
+
attr_reader :reflection, :associated_class
|
20
45
|
def initialize(reflection = nil, associated_class = nil)
|
21
46
|
if reflection
|
47
|
+
@reflection = reflection
|
48
|
+
@associated_class = associated_class.nil? ? reflection.klass : associated_class
|
22
49
|
super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
|
23
50
|
else
|
24
51
|
super("Could not find the inverse association.")
|
25
52
|
end
|
26
53
|
end
|
54
|
+
|
55
|
+
class Correction
|
56
|
+
def initialize(error)
|
57
|
+
@error = error
|
58
|
+
end
|
59
|
+
|
60
|
+
def corrections
|
61
|
+
if @error.reflection && @error.associated_class
|
62
|
+
maybe_these = @error.associated_class.reflections.keys
|
63
|
+
|
64
|
+
maybe_these.sort_by { |n|
|
65
|
+
DidYouMean::Jaro.distance(@error.reflection.options[:inverse_of].to_s, n)
|
66
|
+
}.reverse.first(4)
|
67
|
+
else
|
68
|
+
[]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# We may not have DYM, and DYM might not let us register error handlers
|
74
|
+
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
|
75
|
+
DidYouMean.correct_error(self, Correction)
|
76
|
+
end
|
27
77
|
end
|
28
78
|
|
29
79
|
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
|
30
|
-
|
31
|
-
|
32
|
-
|
80
|
+
attr_reader :owner_class, :reflection
|
81
|
+
|
82
|
+
def initialize(owner_class = nil, reflection = nil)
|
83
|
+
if owner_class && reflection
|
84
|
+
@owner_class = owner_class
|
85
|
+
@reflection = reflection
|
86
|
+
super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class.name}")
|
33
87
|
else
|
34
88
|
super("Could not find the association.")
|
35
89
|
end
|
36
90
|
end
|
91
|
+
|
92
|
+
class Correction
|
93
|
+
def initialize(error)
|
94
|
+
@error = error
|
95
|
+
end
|
96
|
+
|
97
|
+
def corrections
|
98
|
+
if @error.reflection && @error.owner_class
|
99
|
+
maybe_these = @error.owner_class.reflections.keys
|
100
|
+
maybe_these -= [@error.reflection.name.to_s] # remove failing reflection
|
101
|
+
|
102
|
+
maybe_these.sort_by { |n|
|
103
|
+
DidYouMean::Jaro.distance(@error.reflection.options[:through].to_s, n)
|
104
|
+
}.reverse.first(4)
|
105
|
+
else
|
106
|
+
[]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# We may not have DYM, and DYM might not let us register error handlers
|
112
|
+
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
|
113
|
+
DidYouMean.correct_error(self, Correction)
|
114
|
+
end
|
37
115
|
end
|
38
116
|
|
39
117
|
class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
|
@@ -92,7 +170,7 @@ module ActiveRecord
|
|
92
170
|
through_reflection = reflection.through_reflection
|
93
171
|
source_reflection_names = reflection.source_reflection_names
|
94
172
|
source_associations = reflection.through_reflection.klass._reflections.keys
|
95
|
-
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or '
|
173
|
+
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')}?")
|
96
174
|
else
|
97
175
|
super("Could not find the source association(s).")
|
98
176
|
end
|
@@ -292,13 +370,13 @@ module ActiveRecord
|
|
292
370
|
#
|
293
371
|
# The project class now has the following methods (and more) to ease the traversal and
|
294
372
|
# manipulation of its relationships:
|
295
|
-
# * <tt>Project#portfolio
|
296
|
-
# * <tt>Project#project_manager
|
297
|
-
# * <tt>Project#milestones.empty
|
298
|
-
# <tt>Project#milestones.delete(milestone)
|
299
|
-
# <tt>Project#milestones.build
|
300
|
-
# * <tt>Project#categories.empty
|
301
|
-
# <tt>Project#categories.delete(category1)
|
373
|
+
# * <tt>Project#portfolio</tt>, <tt>Project#portfolio=(portfolio)</tt>, <tt>Project#reload_portfolio</tt>
|
374
|
+
# * <tt>Project#project_manager</tt>, <tt>Project#project_manager=(project_manager)</tt>, <tt>Project#reload_project_manager</tt>
|
375
|
+
# * <tt>Project#milestones.empty?</tt>, <tt>Project#milestones.size</tt>, <tt>Project#milestones</tt>, <tt>Project#milestones<<(milestone)</tt>,
|
376
|
+
# <tt>Project#milestones.delete(milestone)</tt>, <tt>Project#milestones.destroy(milestone)</tt>, <tt>Project#milestones.find(milestone_id)</tt>,
|
377
|
+
# <tt>Project#milestones.build</tt>, <tt>Project#milestones.create</tt>
|
378
|
+
# * <tt>Project#categories.empty?</tt>, <tt>Project#categories.size</tt>, <tt>Project#categories</tt>, <tt>Project#categories<<(category1)</tt>,
|
379
|
+
# <tt>Project#categories.delete(category1)</tt>, <tt>Project#categories.destroy(category1)</tt>
|
302
380
|
#
|
303
381
|
# === A word of warning
|
304
382
|
#
|
@@ -702,9 +780,9 @@ module ActiveRecord
|
|
702
780
|
# inverse detection only works on #has_many, #has_one, and
|
703
781
|
# #belongs_to associations.
|
704
782
|
#
|
705
|
-
#
|
706
|
-
#
|
707
|
-
#
|
783
|
+
# <tt>:foreign_key</tt> and <tt>:through</tt> options on the associations,
|
784
|
+
# or a custom scope, will also prevent the association's inverse
|
785
|
+
# from being found automatically.
|
708
786
|
#
|
709
787
|
# The automatic guessing of the inverse association uses a heuristic based
|
710
788
|
# on the name of the class, so it may not work for all associations,
|
@@ -1291,10 +1369,13 @@ module ActiveRecord
|
|
1291
1369
|
# similar callbacks may affect the <tt>:dependent</tt> behavior, and the
|
1292
1370
|
# <tt>:dependent</tt> behavior may affect other callbacks.
|
1293
1371
|
#
|
1372
|
+
# * <tt>nil</tt> do nothing (default).
|
1294
1373
|
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
|
1374
|
+
# * <tt>:destroy_async</tt> destroys all the associated objects in a background job.
|
1295
1375
|
# * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
|
1296
|
-
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+.
|
1297
|
-
#
|
1376
|
+
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Polymorphic type will also be nullified
|
1377
|
+
# on polymorphic associations. Callbacks are not executed.
|
1378
|
+
# * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there are any associated records.
|
1298
1379
|
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
|
1299
1380
|
#
|
1300
1381
|
# If using with the <tt>:through</tt> option, the association on the join model must be
|
@@ -1355,6 +1436,11 @@ module ActiveRecord
|
|
1355
1436
|
# Specifies a module or array of modules that will be extended into the association object returned.
|
1356
1437
|
# Useful for defining methods on associations, especially when they should be shared between multiple
|
1357
1438
|
# association objects.
|
1439
|
+
# [:strict_loading]
|
1440
|
+
# Enforces strict loading every time the associated record is loaded through this association.
|
1441
|
+
# [:ensuring_owner_was]
|
1442
|
+
# Specifies an instance method to be called on the owner. The method must return true in order for the
|
1443
|
+
# associated records to be deleted in a background job.
|
1358
1444
|
#
|
1359
1445
|
# Option examples:
|
1360
1446
|
# has_many :comments, -> { order("posted_on") }
|
@@ -1365,6 +1451,7 @@ module ActiveRecord
|
|
1365
1451
|
# has_many :tags, as: :taggable
|
1366
1452
|
# has_many :reports, -> { readonly }
|
1367
1453
|
# has_many :subscribers, through: :subscriptions, source: :user
|
1454
|
+
# has_many :comments, strict_loading: true
|
1368
1455
|
def has_many(name, scope = nil, **options, &extension)
|
1369
1456
|
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
|
1370
1457
|
Reflection.add_reflection self, name, reflection
|
@@ -1434,10 +1521,13 @@ module ActiveRecord
|
|
1434
1521
|
# Controls what happens to the associated object when
|
1435
1522
|
# its owner is destroyed:
|
1436
1523
|
#
|
1524
|
+
# * <tt>nil</tt> do nothing (default).
|
1437
1525
|
# * <tt>:destroy</tt> causes the associated object to also be destroyed
|
1526
|
+
# * <tt>:destroy_async</tt> causes all the associated object to be destroyed in a background job.
|
1438
1527
|
# * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
|
1439
|
-
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+.
|
1440
|
-
#
|
1528
|
+
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Polymorphic type column is also nullified
|
1529
|
+
# on polymorphic associations. Callbacks are not executed.
|
1530
|
+
# * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there is an associated record
|
1441
1531
|
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
|
1442
1532
|
#
|
1443
1533
|
# Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option.
|
@@ -1492,6 +1582,11 @@ module ActiveRecord
|
|
1492
1582
|
# When set to +true+, the association will also have its presence validated.
|
1493
1583
|
# This will validate the association itself, not the id. You can use
|
1494
1584
|
# +:inverse_of+ to avoid an extra query during validation.
|
1585
|
+
# [:strict_loading]
|
1586
|
+
# Enforces strict loading every time the associated record is loaded through this association.
|
1587
|
+
# [:ensuring_owner_was]
|
1588
|
+
# Specifies an instance method to be called on the owner. The method must return true in order for the
|
1589
|
+
# associated records to be deleted in a background job.
|
1495
1590
|
#
|
1496
1591
|
# Option examples:
|
1497
1592
|
# has_one :credit_card, dependent: :destroy # destroys the associated credit card
|
@@ -1504,6 +1599,7 @@ module ActiveRecord
|
|
1504
1599
|
# has_one :club, through: :membership
|
1505
1600
|
# has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
|
1506
1601
|
# has_one :credit_card, required: true
|
1602
|
+
# has_one :credit_card, strict_loading: true
|
1507
1603
|
def has_one(name, scope = nil, **options)
|
1508
1604
|
reflection = Builder::HasOne.build(self, name, scope, options)
|
1509
1605
|
Reflection.add_reflection self, name, reflection
|
@@ -1524,6 +1620,7 @@ module ActiveRecord
|
|
1524
1620
|
# Returns the associated object. +nil+ is returned if none is found.
|
1525
1621
|
# [association=(associate)]
|
1526
1622
|
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
|
1623
|
+
# No modification or deletion of existing records takes place.
|
1527
1624
|
# [build_association(attributes = {})]
|
1528
1625
|
# Returns a new object of the associated type that has been instantiated
|
1529
1626
|
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
|
@@ -1581,10 +1678,11 @@ module ActiveRecord
|
|
1581
1678
|
# association will use "taggable_type" as the default <tt>:foreign_type</tt>.
|
1582
1679
|
# [:primary_key]
|
1583
1680
|
# Specify the method that returns the primary key of associated object used for the association.
|
1584
|
-
# By default this is id
|
1681
|
+
# By default this is +id+.
|
1585
1682
|
# [:dependent]
|
1586
1683
|
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
|
1587
|
-
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
|
1684
|
+
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to
|
1685
|
+
# <tt>:destroy_async</tt>, the associated object is scheduled to be destroyed in a background job.
|
1588
1686
|
# This option should not be specified when #belongs_to is used in conjunction with
|
1589
1687
|
# a #has_many relationship on another class because of the potential to leave
|
1590
1688
|
# orphaned records behind.
|
@@ -1636,6 +1734,11 @@ module ActiveRecord
|
|
1636
1734
|
# [:default]
|
1637
1735
|
# Provide a callable (i.e. proc or lambda) to specify that the association should
|
1638
1736
|
# be initialized with a particular record before validation.
|
1737
|
+
# [:strict_loading]
|
1738
|
+
# Enforces strict loading every time the associated record is loaded through this association.
|
1739
|
+
# [:ensuring_owner_was]
|
1740
|
+
# Specifies an instance method to be called on the owner. The method must return true in order for the
|
1741
|
+
# associated records to be deleted in a background job.
|
1639
1742
|
#
|
1640
1743
|
# Option examples:
|
1641
1744
|
# belongs_to :firm, foreign_key: "client_of"
|
@@ -1650,6 +1753,7 @@ module ActiveRecord
|
|
1650
1753
|
# belongs_to :company, touch: :employees_last_updated_at
|
1651
1754
|
# belongs_to :user, optional: true
|
1652
1755
|
# belongs_to :account, default: -> { company.account }
|
1756
|
+
# belongs_to :account, strict_loading: true
|
1653
1757
|
def belongs_to(name, scope = nil, **options)
|
1654
1758
|
reflection = Builder::BelongsTo.build(self, name, scope, options)
|
1655
1759
|
Reflection.add_reflection self, name, reflection
|
@@ -1672,7 +1776,7 @@ module ActiveRecord
|
|
1672
1776
|
# The join table should not have a primary key or a model associated with it. You must manually generate the
|
1673
1777
|
# join table with a migration such as this:
|
1674
1778
|
#
|
1675
|
-
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[
|
1779
|
+
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[6.0]
|
1676
1780
|
# def change
|
1677
1781
|
# create_join_table :developers, :projects
|
1678
1782
|
# end
|
@@ -1761,6 +1865,7 @@ module ActiveRecord
|
|
1761
1865
|
# has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
|
1762
1866
|
# has_and_belongs_to_many :categories, ->(post) {
|
1763
1867
|
# where("default_category = ?", post.default_category)
|
1868
|
+
# }
|
1764
1869
|
#
|
1765
1870
|
# === Extensions
|
1766
1871
|
#
|
@@ -1811,6 +1916,8 @@ module ActiveRecord
|
|
1811
1916
|
#
|
1812
1917
|
# Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets
|
1813
1918
|
# <tt>:autosave</tt> to <tt>true</tt>.
|
1919
|
+
# [:strict_loading]
|
1920
|
+
# Enforces strict loading every time an associated record is loaded through this association.
|
1814
1921
|
#
|
1815
1922
|
# Option examples:
|
1816
1923
|
# has_and_belongs_to_many :projects
|
@@ -1818,6 +1925,7 @@ module ActiveRecord
|
|
1818
1925
|
# has_and_belongs_to_many :nations, class_name: "Country"
|
1819
1926
|
# has_and_belongs_to_many :categories, join_table: "prods_cats"
|
1820
1927
|
# has_and_belongs_to_many :categories, -> { readonly }
|
1928
|
+
# has_and_belongs_to_many :categories, strict_loading: true
|
1821
1929
|
def has_and_belongs_to_many(name, scope = nil, **options, &extension)
|
1822
1930
|
habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
|
1823
1931
|
|
@@ -1848,11 +1956,11 @@ module ActiveRecord
|
|
1848
1956
|
hm_options[:through] = middle_reflection.name
|
1849
1957
|
hm_options[:source] = join_model.right_reflection.name
|
1850
1958
|
|
1851
|
-
[:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k|
|
1959
|
+
[:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k|
|
1852
1960
|
hm_options[k] = options[k] if options.key? k
|
1853
1961
|
end
|
1854
1962
|
|
1855
|
-
has_many name, scope, hm_options, &extension
|
1963
|
+
has_many name, scope, **hm_options, &extension
|
1856
1964
|
_reflections[name.to_s].parent_reflection = habtm_reflection
|
1857
1965
|
end
|
1858
1966
|
end
|
@@ -4,26 +4,26 @@ require "active_model/forbidden_attributes_protection"
|
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
6
|
module AttributeAssignment
|
7
|
-
extend ActiveSupport::Concern
|
8
7
|
include ActiveModel::AttributeAssignment
|
9
8
|
|
10
9
|
private
|
11
|
-
|
12
10
|
def _assign_attributes(attributes)
|
13
|
-
multi_parameter_attributes
|
14
|
-
nested_parameter_attributes = {}
|
11
|
+
multi_parameter_attributes = nested_parameter_attributes = nil
|
15
12
|
|
16
13
|
attributes.each do |k, v|
|
17
|
-
|
18
|
-
|
14
|
+
key = k.to_s
|
15
|
+
|
16
|
+
if key.include?("(")
|
17
|
+
(multi_parameter_attributes ||= {})[key] = v
|
19
18
|
elsif v.is_a?(Hash)
|
20
|
-
nested_parameter_attributes[
|
19
|
+
(nested_parameter_attributes ||= {})[key] = v
|
20
|
+
else
|
21
|
+
_assign_attribute(key, v)
|
21
22
|
end
|
22
23
|
end
|
23
|
-
super(attributes)
|
24
24
|
|
25
|
-
assign_nested_parameter_attributes(nested_parameter_attributes)
|
26
|
-
assign_multiparameter_attributes(multi_parameter_attributes)
|
25
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
|
26
|
+
assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
|
27
27
|
end
|
28
28
|
|
29
29
|
# Assign any deferred nested attributes after the base attributes have been set.
|
@@ -46,16 +46,14 @@ module ActiveRecord
|
|
46
46
|
def execute_callstack_for_multiparameter_attributes(callstack)
|
47
47
|
errors = []
|
48
48
|
callstack.each do |name, values_with_empty_parameters|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
values = values_with_empty_parameters
|
54
|
-
end
|
55
|
-
send("#{name}=", values)
|
56
|
-
rescue => ex
|
57
|
-
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
49
|
+
if values_with_empty_parameters.each_value.all?(&:nil?)
|
50
|
+
values = nil
|
51
|
+
else
|
52
|
+
values = values_with_empty_parameters
|
58
53
|
end
|
54
|
+
send("#{name}=", values)
|
55
|
+
rescue => ex
|
56
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
59
57
|
end
|
60
58
|
unless errors.empty?
|
61
59
|
error_descriptions = errors.map(&:message).join(",")
|
@@ -29,7 +29,7 @@ module ActiveRecord
|
|
29
29
|
extend ActiveSupport::Concern
|
30
30
|
|
31
31
|
included do
|
32
|
-
attribute_method_suffix "_before_type_cast"
|
32
|
+
attribute_method_suffix "_before_type_cast", "_for_database"
|
33
33
|
attribute_method_suffix "_came_from_user?"
|
34
34
|
end
|
35
35
|
|
@@ -46,7 +46,10 @@ module ActiveRecord
|
|
46
46
|
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
47
47
|
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
|
48
48
|
def read_attribute_before_type_cast(attr_name)
|
49
|
-
|
49
|
+
name = attr_name.to_s
|
50
|
+
name = self.class.attribute_aliases[name] || name
|
51
|
+
|
52
|
+
attribute_before_type_cast(name)
|
50
53
|
end
|
51
54
|
|
52
55
|
# Returns a hash of attributes before typecasting and deserialization.
|
@@ -64,14 +67,17 @@ module ActiveRecord
|
|
64
67
|
end
|
65
68
|
|
66
69
|
private
|
70
|
+
# Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
|
71
|
+
def attribute_before_type_cast(attr_name)
|
72
|
+
@attributes[attr_name].value_before_type_cast
|
73
|
+
end
|
67
74
|
|
68
|
-
|
69
|
-
|
70
|
-
read_attribute_before_type_cast(attribute_name)
|
75
|
+
def attribute_for_database(attr_name)
|
76
|
+
@attributes[attr_name].value_for_database
|
71
77
|
end
|
72
78
|
|
73
|
-
def attribute_came_from_user?(
|
74
|
-
@attributes[
|
79
|
+
def attribute_came_from_user?(attr_name)
|
80
|
+
@attributes[attr_name].came_from_user?
|
75
81
|
end
|
76
82
|
end
|
77
83
|
end
|