activerecord 6.1.7 → 7.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +616 -1290
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +17 -14
- data/lib/active_record/association_relation.rb +2 -12
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +60 -21
- data/lib/active_record/associations/association_scope.rb +17 -12
- data/lib/active_record/associations/belongs_to_association.rb +37 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
- data/lib/active_record/associations/builder/association.rb +11 -5
- data/lib/active_record/associations/builder/belongs_to.rb +41 -14
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +4 -4
- data/lib/active_record/associations/builder/singular_association.rb +6 -2
- data/lib/active_record/associations/collection_association.rb +46 -36
- data/lib/active_record/associations/collection_proxy.rb +44 -16
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +29 -19
- data/lib/active_record/associations/has_many_through_association.rb +19 -8
- data/lib/active_record/associations/has_one_association.rb +20 -10
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +28 -20
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +212 -53
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +50 -16
- data/lib/active_record/associations/preloader.rb +50 -121
- data/lib/active_record/associations/singular_association.rb +15 -3
- data/lib/active_record/associations/through_association.rb +25 -14
- data/lib/active_record/associations.rb +429 -522
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +73 -22
- data/lib/active_record/attribute_methods/primary_key.rb +47 -27
- data/lib/active_record/attribute_methods/query.rb +31 -19
- data/lib/active_record/attribute_methods/read.rb +14 -11
- data/lib/active_record/attribute_methods/serialization.rb +174 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +15 -9
- data/lib/active_record/attribute_methods/write.rb +12 -15
- data/lib/active_record/attribute_methods.rb +164 -52
- data/lib/active_record/attributes.rb +57 -54
- data/lib/active_record/autosave_association.rb +74 -57
- data/lib/active_record/base.rb +27 -5
- data/lib/active_record/callbacks.rb +19 -35
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -46
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +325 -604
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +230 -64
- data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +378 -143
- data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
- data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +348 -165
- data/lib/active_record/connection_adapters/column.rb +13 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
- data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
- data/lib/active_record/connection_adapters/pool_config.rb +26 -16
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +403 -77
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
- data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +130 -6
- data/lib/active_record/connection_handling.rb +132 -146
- data/lib/active_record/core.rb +310 -253
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
- data/lib/active_record/database_configurations/database_config.rb +34 -10
- data/lib/active_record/database_configurations/hash_config.rb +107 -31
- data/lib/active_record/database_configurations/url_config.rb +38 -13
- data/lib/active_record/database_configurations.rb +96 -60
- data/lib/active_record/delegated_type.rb +90 -20
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +4 -2
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +3 -3
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +170 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +100 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +170 -62
- data/lib/active_record/errors.rb +210 -27
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +15 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +70 -14
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +179 -112
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +85 -31
- data/lib/active_record/insert_all.rb +148 -32
- data/lib/active_record/integration.rb +14 -10
- data/lib/active_record/internal_metadata.rb +123 -23
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +43 -27
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +41 -29
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
- data/lib/active_record/middleware/database_selector.rb +23 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +113 -16
- data/lib/active_record/migration/compatibility.rb +235 -46
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +374 -177
- data/lib/active_record/model_schema.rb +145 -158
- data/lib/active_record/nested_attributes.rb +61 -23
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +282 -283
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +189 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +44 -9
- data/lib/active_record/railtie.rb +229 -71
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +189 -256
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +41 -3
- data/lib/active_record/reflection.rb +332 -103
- data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
- data/lib/active_record/relation/batches.rb +200 -65
- data/lib/active_record/relation/calculations.rb +301 -112
- data/lib/active_record/relation/delegation.rb +33 -22
- data/lib/active_record/relation/finder_methods.rb +123 -52
- data/lib/active_record/relation/merger.rb +26 -19
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +29 -22
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +870 -163
- data/lib/active_record/relation/record_fetch_warning.rb +10 -9
- data/lib/active_record/relation/spawn_methods.rb +7 -6
- data/lib/active_record/relation/where_clause.rb +15 -36
- data/lib/active_record/relation.rb +736 -145
- data/lib/active_record/result.rb +67 -54
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +84 -34
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +90 -31
- data/lib/active_record/schema_migration.rb +74 -23
- data/lib/active_record/scoping/default.rb +72 -15
- data/lib/active_record/scoping/named.rb +6 -13
- data/lib/active_record/scoping.rb +65 -34
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +6 -1
- data/lib/active_record/signed_id.rb +30 -9
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +10 -10
- data/lib/active_record/suppressor.rb +13 -15
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +288 -149
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
- data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +173 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +32 -19
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +118 -41
- data/lib/active_record/translation.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +32 -14
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +9 -7
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +13 -7
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +65 -15
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +444 -32
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/crud.rb +28 -22
- data/lib/arel/delete_manager.rb +18 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/casted.rb +1 -1
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +7 -2
- data/lib/arel/predications.rb +14 -4
- data/lib/arel/select_manager.rb +11 -5
- data/lib/arel/table.rb +9 -6
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +20 -5
- data/lib/arel/visitors/dot.rb +81 -90
- data/lib/arel/visitors/mysql.rb +23 -5
- data/lib/arel/visitors/postgresql.rb +1 -22
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +170 -36
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +23 -4
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +103 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -67
@@ -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,7 +428,15 @@ 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 ||=
|
431
|
+
@klass ||= _klass(class_name)
|
432
|
+
end
|
433
|
+
|
434
|
+
def _klass(class_name) # :nodoc:
|
435
|
+
if active_record.name.demodulize == class_name
|
436
|
+
return compute_class("::#{class_name}") rescue NameError
|
437
|
+
end
|
438
|
+
|
439
|
+
compute_class(class_name)
|
367
440
|
end
|
368
441
|
|
369
442
|
def compute_class(name)
|
@@ -388,11 +461,31 @@ module ActiveRecord
|
|
388
461
|
def derive_class_name
|
389
462
|
name.to_s.camelize
|
390
463
|
end
|
464
|
+
|
465
|
+
def normalize_options(options)
|
466
|
+
counter_cache = options.delete(:counter_cache)
|
467
|
+
|
468
|
+
if counter_cache
|
469
|
+
active = true
|
470
|
+
|
471
|
+
case counter_cache
|
472
|
+
when String, Symbol
|
473
|
+
column = -counter_cache.to_s
|
474
|
+
when Hash
|
475
|
+
active = counter_cache.fetch(:active, true)
|
476
|
+
column = counter_cache[:column]&.to_s
|
477
|
+
end
|
478
|
+
|
479
|
+
options[:counter_cache] = { active: active, column: column }
|
480
|
+
end
|
481
|
+
|
482
|
+
options
|
483
|
+
end
|
391
484
|
end
|
392
485
|
|
393
486
|
# Holds all the metadata about an aggregation as it was specified in the
|
394
487
|
# Active Record class.
|
395
|
-
class AggregateReflection < MacroReflection
|
488
|
+
class AggregateReflection < MacroReflection # :nodoc:
|
396
489
|
def mapping
|
397
490
|
mapping = options[:mapping] || [name, name]
|
398
491
|
mapping.first.is_a?(Array) ? mapping : [mapping]
|
@@ -401,12 +494,29 @@ module ActiveRecord
|
|
401
494
|
|
402
495
|
# Holds all the metadata about an association as it was specified in the
|
403
496
|
# Active Record class.
|
404
|
-
class AssociationReflection < MacroReflection
|
497
|
+
class AssociationReflection < MacroReflection # :nodoc:
|
405
498
|
def compute_class(name)
|
406
499
|
if polymorphic?
|
407
500
|
raise ArgumentError, "Polymorphic associations do not support computing the class."
|
408
501
|
end
|
409
|
-
|
502
|
+
|
503
|
+
begin
|
504
|
+
klass = active_record.send(:compute_type, name)
|
505
|
+
rescue NameError => error
|
506
|
+
if error.name.match?(/(?:\A|::)#{name}\z/)
|
507
|
+
message = "Missing model class #{name} for the #{active_record}##{self.name} association."
|
508
|
+
message += " You can specify a different model class with the :class_name option." unless options[:class_name]
|
509
|
+
raise NameError.new(message, name)
|
510
|
+
else
|
511
|
+
raise
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
unless klass < ActiveRecord::Base
|
516
|
+
raise ArgumentError, "The #{name} model class for the #{active_record}##{self.name} association is not an ActiveRecord::Base subclass."
|
517
|
+
end
|
518
|
+
|
519
|
+
klass
|
410
520
|
end
|
411
521
|
|
412
522
|
attr_reader :type, :foreign_type
|
@@ -416,11 +526,23 @@ module ActiveRecord
|
|
416
526
|
super
|
417
527
|
@type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
|
418
528
|
@foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
|
419
|
-
@
|
529
|
+
@join_table = nil
|
530
|
+
@foreign_key = nil
|
531
|
+
@association_foreign_key = nil
|
532
|
+
@association_primary_key = nil
|
533
|
+
if options[:query_constraints]
|
534
|
+
ActiveRecord.deprecator.warn <<~MSG.squish
|
535
|
+
Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is deprecated.
|
536
|
+
To maintain current behavior, use the `foreign_key` option instead.
|
537
|
+
MSG
|
538
|
+
end
|
420
539
|
|
421
|
-
|
422
|
-
|
540
|
+
# If the foreign key is an array, set query constraints options and don't use the foreign key
|
541
|
+
if options[:foreign_key].is_a?(Array)
|
542
|
+
options[:query_constraints] = options.delete(:foreign_key)
|
423
543
|
end
|
544
|
+
|
545
|
+
ensure_option_not_given_as_class!(:class_name)
|
424
546
|
end
|
425
547
|
|
426
548
|
def association_scope_cache(klass, owner, &block)
|
@@ -428,19 +550,33 @@ module ActiveRecord
|
|
428
550
|
if polymorphic?
|
429
551
|
key = [key, owner._read_attribute(@foreign_type)]
|
430
552
|
end
|
431
|
-
klass.
|
432
|
-
|
433
|
-
|
434
|
-
def constructable? # :nodoc:
|
435
|
-
@constructable
|
553
|
+
klass.with_connection do |connection|
|
554
|
+
klass.cached_find_by_statement(connection, key, &block)
|
555
|
+
end
|
436
556
|
end
|
437
557
|
|
438
558
|
def join_table
|
439
559
|
@join_table ||= -(options[:join_table]&.to_s || derive_join_table)
|
440
560
|
end
|
441
561
|
|
442
|
-
def foreign_key
|
443
|
-
@foreign_key ||=
|
562
|
+
def foreign_key(infer_from_inverse_of: true)
|
563
|
+
@foreign_key ||= if options[:foreign_key]
|
564
|
+
if options[:foreign_key].is_a?(Array)
|
565
|
+
options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
|
566
|
+
else
|
567
|
+
options[:foreign_key].to_s.freeze
|
568
|
+
end
|
569
|
+
elsif options[:query_constraints]
|
570
|
+
options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
|
571
|
+
else
|
572
|
+
derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
|
573
|
+
|
574
|
+
if active_record.has_query_constraints?
|
575
|
+
derived_fk = derive_fk_query_constraints(derived_fk)
|
576
|
+
end
|
577
|
+
|
578
|
+
derived_fk
|
579
|
+
end
|
444
580
|
end
|
445
581
|
|
446
582
|
def association_foreign_key
|
@@ -452,36 +588,62 @@ module ActiveRecord
|
|
452
588
|
end
|
453
589
|
|
454
590
|
def active_record_primary_key
|
455
|
-
|
591
|
+
custom_primary_key = options[:primary_key]
|
592
|
+
@active_record_primary_key ||= if custom_primary_key
|
593
|
+
if custom_primary_key.is_a?(Array)
|
594
|
+
custom_primary_key.map { |pk| pk.to_s.freeze }.freeze
|
595
|
+
else
|
596
|
+
custom_primary_key.to_s.freeze
|
597
|
+
end
|
598
|
+
elsif active_record.has_query_constraints? || options[:query_constraints]
|
599
|
+
active_record.query_constraints_list
|
600
|
+
elsif active_record.composite_primary_key?
|
601
|
+
# If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
|
602
|
+
primary_key = primary_key(active_record)
|
603
|
+
primary_key.include?("id") ? "id" : primary_key.freeze
|
604
|
+
else
|
605
|
+
primary_key(active_record).freeze
|
606
|
+
end
|
456
607
|
end
|
457
608
|
|
458
609
|
def join_primary_key(klass = nil)
|
459
610
|
foreign_key
|
460
611
|
end
|
461
612
|
|
613
|
+
def join_primary_type
|
614
|
+
type
|
615
|
+
end
|
616
|
+
|
462
617
|
def join_foreign_key
|
463
618
|
active_record_primary_key
|
464
619
|
end
|
465
620
|
|
466
621
|
def check_validity!
|
467
622
|
check_validity_of_inverse!
|
623
|
+
|
624
|
+
if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
|
625
|
+
if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
|
626
|
+
raise CompositePrimaryKeyMismatchError.new(self)
|
627
|
+
elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
|
628
|
+
raise CompositePrimaryKeyMismatchError.new(self)
|
629
|
+
end
|
630
|
+
end
|
468
631
|
end
|
469
632
|
|
470
|
-
def
|
633
|
+
def check_eager_loadable!
|
471
634
|
return unless scope
|
472
635
|
|
473
636
|
unless scope.arity == 0
|
474
637
|
raise ArgumentError, <<-MSG.squish
|
475
638
|
The association scope '#{name}' is instance dependent (the scope
|
476
|
-
block takes an argument).
|
477
|
-
not supported.
|
639
|
+
block takes an argument). Eager loading instance dependent scopes
|
640
|
+
is not supported.
|
478
641
|
MSG
|
479
642
|
end
|
480
643
|
end
|
481
|
-
alias :check_eager_loadable! :check_preloadable!
|
482
644
|
|
483
645
|
def join_id_for(owner) # :nodoc:
|
484
|
-
owner
|
646
|
+
Array(join_foreign_key).map { |key| owner._read_attribute(key) }
|
485
647
|
end
|
486
648
|
|
487
649
|
def through_reflection
|
@@ -563,8 +725,9 @@ module ActiveRecord
|
|
563
725
|
options[:polymorphic]
|
564
726
|
end
|
565
727
|
|
566
|
-
|
567
|
-
|
728
|
+
def polymorphic_name
|
729
|
+
active_record.polymorphic_name
|
730
|
+
end
|
568
731
|
|
569
732
|
def add_as_source(seed)
|
570
733
|
seed
|
@@ -583,10 +746,6 @@ module ActiveRecord
|
|
583
746
|
end
|
584
747
|
|
585
748
|
private
|
586
|
-
def calculate_constructable(macro, options)
|
587
|
-
true
|
588
|
-
end
|
589
|
-
|
590
749
|
# Attempts to find the inverse association name automatically.
|
591
750
|
# If it cannot find a suitable inverse association name, it returns
|
592
751
|
# +nil+.
|
@@ -605,14 +764,20 @@ module ActiveRecord
|
|
605
764
|
|
606
765
|
begin
|
607
766
|
reflection = klass._reflect_on_association(inverse_name)
|
608
|
-
|
767
|
+
if !reflection && active_record.automatically_invert_plural_associations
|
768
|
+
plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
|
769
|
+
reflection = klass._reflect_on_association(plural_inverse_name)
|
770
|
+
end
|
771
|
+
rescue NameError => error
|
772
|
+
raise unless error.name.to_s == class_name
|
773
|
+
|
609
774
|
# Give up: we couldn't compute the klass type so we won't be able
|
610
775
|
# to find any associations either.
|
611
776
|
reflection = false
|
612
777
|
end
|
613
778
|
|
614
779
|
if valid_inverse_reflection?(reflection)
|
615
|
-
|
780
|
+
reflection.name
|
616
781
|
end
|
617
782
|
end
|
618
783
|
end
|
@@ -623,9 +788,10 @@ module ActiveRecord
|
|
623
788
|
# with the current reflection's klass name.
|
624
789
|
def valid_inverse_reflection?(reflection)
|
625
790
|
reflection &&
|
791
|
+
reflection != self &&
|
626
792
|
foreign_key == reflection.foreign_key &&
|
627
793
|
klass <= reflection.active_record &&
|
628
|
-
can_find_inverse_of_automatically?(reflection)
|
794
|
+
can_find_inverse_of_automatically?(reflection, true)
|
629
795
|
end
|
630
796
|
|
631
797
|
# Checks to see if the reflection doesn't have any options that prevent
|
@@ -634,14 +800,25 @@ module ActiveRecord
|
|
634
800
|
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
|
635
801
|
# Third, we must not have options such as <tt>:foreign_key</tt>
|
636
802
|
# 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)
|
803
|
+
def can_find_inverse_of_automatically?(reflection, inverse_reflection = false)
|
641
804
|
reflection.options[:inverse_of] != false &&
|
642
|
-
|
643
|
-
!
|
805
|
+
!reflection.options[:through] &&
|
806
|
+
!reflection.options[:foreign_key] &&
|
807
|
+
scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
|
808
|
+
end
|
809
|
+
|
810
|
+
# Scopes on the potential inverse reflection prevent automatic
|
811
|
+
# <tt>inverse_of</tt>, since the scope could exclude the owner record
|
812
|
+
# we would inverse from. Scopes on the reflection itself allow for
|
813
|
+
# automatic <tt>inverse_of</tt> as long as
|
814
|
+
# <tt>config.active_record.automatic_scope_inversing<tt> is set to
|
815
|
+
# +true+ (the default for new applications).
|
816
|
+
def scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
|
817
|
+
if inverse_reflection
|
644
818
|
!reflection.scope
|
819
|
+
else
|
820
|
+
!reflection.scope || reflection.klass.automatic_scope_inversing
|
821
|
+
end
|
645
822
|
end
|
646
823
|
|
647
824
|
def derive_class_name
|
@@ -650,13 +827,55 @@ module ActiveRecord
|
|
650
827
|
class_name.camelize
|
651
828
|
end
|
652
829
|
|
653
|
-
def derive_foreign_key
|
830
|
+
def derive_foreign_key(infer_from_inverse_of: true)
|
654
831
|
if belongs_to?
|
655
832
|
"#{name}_id"
|
656
833
|
elsif options[:as]
|
657
834
|
"#{options[:as]}_id"
|
835
|
+
elsif options[:inverse_of] && infer_from_inverse_of
|
836
|
+
inverse_of.foreign_key(infer_from_inverse_of: false)
|
658
837
|
else
|
659
|
-
active_record.
|
838
|
+
active_record.model_name.to_s.foreign_key
|
839
|
+
end
|
840
|
+
end
|
841
|
+
|
842
|
+
def derive_fk_query_constraints(foreign_key)
|
843
|
+
primary_query_constraints = active_record.query_constraints_list
|
844
|
+
owner_pk = active_record.primary_key
|
845
|
+
|
846
|
+
if primary_query_constraints.size > 2
|
847
|
+
raise ArgumentError, <<~MSG.squish
|
848
|
+
The query constraints list on the `#{active_record}` model has more than 2
|
849
|
+
attributes. Active Record is unable to derive the query constraints
|
850
|
+
for the association. You need to explicitly define the query constraints
|
851
|
+
for this association.
|
852
|
+
MSG
|
853
|
+
end
|
854
|
+
|
855
|
+
if !primary_query_constraints.include?(owner_pk)
|
856
|
+
raise ArgumentError, <<~MSG.squish
|
857
|
+
The query constraints on the `#{active_record}` model does not include the primary
|
858
|
+
key so Active Record is unable to derive the foreign key constraints for
|
859
|
+
the association. You need to explicitly define the query constraints for this
|
860
|
+
association.
|
861
|
+
MSG
|
862
|
+
end
|
863
|
+
|
864
|
+
return foreign_key if primary_query_constraints.include?(foreign_key)
|
865
|
+
|
866
|
+
first_key, last_key = primary_query_constraints
|
867
|
+
|
868
|
+
if first_key == owner_pk
|
869
|
+
[foreign_key, last_key.to_s]
|
870
|
+
elsif last_key == owner_pk
|
871
|
+
[first_key.to_s, foreign_key]
|
872
|
+
else
|
873
|
+
raise ArgumentError, <<~MSG.squish
|
874
|
+
Active Record couldn't correctly interpret the query constraints
|
875
|
+
for the `#{active_record}` model. The query constraints on `#{active_record}` are
|
876
|
+
`#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
|
877
|
+
You need to explicitly set the query constraints for this association.
|
878
|
+
MSG
|
660
879
|
end
|
661
880
|
end
|
662
881
|
|
@@ -691,11 +910,6 @@ module ActiveRecord
|
|
691
910
|
Associations::HasOneAssociation
|
692
911
|
end
|
693
912
|
end
|
694
|
-
|
695
|
-
private
|
696
|
-
def calculate_constructable(macro, options)
|
697
|
-
!options[:through]
|
698
|
-
end
|
699
913
|
end
|
700
914
|
|
701
915
|
class BelongsToReflection < AssociationReflection # :nodoc:
|
@@ -714,7 +928,17 @@ module ActiveRecord
|
|
714
928
|
# klass option is necessary to support loading polymorphic associations
|
715
929
|
def association_primary_key(klass = nil)
|
716
930
|
if primary_key = options[:primary_key]
|
717
|
-
@association_primary_key ||=
|
931
|
+
@association_primary_key ||= if primary_key.is_a?(Array)
|
932
|
+
primary_key.map { |pk| pk.to_s.freeze }.freeze
|
933
|
+
else
|
934
|
+
-primary_key.to_s
|
935
|
+
end
|
936
|
+
elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
|
937
|
+
(klass || self.klass).composite_query_constraints_list
|
938
|
+
elsif (klass || self.klass).composite_primary_key?
|
939
|
+
# If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
|
940
|
+
primary_key = (klass || self.klass).primary_key
|
941
|
+
primary_key.include?("id") ? "id" : primary_key
|
718
942
|
else
|
719
943
|
primary_key(klass || self.klass)
|
720
944
|
end
|
@@ -733,13 +957,9 @@ module ActiveRecord
|
|
733
957
|
end
|
734
958
|
|
735
959
|
private
|
736
|
-
def can_find_inverse_of_automatically?(
|
960
|
+
def can_find_inverse_of_automatically?(*)
|
737
961
|
!polymorphic? && super
|
738
962
|
end
|
739
|
-
|
740
|
-
def calculate_constructable(macro, options)
|
741
|
-
!polymorphic?
|
742
|
-
end
|
743
963
|
end
|
744
964
|
|
745
965
|
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
|
@@ -752,14 +972,17 @@ module ActiveRecord
|
|
752
972
|
|
753
973
|
# Holds all the metadata about a :through association as it was specified
|
754
974
|
# in the Active Record class.
|
755
|
-
class ThroughReflection < AbstractReflection
|
975
|
+
class ThroughReflection < AbstractReflection # :nodoc:
|
756
976
|
delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :type,
|
757
977
|
:active_record_primary_key, :join_foreign_key, to: :source_reflection
|
758
978
|
|
759
979
|
def initialize(delegate_reflection)
|
980
|
+
super()
|
760
981
|
@delegate_reflection = delegate_reflection
|
761
982
|
@klass = delegate_reflection.options[:anonymous_class]
|
762
983
|
@source_reflection_name = delegate_reflection.options[:source]
|
984
|
+
|
985
|
+
ensure_option_not_given_as_class!(:source_type)
|
763
986
|
end
|
764
987
|
|
765
988
|
def through_reflection?
|
@@ -767,7 +990,7 @@ module ActiveRecord
|
|
767
990
|
end
|
768
991
|
|
769
992
|
def klass
|
770
|
-
@klass ||= delegate_reflection.
|
993
|
+
@klass ||= delegate_reflection._klass(class_name)
|
771
994
|
end
|
772
995
|
|
773
996
|
# Returns the source of the through reflection. It checks both a singularized
|
@@ -788,6 +1011,8 @@ module ActiveRecord
|
|
788
1011
|
# # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
|
789
1012
|
#
|
790
1013
|
def source_reflection
|
1014
|
+
return unless source_reflection_name
|
1015
|
+
|
791
1016
|
through_reflection.klass._reflect_on_association(source_reflection_name)
|
792
1017
|
end
|
793
1018
|
|
@@ -840,8 +1065,8 @@ module ActiveRecord
|
|
840
1065
|
source_reflection.scopes + super
|
841
1066
|
end
|
842
1067
|
|
843
|
-
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
|
844
|
-
source_reflection.join_scopes(table, predicate_builder, klass) + super
|
1068
|
+
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
1069
|
+
source_reflection.join_scopes(table, predicate_builder, klass, record) + super
|
845
1070
|
end
|
846
1071
|
|
847
1072
|
def has_scope?
|
@@ -888,24 +1113,23 @@ module ActiveRecord
|
|
888
1113
|
end
|
889
1114
|
|
890
1115
|
def source_reflection_name # :nodoc:
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
1116
|
+
@source_reflection_name ||= begin
|
1117
|
+
names = [name.to_s.singularize, name].collect(&:to_sym).uniq
|
1118
|
+
names = names.find_all { |n|
|
1119
|
+
through_reflection.klass._reflect_on_association(n)
|
1120
|
+
}
|
1121
|
+
|
1122
|
+
if names.length > 1
|
1123
|
+
raise AmbiguousSourceReflectionForThroughAssociation.new(
|
1124
|
+
active_record.name,
|
1125
|
+
macro,
|
1126
|
+
name,
|
1127
|
+
options,
|
1128
|
+
source_reflection_names
|
1129
|
+
)
|
1130
|
+
end
|
1131
|
+
names.first
|
906
1132
|
end
|
907
|
-
|
908
|
-
@source_reflection_name = names.first
|
909
1133
|
end
|
910
1134
|
|
911
1135
|
def source_options
|
@@ -946,7 +1170,7 @@ module ActiveRecord
|
|
946
1170
|
end
|
947
1171
|
|
948
1172
|
if parent_reflection.nil?
|
949
|
-
reflections = active_record.
|
1173
|
+
reflections = active_record.normalized_reflections.keys
|
950
1174
|
|
951
1175
|
if reflections.index(through_reflection.name) > reflections.index(name)
|
952
1176
|
raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
|
@@ -1009,13 +1233,17 @@ module ActiveRecord
|
|
1009
1233
|
:name, :scope_for, to: :@reflection
|
1010
1234
|
|
1011
1235
|
def initialize(reflection, previous_reflection)
|
1236
|
+
super()
|
1012
1237
|
@reflection = reflection
|
1013
1238
|
@previous_reflection = previous_reflection
|
1014
1239
|
end
|
1015
1240
|
|
1016
|
-
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
|
1017
|
-
scopes =
|
1018
|
-
|
1241
|
+
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
1242
|
+
scopes = super
|
1243
|
+
unless @previous_reflection.through_reflection?
|
1244
|
+
scopes += @previous_reflection.join_scopes(table, predicate_builder, klass, record)
|
1245
|
+
end
|
1246
|
+
scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
|
1019
1247
|
end
|
1020
1248
|
|
1021
1249
|
def constraints
|
@@ -1034,6 +1262,7 @@ module ActiveRecord
|
|
1034
1262
|
delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
|
1035
1263
|
|
1036
1264
|
def initialize(reflection, association)
|
1265
|
+
super()
|
1037
1266
|
@reflection = reflection
|
1038
1267
|
@association = association
|
1039
1268
|
end
|