activerecord 6.1.7 → 7.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +520 -1385
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +17 -14
- data/lib/active_record/association_relation.rb +2 -12
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +60 -21
- data/lib/active_record/associations/association_scope.rb +17 -12
- data/lib/active_record/associations/belongs_to_association.rb +37 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
- data/lib/active_record/associations/builder/association.rb +11 -5
- data/lib/active_record/associations/builder/belongs_to.rb +41 -14
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +4 -4
- data/lib/active_record/associations/builder/singular_association.rb +6 -2
- data/lib/active_record/associations/collection_association.rb +46 -36
- data/lib/active_record/associations/collection_proxy.rb +44 -16
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +29 -19
- data/lib/active_record/associations/has_many_through_association.rb +12 -7
- data/lib/active_record/associations/has_one_association.rb +20 -10
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +23 -15
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +212 -53
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +50 -16
- data/lib/active_record/associations/preloader.rb +50 -121
- data/lib/active_record/associations/singular_association.rb +15 -3
- data/lib/active_record/associations/through_association.rb +25 -14
- data/lib/active_record/associations.rb +404 -509
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +2 -14
- data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +73 -22
- data/lib/active_record/attribute_methods/primary_key.rb +47 -27
- data/lib/active_record/attribute_methods/query.rb +31 -19
- data/lib/active_record/attribute_methods/read.rb +14 -11
- data/lib/active_record/attribute_methods/serialization.rb +174 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -9
- data/lib/active_record/attribute_methods/write.rb +12 -15
- data/lib/active_record/attribute_methods.rb +164 -52
- data/lib/active_record/attributes.rb +51 -49
- data/lib/active_record/autosave_association.rb +74 -57
- data/lib/active_record/base.rb +27 -5
- data/lib/active_record/callbacks.rb +18 -34
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -46
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +327 -612
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -64
- data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +377 -142
- data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
- data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +345 -166
- data/lib/active_record/connection_adapters/column.rb +13 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
- data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
- data/lib/active_record/connection_adapters/pool_config.rb +26 -16
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +401 -77
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
- data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +130 -6
- data/lib/active_record/connection_handling.rb +132 -146
- data/lib/active_record/core.rb +276 -251
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
- data/lib/active_record/database_configurations/database_config.rb +34 -10
- data/lib/active_record/database_configurations/hash_config.rb +107 -31
- data/lib/active_record/database_configurations/url_config.rb +38 -13
- data/lib/active_record/database_configurations.rb +96 -60
- data/lib/active_record/delegated_type.rb +90 -20
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +4 -2
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +3 -3
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +170 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +100 -0
- data/lib/active_record/encryption.rb +56 -0
- data/lib/active_record/enum.rb +163 -63
- data/lib/active_record/errors.rb +210 -27
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +15 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +70 -14
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +179 -112
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +85 -31
- data/lib/active_record/insert_all.rb +148 -32
- data/lib/active_record/integration.rb +14 -10
- data/lib/active_record/internal_metadata.rb +123 -23
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +43 -27
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +41 -29
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
- data/lib/active_record/middleware/database_selector.rb +23 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +113 -16
- data/lib/active_record/migration/compatibility.rb +235 -46
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +374 -177
- data/lib/active_record/model_schema.rb +143 -159
- data/lib/active_record/nested_attributes.rb +48 -21
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +282 -283
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +189 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +44 -9
- data/lib/active_record/railtie.rb +234 -71
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +189 -256
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +41 -3
- data/lib/active_record/reflection.rb +325 -103
- data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +300 -111
- data/lib/active_record/relation/delegation.rb +33 -22
- data/lib/active_record/relation/finder_methods.rb +123 -52
- data/lib/active_record/relation/merger.rb +26 -19
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +29 -22
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +842 -150
- data/lib/active_record/relation/record_fetch_warning.rb +10 -9
- data/lib/active_record/relation/spawn_methods.rb +7 -6
- data/lib/active_record/relation/where_clause.rb +15 -36
- data/lib/active_record/relation.rb +736 -145
- data/lib/active_record/result.rb +67 -54
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +84 -34
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +90 -31
- data/lib/active_record/schema_migration.rb +74 -23
- data/lib/active_record/scoping/default.rb +72 -15
- data/lib/active_record/scoping/named.rb +5 -13
- data/lib/active_record/scoping.rb +65 -34
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +6 -1
- data/lib/active_record/signed_id.rb +30 -9
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +10 -10
- data/lib/active_record/suppressor.rb +13 -15
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +277 -149
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
- data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +173 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +32 -19
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +118 -41
- data/lib/active_record/translation.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +32 -14
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +9 -7
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +13 -7
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +64 -15
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +444 -32
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/crud.rb +28 -22
- data/lib/arel/delete_manager.rb +18 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/casted.rb +1 -1
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +7 -2
- data/lib/arel/predications.rb +14 -4
- data/lib/arel/select_manager.rb +11 -5
- data/lib/arel/table.rb +9 -6
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +20 -5
- data/lib/arel/visitors/dot.rb +81 -90
- data/lib/arel/visitors/mysql.rb +23 -5
- data/lib/arel/visitors/postgresql.rb +1 -22
- data/lib/arel/visitors/to_sql.rb +170 -36
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +23 -4
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +100 -14
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -67
@@ -10,6 +10,8 @@ module ActiveRecord
|
|
10
10
|
included do
|
11
11
|
class_attribute :_reflections, instance_writer: false, default: {}
|
12
12
|
class_attribute :aggregate_reflections, instance_writer: false, default: {}
|
13
|
+
class_attribute :automatic_scope_inversing, instance_writer: false, default: false
|
14
|
+
class_attribute :automatically_invert_plural_associations, instance_writer: false, default: false
|
13
15
|
end
|
14
16
|
|
15
17
|
class << self
|
@@ -20,12 +22,12 @@ module ActiveRecord
|
|
20
22
|
|
21
23
|
def add_reflection(ar, name, reflection)
|
22
24
|
ar.clear_reflections_cache
|
23
|
-
name =
|
25
|
+
name = name.to_sym
|
24
26
|
ar._reflections = ar._reflections.except(name).merge!(name => reflection)
|
25
27
|
end
|
26
28
|
|
27
29
|
def add_aggregate_reflection(ar, name, reflection)
|
28
|
-
ar.aggregate_reflections = ar.aggregate_reflections.merge(
|
30
|
+
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection)
|
29
31
|
end
|
30
32
|
|
31
33
|
private
|
@@ -45,6 +47,8 @@ module ActiveRecord
|
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
50
|
+
# = Active Record Reflection
|
51
|
+
#
|
48
52
|
# \Reflection enables the ability to examine the associations and aggregations of
|
49
53
|
# Active Record classes and objects. This information, for example,
|
50
54
|
# can be used in a form builder that takes an Active Record object
|
@@ -64,7 +68,7 @@ module ActiveRecord
|
|
64
68
|
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
65
69
|
#
|
66
70
|
def reflect_on_aggregation(aggregation)
|
67
|
-
aggregate_reflections[aggregation.
|
71
|
+
aggregate_reflections[aggregation.to_sym]
|
68
72
|
end
|
69
73
|
|
70
74
|
# Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
|
@@ -72,6 +76,10 @@ module ActiveRecord
|
|
72
76
|
# Account.reflections # => {"balance" => AggregateReflection}
|
73
77
|
#
|
74
78
|
def reflections
|
79
|
+
normalized_reflections.stringify_keys
|
80
|
+
end
|
81
|
+
|
82
|
+
def normalized_reflections # :nodoc
|
75
83
|
@__reflections ||= begin
|
76
84
|
ref = {}
|
77
85
|
|
@@ -80,13 +88,13 @@ module ActiveRecord
|
|
80
88
|
|
81
89
|
if parent_reflection
|
82
90
|
parent_name = parent_reflection.name
|
83
|
-
ref[parent_name
|
91
|
+
ref[parent_name] = parent_reflection
|
84
92
|
else
|
85
93
|
ref[name] = reflection
|
86
94
|
end
|
87
95
|
end
|
88
96
|
|
89
|
-
ref
|
97
|
+
ref.freeze
|
90
98
|
end
|
91
99
|
end
|
92
100
|
|
@@ -101,7 +109,7 @@ module ActiveRecord
|
|
101
109
|
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
102
110
|
#
|
103
111
|
def reflect_on_all_associations(macro = nil)
|
104
|
-
association_reflections =
|
112
|
+
association_reflections = normalized_reflections.values
|
105
113
|
association_reflections.select! { |reflection| reflection.macro == macro } if macro
|
106
114
|
association_reflections
|
107
115
|
end
|
@@ -112,21 +120,31 @@ module ActiveRecord
|
|
112
120
|
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
113
121
|
#
|
114
122
|
def reflect_on_association(association)
|
115
|
-
|
123
|
+
normalized_reflections[association.to_sym]
|
116
124
|
end
|
117
125
|
|
118
|
-
def _reflect_on_association(association)
|
119
|
-
_reflections[association.
|
126
|
+
def _reflect_on_association(association) # :nodoc:
|
127
|
+
_reflections[association.to_sym]
|
120
128
|
end
|
121
129
|
|
122
130
|
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
123
131
|
def reflect_on_all_autosave_associations
|
124
|
-
reflections
|
132
|
+
reflections = normalized_reflections.values
|
133
|
+
reflections.select! { |reflection| reflection.options[:autosave] }
|
134
|
+
reflections
|
125
135
|
end
|
126
136
|
|
127
137
|
def clear_reflections_cache # :nodoc:
|
128
138
|
@__reflections = nil
|
129
139
|
end
|
140
|
+
|
141
|
+
private
|
142
|
+
def inherited(subclass)
|
143
|
+
super
|
144
|
+
subclass.class_eval do
|
145
|
+
@__reflections = nil
|
146
|
+
end
|
147
|
+
end
|
130
148
|
end
|
131
149
|
|
132
150
|
# Holds all the methods that are shared between MacroReflection and ThroughReflection.
|
@@ -143,6 +161,14 @@ module ActiveRecord
|
|
143
161
|
# PolymorphicReflection
|
144
162
|
# RuntimeReflection
|
145
163
|
class AbstractReflection # :nodoc:
|
164
|
+
def initialize
|
165
|
+
@class_name = nil
|
166
|
+
@counter_cache_column = nil
|
167
|
+
@inverse_of = nil
|
168
|
+
@inverse_which_updates_counter_cache_defined = false
|
169
|
+
@inverse_which_updates_counter_cache = nil
|
170
|
+
end
|
171
|
+
|
146
172
|
def through_reflection?
|
147
173
|
false
|
148
174
|
end
|
@@ -182,10 +208,14 @@ module ActiveRecord
|
|
182
208
|
|
183
209
|
scope_chain_items.inject(klass_scope, &:merge!)
|
184
210
|
|
185
|
-
|
186
|
-
|
211
|
+
primary_key_column_names = Array(join_primary_key)
|
212
|
+
foreign_key_column_names = Array(join_foreign_key)
|
213
|
+
|
214
|
+
primary_foreign_key_pairs = primary_key_column_names.zip(foreign_key_column_names)
|
187
215
|
|
188
|
-
|
216
|
+
primary_foreign_key_pairs.each do |primary_key_column_name, foreign_key_column_name|
|
217
|
+
klass_scope.where!(table[primary_key_column_name].eq(foreign_table[foreign_key_column_name]))
|
218
|
+
end
|
189
219
|
|
190
220
|
if klass.finder_needs_type_condition?
|
191
221
|
klass_scope.where!(klass.send(:type_condition, table))
|
@@ -194,9 +224,9 @@ module ActiveRecord
|
|
194
224
|
klass_scope
|
195
225
|
end
|
196
226
|
|
197
|
-
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
|
227
|
+
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
198
228
|
if scope
|
199
|
-
[scope_for(build_scope(table, predicate_builder, klass))]
|
229
|
+
[scope_for(build_scope(table, predicate_builder, klass), record)]
|
200
230
|
else
|
201
231
|
[]
|
202
232
|
end
|
@@ -212,14 +242,16 @@ module ActiveRecord
|
|
212
242
|
end
|
213
243
|
|
214
244
|
def counter_cache_column
|
215
|
-
@counter_cache_column ||=
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
245
|
+
@counter_cache_column ||= begin
|
246
|
+
counter_cache = options[:counter_cache]
|
247
|
+
|
248
|
+
if belongs_to?
|
249
|
+
if counter_cache
|
250
|
+
counter_cache[:column] || -"#{active_record.name.demodulize.underscore.pluralize}_count"
|
251
|
+
end
|
252
|
+
else
|
253
|
+
-((counter_cache && -counter_cache[:column]) || "#{name}_count")
|
220
254
|
end
|
221
|
-
else
|
222
|
-
-(options[:counter_cache]&.to_s || "#{name}_count")
|
223
255
|
end
|
224
256
|
end
|
225
257
|
|
@@ -230,14 +262,17 @@ module ActiveRecord
|
|
230
262
|
end
|
231
263
|
|
232
264
|
def check_validity_of_inverse!
|
233
|
-
|
234
|
-
if
|
265
|
+
if !polymorphic? && has_inverse?
|
266
|
+
if inverse_of.nil?
|
235
267
|
raise InverseOfAssociationNotFoundError.new(self)
|
236
268
|
end
|
269
|
+
if inverse_of == self
|
270
|
+
raise InverseOfAssociationRecursiveError.new(self)
|
271
|
+
end
|
237
272
|
end
|
238
273
|
end
|
239
274
|
|
240
|
-
#
|
275
|
+
# We need to avoid the following situation:
|
241
276
|
#
|
242
277
|
# * An associated record is deleted via record.destroy
|
243
278
|
# * Hence the callbacks run, and they find a belongs_to on the record with a
|
@@ -248,10 +283,16 @@ module ActiveRecord
|
|
248
283
|
#
|
249
284
|
# Hence this method.
|
250
285
|
def inverse_which_updates_counter_cache
|
251
|
-
|
252
|
-
|
253
|
-
|
286
|
+
unless @inverse_which_updates_counter_cache_defined
|
287
|
+
if counter_cache_column
|
288
|
+
inverse_candidates = inverse_of ? [inverse_of] : klass.reflect_on_all_associations(:belongs_to)
|
289
|
+
@inverse_which_updates_counter_cache = inverse_candidates.find do |inverse|
|
290
|
+
inverse.counter_cache_column == counter_cache_column && (inverse.polymorphic? || inverse.klass == active_record)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
@inverse_which_updates_counter_cache_defined = true
|
254
294
|
end
|
295
|
+
@inverse_which_updates_counter_cache
|
255
296
|
end
|
256
297
|
alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
|
257
298
|
|
@@ -259,7 +300,7 @@ module ActiveRecord
|
|
259
300
|
inverse_of && inverse_which_updates_counter_cache == inverse_of
|
260
301
|
end
|
261
302
|
|
262
|
-
# Returns whether a counter cache
|
303
|
+
# Returns whether this association has a counter cache.
|
263
304
|
#
|
264
305
|
# The counter_cache option must be given on either the owner or inverse
|
265
306
|
# association, and the column must be present on the owner.
|
@@ -269,6 +310,17 @@ module ActiveRecord
|
|
269
310
|
active_record.has_attribute?(counter_cache_column)
|
270
311
|
end
|
271
312
|
|
313
|
+
# Returns whether this association has a counter cache and its column values were backfilled
|
314
|
+
# (and so it is used internally by methods like +size+/+any?+/etc).
|
315
|
+
def has_active_cached_counter?
|
316
|
+
return false unless has_cached_counter?
|
317
|
+
|
318
|
+
counter_cache = options[:counter_cache] ||
|
319
|
+
(inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache])
|
320
|
+
|
321
|
+
counter_cache[:active] != false
|
322
|
+
end
|
323
|
+
|
272
324
|
def counter_must_be_updated_by_has_many?
|
273
325
|
!inverse_updates_counter_in_memory? && has_cached_counter?
|
274
326
|
end
|
@@ -293,6 +345,12 @@ module ActiveRecord
|
|
293
345
|
options[:strict_loading]
|
294
346
|
end
|
295
347
|
|
348
|
+
def strict_loading_violation_message(owner)
|
349
|
+
message = +"`#{owner}` is marked for strict_loading."
|
350
|
+
message << " The #{polymorphic? ? "polymorphic association" : "#{klass} association"}"
|
351
|
+
message << " named `:#{name}` cannot be lazily loaded."
|
352
|
+
end
|
353
|
+
|
296
354
|
protected
|
297
355
|
def actual_source_reflection # FIXME: this is a horrible name
|
298
356
|
self
|
@@ -306,6 +364,12 @@ module ActiveRecord
|
|
306
364
|
def primary_key(klass)
|
307
365
|
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
308
366
|
end
|
367
|
+
|
368
|
+
def ensure_option_not_given_as_class!(option_name)
|
369
|
+
if options[option_name] && options[option_name].class == Class
|
370
|
+
raise ArgumentError, "A class was passed to `:#{option_name}` but we are expecting a string."
|
371
|
+
end
|
372
|
+
end
|
309
373
|
end
|
310
374
|
|
311
375
|
# Base class for AggregateReflection and AssociationReflection. Objects of
|
@@ -330,9 +394,10 @@ module ActiveRecord
|
|
330
394
|
attr_reader :plural_name # :nodoc:
|
331
395
|
|
332
396
|
def initialize(name, scope, options, active_record)
|
397
|
+
super()
|
333
398
|
@name = name
|
334
399
|
@scope = scope
|
335
|
-
@options = options
|
400
|
+
@options = normalize_options(options)
|
336
401
|
@active_record = active_record
|
337
402
|
@klass = options[:anonymous_class]
|
338
403
|
@plural_name = active_record.pluralize_table_names ?
|
@@ -363,13 +428,17 @@ module ActiveRecord
|
|
363
428
|
# a new association object. Use +build_association+ or +create_association+
|
364
429
|
# instead. This allows plugins to hook into association object creation.
|
365
430
|
def klass
|
366
|
-
@klass ||= compute_class(class_name)
|
431
|
+
@klass ||= compute_class(compute_name(class_name))
|
367
432
|
end
|
368
433
|
|
369
434
|
def compute_class(name)
|
370
435
|
name.constantize
|
371
436
|
end
|
372
437
|
|
438
|
+
def compute_name(name) # :nodoc:
|
439
|
+
active_record.name.demodulize == name ? "::#{name}" : name
|
440
|
+
end
|
441
|
+
|
373
442
|
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
374
443
|
# and +other_aggregation+ has an options hash assigned to it.
|
375
444
|
def ==(other_aggregation)
|
@@ -388,11 +457,31 @@ module ActiveRecord
|
|
388
457
|
def derive_class_name
|
389
458
|
name.to_s.camelize
|
390
459
|
end
|
460
|
+
|
461
|
+
def normalize_options(options)
|
462
|
+
counter_cache = options.delete(:counter_cache)
|
463
|
+
|
464
|
+
if counter_cache
|
465
|
+
active = true
|
466
|
+
|
467
|
+
case counter_cache
|
468
|
+
when String, Symbol
|
469
|
+
column = -counter_cache.to_s
|
470
|
+
when Hash
|
471
|
+
active = counter_cache.fetch(:active, true)
|
472
|
+
column = counter_cache[:column]&.to_s
|
473
|
+
end
|
474
|
+
|
475
|
+
options[:counter_cache] = { active: active, column: column }
|
476
|
+
end
|
477
|
+
|
478
|
+
options
|
479
|
+
end
|
391
480
|
end
|
392
481
|
|
393
482
|
# Holds all the metadata about an aggregation as it was specified in the
|
394
483
|
# Active Record class.
|
395
|
-
class AggregateReflection < MacroReflection
|
484
|
+
class AggregateReflection < MacroReflection # :nodoc:
|
396
485
|
def mapping
|
397
486
|
mapping = options[:mapping] || [name, name]
|
398
487
|
mapping.first.is_a?(Array) ? mapping : [mapping]
|
@@ -401,12 +490,29 @@ module ActiveRecord
|
|
401
490
|
|
402
491
|
# Holds all the metadata about an association as it was specified in the
|
403
492
|
# Active Record class.
|
404
|
-
class AssociationReflection < MacroReflection
|
493
|
+
class AssociationReflection < MacroReflection # :nodoc:
|
405
494
|
def compute_class(name)
|
406
495
|
if polymorphic?
|
407
496
|
raise ArgumentError, "Polymorphic associations do not support computing the class."
|
408
497
|
end
|
409
|
-
|
498
|
+
|
499
|
+
begin
|
500
|
+
klass = active_record.send(:compute_type, name)
|
501
|
+
rescue NameError => error
|
502
|
+
if error.name.match?(/(?:\A|::)#{name}\z/)
|
503
|
+
message = "Missing model class #{name} for the #{active_record}##{self.name} association."
|
504
|
+
message += " You can specify a different model class with the :class_name option." unless options[:class_name]
|
505
|
+
raise NameError.new(message, name)
|
506
|
+
else
|
507
|
+
raise
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
unless klass < ActiveRecord::Base
|
512
|
+
raise ArgumentError, "The #{name} model class for the #{active_record}##{self.name} association is not an ActiveRecord::Base subclass."
|
513
|
+
end
|
514
|
+
|
515
|
+
klass
|
410
516
|
end
|
411
517
|
|
412
518
|
attr_reader :type, :foreign_type
|
@@ -416,11 +522,23 @@ module ActiveRecord
|
|
416
522
|
super
|
417
523
|
@type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
|
418
524
|
@foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
|
419
|
-
@
|
525
|
+
@join_table = nil
|
526
|
+
@foreign_key = nil
|
527
|
+
@association_foreign_key = nil
|
528
|
+
@association_primary_key = nil
|
529
|
+
if options[:query_constraints]
|
530
|
+
ActiveRecord.deprecator.warn <<~MSG.squish
|
531
|
+
Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is deprecated.
|
532
|
+
To maintain current behavior, use the `foreign_key` option instead.
|
533
|
+
MSG
|
534
|
+
end
|
420
535
|
|
421
|
-
|
422
|
-
|
536
|
+
# If the foreign key is an array, set query constraints options and don't use the foreign key
|
537
|
+
if options[:foreign_key].is_a?(Array)
|
538
|
+
options[:query_constraints] = options.delete(:foreign_key)
|
423
539
|
end
|
540
|
+
|
541
|
+
ensure_option_not_given_as_class!(:class_name)
|
424
542
|
end
|
425
543
|
|
426
544
|
def association_scope_cache(klass, owner, &block)
|
@@ -428,19 +546,33 @@ module ActiveRecord
|
|
428
546
|
if polymorphic?
|
429
547
|
key = [key, owner._read_attribute(@foreign_type)]
|
430
548
|
end
|
431
|
-
klass.
|
432
|
-
|
433
|
-
|
434
|
-
def constructable? # :nodoc:
|
435
|
-
@constructable
|
549
|
+
klass.with_connection do |connection|
|
550
|
+
klass.cached_find_by_statement(connection, key, &block)
|
551
|
+
end
|
436
552
|
end
|
437
553
|
|
438
554
|
def join_table
|
439
555
|
@join_table ||= -(options[:join_table]&.to_s || derive_join_table)
|
440
556
|
end
|
441
557
|
|
442
|
-
def foreign_key
|
443
|
-
@foreign_key ||=
|
558
|
+
def foreign_key(infer_from_inverse_of: true)
|
559
|
+
@foreign_key ||= if options[:foreign_key]
|
560
|
+
if options[:foreign_key].is_a?(Array)
|
561
|
+
options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
|
562
|
+
else
|
563
|
+
options[:foreign_key].to_s.freeze
|
564
|
+
end
|
565
|
+
elsif options[:query_constraints]
|
566
|
+
options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
|
567
|
+
else
|
568
|
+
derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
|
569
|
+
|
570
|
+
if active_record.has_query_constraints?
|
571
|
+
derived_fk = derive_fk_query_constraints(derived_fk)
|
572
|
+
end
|
573
|
+
|
574
|
+
derived_fk
|
575
|
+
end
|
444
576
|
end
|
445
577
|
|
446
578
|
def association_foreign_key
|
@@ -452,36 +584,62 @@ module ActiveRecord
|
|
452
584
|
end
|
453
585
|
|
454
586
|
def active_record_primary_key
|
455
|
-
|
587
|
+
custom_primary_key = options[:primary_key]
|
588
|
+
@active_record_primary_key ||= if custom_primary_key
|
589
|
+
if custom_primary_key.is_a?(Array)
|
590
|
+
custom_primary_key.map { |pk| pk.to_s.freeze }.freeze
|
591
|
+
else
|
592
|
+
custom_primary_key.to_s.freeze
|
593
|
+
end
|
594
|
+
elsif active_record.has_query_constraints? || options[:query_constraints]
|
595
|
+
active_record.query_constraints_list
|
596
|
+
elsif active_record.composite_primary_key?
|
597
|
+
# If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
|
598
|
+
primary_key = primary_key(active_record)
|
599
|
+
primary_key.include?("id") ? "id" : primary_key.freeze
|
600
|
+
else
|
601
|
+
primary_key(active_record).freeze
|
602
|
+
end
|
456
603
|
end
|
457
604
|
|
458
605
|
def join_primary_key(klass = nil)
|
459
606
|
foreign_key
|
460
607
|
end
|
461
608
|
|
609
|
+
def join_primary_type
|
610
|
+
type
|
611
|
+
end
|
612
|
+
|
462
613
|
def join_foreign_key
|
463
614
|
active_record_primary_key
|
464
615
|
end
|
465
616
|
|
466
617
|
def check_validity!
|
467
618
|
check_validity_of_inverse!
|
619
|
+
|
620
|
+
if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
|
621
|
+
if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
|
622
|
+
raise CompositePrimaryKeyMismatchError.new(self)
|
623
|
+
elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
|
624
|
+
raise CompositePrimaryKeyMismatchError.new(self)
|
625
|
+
end
|
626
|
+
end
|
468
627
|
end
|
469
628
|
|
470
|
-
def
|
629
|
+
def check_eager_loadable!
|
471
630
|
return unless scope
|
472
631
|
|
473
632
|
unless scope.arity == 0
|
474
633
|
raise ArgumentError, <<-MSG.squish
|
475
634
|
The association scope '#{name}' is instance dependent (the scope
|
476
|
-
block takes an argument).
|
477
|
-
not supported.
|
635
|
+
block takes an argument). Eager loading instance dependent scopes
|
636
|
+
is not supported.
|
478
637
|
MSG
|
479
638
|
end
|
480
639
|
end
|
481
|
-
alias :check_eager_loadable! :check_preloadable!
|
482
640
|
|
483
641
|
def join_id_for(owner) # :nodoc:
|
484
|
-
owner
|
642
|
+
Array(join_foreign_key).map { |key| owner._read_attribute(key) }
|
485
643
|
end
|
486
644
|
|
487
645
|
def through_reflection
|
@@ -563,8 +721,9 @@ module ActiveRecord
|
|
563
721
|
options[:polymorphic]
|
564
722
|
end
|
565
723
|
|
566
|
-
|
567
|
-
|
724
|
+
def polymorphic_name
|
725
|
+
active_record.polymorphic_name
|
726
|
+
end
|
568
727
|
|
569
728
|
def add_as_source(seed)
|
570
729
|
seed
|
@@ -583,10 +742,6 @@ module ActiveRecord
|
|
583
742
|
end
|
584
743
|
|
585
744
|
private
|
586
|
-
def calculate_constructable(macro, options)
|
587
|
-
true
|
588
|
-
end
|
589
|
-
|
590
745
|
# Attempts to find the inverse association name automatically.
|
591
746
|
# If it cannot find a suitable inverse association name, it returns
|
592
747
|
# +nil+.
|
@@ -605,14 +760,20 @@ module ActiveRecord
|
|
605
760
|
|
606
761
|
begin
|
607
762
|
reflection = klass._reflect_on_association(inverse_name)
|
608
|
-
|
763
|
+
if !reflection && active_record.automatically_invert_plural_associations
|
764
|
+
plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
|
765
|
+
reflection = klass._reflect_on_association(plural_inverse_name)
|
766
|
+
end
|
767
|
+
rescue NameError => error
|
768
|
+
raise unless error.name.to_s == class_name
|
769
|
+
|
609
770
|
# Give up: we couldn't compute the klass type so we won't be able
|
610
771
|
# to find any associations either.
|
611
772
|
reflection = false
|
612
773
|
end
|
613
774
|
|
614
775
|
if valid_inverse_reflection?(reflection)
|
615
|
-
|
776
|
+
reflection.name
|
616
777
|
end
|
617
778
|
end
|
618
779
|
end
|
@@ -623,9 +784,10 @@ module ActiveRecord
|
|
623
784
|
# with the current reflection's klass name.
|
624
785
|
def valid_inverse_reflection?(reflection)
|
625
786
|
reflection &&
|
787
|
+
reflection != self &&
|
626
788
|
foreign_key == reflection.foreign_key &&
|
627
789
|
klass <= reflection.active_record &&
|
628
|
-
can_find_inverse_of_automatically?(reflection)
|
790
|
+
can_find_inverse_of_automatically?(reflection, true)
|
629
791
|
end
|
630
792
|
|
631
793
|
# Checks to see if the reflection doesn't have any options that prevent
|
@@ -634,14 +796,25 @@ module ActiveRecord
|
|
634
796
|
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
|
635
797
|
# Third, we must not have options such as <tt>:foreign_key</tt>
|
636
798
|
# which prevent us from correctly guessing the inverse association.
|
637
|
-
|
638
|
-
# Anything with a scope can additionally ruin our attempt at finding an
|
639
|
-
# inverse, so we exclude reflections with scopes.
|
640
|
-
def can_find_inverse_of_automatically?(reflection)
|
799
|
+
def can_find_inverse_of_automatically?(reflection, inverse_reflection = false)
|
641
800
|
reflection.options[:inverse_of] != false &&
|
642
|
-
|
643
|
-
!
|
801
|
+
!reflection.options[:through] &&
|
802
|
+
!reflection.options[:foreign_key] &&
|
803
|
+
scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
|
804
|
+
end
|
805
|
+
|
806
|
+
# Scopes on the potential inverse reflection prevent automatic
|
807
|
+
# <tt>inverse_of</tt>, since the scope could exclude the owner record
|
808
|
+
# we would inverse from. Scopes on the reflection itself allow for
|
809
|
+
# automatic <tt>inverse_of</tt> as long as
|
810
|
+
# <tt>config.active_record.automatic_scope_inversing<tt> is set to
|
811
|
+
# +true+ (the default for new applications).
|
812
|
+
def scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
|
813
|
+
if inverse_reflection
|
644
814
|
!reflection.scope
|
815
|
+
else
|
816
|
+
!reflection.scope || reflection.klass.automatic_scope_inversing
|
817
|
+
end
|
645
818
|
end
|
646
819
|
|
647
820
|
def derive_class_name
|
@@ -650,13 +823,55 @@ module ActiveRecord
|
|
650
823
|
class_name.camelize
|
651
824
|
end
|
652
825
|
|
653
|
-
def derive_foreign_key
|
826
|
+
def derive_foreign_key(infer_from_inverse_of: true)
|
654
827
|
if belongs_to?
|
655
828
|
"#{name}_id"
|
656
829
|
elsif options[:as]
|
657
830
|
"#{options[:as]}_id"
|
831
|
+
elsif options[:inverse_of] && infer_from_inverse_of
|
832
|
+
inverse_of.foreign_key(infer_from_inverse_of: false)
|
833
|
+
else
|
834
|
+
active_record.model_name.to_s.foreign_key
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
def derive_fk_query_constraints(foreign_key)
|
839
|
+
primary_query_constraints = active_record.query_constraints_list
|
840
|
+
owner_pk = active_record.primary_key
|
841
|
+
|
842
|
+
if primary_query_constraints.size > 2
|
843
|
+
raise ArgumentError, <<~MSG.squish
|
844
|
+
The query constraints list on the `#{active_record}` model has more than 2
|
845
|
+
attributes. Active Record is unable to derive the query constraints
|
846
|
+
for the association. You need to explicitly define the query constraints
|
847
|
+
for this association.
|
848
|
+
MSG
|
849
|
+
end
|
850
|
+
|
851
|
+
if !primary_query_constraints.include?(owner_pk)
|
852
|
+
raise ArgumentError, <<~MSG.squish
|
853
|
+
The query constraints on the `#{active_record}` model does not include the primary
|
854
|
+
key so Active Record is unable to derive the foreign key constraints for
|
855
|
+
the association. You need to explicitly define the query constraints for this
|
856
|
+
association.
|
857
|
+
MSG
|
858
|
+
end
|
859
|
+
|
860
|
+
return foreign_key if primary_query_constraints.include?(foreign_key)
|
861
|
+
|
862
|
+
first_key, last_key = primary_query_constraints
|
863
|
+
|
864
|
+
if first_key == owner_pk
|
865
|
+
[foreign_key, last_key.to_s]
|
866
|
+
elsif last_key == owner_pk
|
867
|
+
[first_key.to_s, foreign_key]
|
658
868
|
else
|
659
|
-
|
869
|
+
raise ArgumentError, <<~MSG.squish
|
870
|
+
Active Record couldn't correctly interpret the query constraints
|
871
|
+
for the `#{active_record}` model. The query constraints on `#{active_record}` are
|
872
|
+
`#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
|
873
|
+
You need to explicitly set the query constraints for this association.
|
874
|
+
MSG
|
660
875
|
end
|
661
876
|
end
|
662
877
|
|
@@ -691,11 +906,6 @@ module ActiveRecord
|
|
691
906
|
Associations::HasOneAssociation
|
692
907
|
end
|
693
908
|
end
|
694
|
-
|
695
|
-
private
|
696
|
-
def calculate_constructable(macro, options)
|
697
|
-
!options[:through]
|
698
|
-
end
|
699
909
|
end
|
700
910
|
|
701
911
|
class BelongsToReflection < AssociationReflection # :nodoc:
|
@@ -714,7 +924,17 @@ module ActiveRecord
|
|
714
924
|
# klass option is necessary to support loading polymorphic associations
|
715
925
|
def association_primary_key(klass = nil)
|
716
926
|
if primary_key = options[:primary_key]
|
717
|
-
@association_primary_key ||=
|
927
|
+
@association_primary_key ||= if primary_key.is_a?(Array)
|
928
|
+
primary_key.map { |pk| pk.to_s.freeze }.freeze
|
929
|
+
else
|
930
|
+
-primary_key.to_s
|
931
|
+
end
|
932
|
+
elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
|
933
|
+
(klass || self.klass).composite_query_constraints_list
|
934
|
+
elsif (klass || self.klass).composite_primary_key?
|
935
|
+
# If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
|
936
|
+
primary_key = (klass || self.klass).primary_key
|
937
|
+
primary_key.include?("id") ? "id" : primary_key
|
718
938
|
else
|
719
939
|
primary_key(klass || self.klass)
|
720
940
|
end
|
@@ -733,13 +953,9 @@ module ActiveRecord
|
|
733
953
|
end
|
734
954
|
|
735
955
|
private
|
736
|
-
def can_find_inverse_of_automatically?(
|
956
|
+
def can_find_inverse_of_automatically?(*)
|
737
957
|
!polymorphic? && super
|
738
958
|
end
|
739
|
-
|
740
|
-
def calculate_constructable(macro, options)
|
741
|
-
!polymorphic?
|
742
|
-
end
|
743
959
|
end
|
744
960
|
|
745
961
|
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
|
@@ -752,14 +968,17 @@ module ActiveRecord
|
|
752
968
|
|
753
969
|
# Holds all the metadata about a :through association as it was specified
|
754
970
|
# in the Active Record class.
|
755
|
-
class ThroughReflection < AbstractReflection
|
971
|
+
class ThroughReflection < AbstractReflection # :nodoc:
|
756
972
|
delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :type,
|
757
973
|
:active_record_primary_key, :join_foreign_key, to: :source_reflection
|
758
974
|
|
759
975
|
def initialize(delegate_reflection)
|
976
|
+
super()
|
760
977
|
@delegate_reflection = delegate_reflection
|
761
978
|
@klass = delegate_reflection.options[:anonymous_class]
|
762
979
|
@source_reflection_name = delegate_reflection.options[:source]
|
980
|
+
|
981
|
+
ensure_option_not_given_as_class!(:source_type)
|
763
982
|
end
|
764
983
|
|
765
984
|
def through_reflection?
|
@@ -767,7 +986,7 @@ module ActiveRecord
|
|
767
986
|
end
|
768
987
|
|
769
988
|
def klass
|
770
|
-
@klass ||= delegate_reflection.compute_class(class_name)
|
989
|
+
@klass ||= delegate_reflection.compute_class(compute_name(class_name))
|
771
990
|
end
|
772
991
|
|
773
992
|
# Returns the source of the through reflection. It checks both a singularized
|
@@ -788,6 +1007,8 @@ module ActiveRecord
|
|
788
1007
|
# # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
|
789
1008
|
#
|
790
1009
|
def source_reflection
|
1010
|
+
return unless source_reflection_name
|
1011
|
+
|
791
1012
|
through_reflection.klass._reflect_on_association(source_reflection_name)
|
792
1013
|
end
|
793
1014
|
|
@@ -840,8 +1061,8 @@ module ActiveRecord
|
|
840
1061
|
source_reflection.scopes + super
|
841
1062
|
end
|
842
1063
|
|
843
|
-
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
|
844
|
-
source_reflection.join_scopes(table, predicate_builder, klass) + super
|
1064
|
+
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
1065
|
+
source_reflection.join_scopes(table, predicate_builder, klass, record) + super
|
845
1066
|
end
|
846
1067
|
|
847
1068
|
def has_scope?
|
@@ -888,24 +1109,23 @@ module ActiveRecord
|
|
888
1109
|
end
|
889
1110
|
|
890
1111
|
def source_reflection_name # :nodoc:
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
1112
|
+
@source_reflection_name ||= begin
|
1113
|
+
names = [name.to_s.singularize, name].collect(&:to_sym).uniq
|
1114
|
+
names = names.find_all { |n|
|
1115
|
+
through_reflection.klass._reflect_on_association(n)
|
1116
|
+
}
|
1117
|
+
|
1118
|
+
if names.length > 1
|
1119
|
+
raise AmbiguousSourceReflectionForThroughAssociation.new(
|
1120
|
+
active_record.name,
|
1121
|
+
macro,
|
1122
|
+
name,
|
1123
|
+
options,
|
1124
|
+
source_reflection_names
|
1125
|
+
)
|
1126
|
+
end
|
1127
|
+
names.first
|
906
1128
|
end
|
907
|
-
|
908
|
-
@source_reflection_name = names.first
|
909
1129
|
end
|
910
1130
|
|
911
1131
|
def source_options
|
@@ -946,7 +1166,7 @@ module ActiveRecord
|
|
946
1166
|
end
|
947
1167
|
|
948
1168
|
if parent_reflection.nil?
|
949
|
-
reflections = active_record.
|
1169
|
+
reflections = active_record.normalized_reflections.keys
|
950
1170
|
|
951
1171
|
if reflections.index(through_reflection.name) > reflections.index(name)
|
952
1172
|
raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
|
@@ -1009,13 +1229,14 @@ module ActiveRecord
|
|
1009
1229
|
:name, :scope_for, to: :@reflection
|
1010
1230
|
|
1011
1231
|
def initialize(reflection, previous_reflection)
|
1232
|
+
super()
|
1012
1233
|
@reflection = reflection
|
1013
1234
|
@previous_reflection = previous_reflection
|
1014
1235
|
end
|
1015
1236
|
|
1016
|
-
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
|
1017
|
-
scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
|
1018
|
-
scopes << build_scope(table, predicate_builder, klass).instance_exec(
|
1237
|
+
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
1238
|
+
scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
|
1239
|
+
scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
|
1019
1240
|
end
|
1020
1241
|
|
1021
1242
|
def constraints
|
@@ -1034,6 +1255,7 @@ module ActiveRecord
|
|
1034
1255
|
delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
|
1035
1256
|
|
1036
1257
|
def initialize(reflection, association)
|
1258
|
+
super()
|
1037
1259
|
@reflection = reflection
|
1038
1260
|
@association = association
|
1039
1261
|
end
|