omg-activerecord 8.0.0.alpha1
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 +7 -0
- data/CHANGELOG.md +355 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +219 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record/aggregations.rb +287 -0
- data/lib/active_record/association_relation.rb +50 -0
- data/lib/active_record/associations/alias_tracker.rb +90 -0
- data/lib/active_record/associations/association.rb +417 -0
- data/lib/active_record/associations/association_scope.rb +175 -0
- data/lib/active_record/associations/belongs_to_association.rb +163 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/builder/association.rb +170 -0
- data/lib/active_record/associations/builder/belongs_to.rb +160 -0
- data/lib/active_record/associations/builder/collection_association.rb +80 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
- data/lib/active_record/associations/builder/has_many.rb +23 -0
- data/lib/active_record/associations/builder/has_one.rb +61 -0
- data/lib/active_record/associations/builder/singular_association.rb +48 -0
- data/lib/active_record/associations/collection_association.rb +535 -0
- data/lib/active_record/associations/collection_proxy.rb +1163 -0
- 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 +40 -0
- data/lib/active_record/associations/has_many_association.rb +167 -0
- data/lib/active_record/associations/has_many_through_association.rb +232 -0
- data/lib/active_record/associations/has_one_association.rb +142 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/join_dependency.rb +301 -0
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +316 -0
- 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 +150 -0
- data/lib/active_record/associations/preloader.rb +135 -0
- data/lib/active_record/associations/singular_association.rb +76 -0
- data/lib/active_record/associations/through_association.rb +132 -0
- data/lib/active_record/associations.rb +1897 -0
- data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
- data/lib/active_record/attribute_assignment.rb +82 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +262 -0
- data/lib/active_record/attribute_methods/primary_key.rb +158 -0
- data/lib/active_record/attribute_methods/query.rb +50 -0
- data/lib/active_record/attribute_methods/read.rb +46 -0
- data/lib/active_record/attribute_methods/serialization.rb +232 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
- data/lib/active_record/attribute_methods/write.rb +49 -0
- data/lib/active_record/attribute_methods.rb +542 -0
- data/lib/active_record/attributes.rb +307 -0
- data/lib/active_record/autosave_association.rb +586 -0
- data/lib/active_record/base.rb +338 -0
- data/lib/active_record/callbacks.rb +452 -0
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
- data/lib/active_record/connection_adapters/column.rb +122 -0
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
- data/lib/active_record/connection_adapters/pool_config.rb +83 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +82 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- 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 +125 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
- data/lib/active_record/connection_adapters.rb +176 -0
- data/lib/active_record/connection_handling.rb +413 -0
- data/lib/active_record/core.rb +836 -0
- data/lib/active_record/counter_cache.rb +230 -0
- data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
- data/lib/active_record/database_configurations/database_config.rb +104 -0
- data/lib/active_record/database_configurations/hash_config.rb +172 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/database_configurations.rb +309 -0
- data/lib/active_record/delegated_type.rb +289 -0
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +38 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +121 -0
- 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 +70 -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 +184 -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 +177 -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 +159 -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 +107 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +424 -0
- data/lib/active_record/errors.rb +614 -0
- data/lib/active_record/explain.rb +63 -0
- data/lib/active_record/explain_registry.rb +37 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +89 -0
- data/lib/active_record/fixture_set/model_metadata.rb +42 -0
- data/lib/active_record/fixture_set/render_context.rb +19 -0
- data/lib/active_record/fixture_set/table_row.rb +208 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +850 -0
- data/lib/active_record/future_result.rb +182 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +366 -0
- data/lib/active_record/insert_all.rb +328 -0
- data/lib/active_record/integration.rb +209 -0
- data/lib/active_record/internal_metadata.rb +164 -0
- data/lib/active_record/legacy_yaml_adapter.rb +15 -0
- data/lib/active_record/locale/en.yml +48 -0
- data/lib/active_record/locking/optimistic.rb +228 -0
- data/lib/active_record/locking/pessimistic.rb +102 -0
- data/lib/active_record/log_subscriber.rb +149 -0
- 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/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +87 -0
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +406 -0
- data/lib/active_record/migration/compatibility.rb +490 -0
- 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 +16 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +1626 -0
- data/lib/active_record/model_schema.rb +635 -0
- data/lib/active_record/nested_attributes.rb +633 -0
- data/lib/active_record/no_touching.rb +65 -0
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +968 -0
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/query_logs.rb +247 -0
- data/lib/active_record/query_logs_formatter.rb +30 -0
- data/lib/active_record/querying.rb +122 -0
- data/lib/active_record/railtie.rb +440 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +65 -0
- data/lib/active_record/railties/databases.rake +641 -0
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +66 -0
- data/lib/active_record/reflection.rb +1287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
- data/lib/active_record/relation/batches.rb +491 -0
- data/lib/active_record/relation/calculations.rb +679 -0
- data/lib/active_record/relation/delegation.rb +154 -0
- data/lib/active_record/relation/finder_methods.rb +661 -0
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +192 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
- data/lib/active_record/relation/predicate_builder.rb +181 -0
- data/lib/active_record/relation/query_attribute.rb +68 -0
- data/lib/active_record/relation/query_methods.rb +2235 -0
- data/lib/active_record/relation/record_fetch_warning.rb +52 -0
- data/lib/active_record/relation/spawn_methods.rb +78 -0
- data/lib/active_record/relation/where_clause.rb +218 -0
- data/lib/active_record/relation.rb +1495 -0
- data/lib/active_record/result.rb +249 -0
- data/lib/active_record/runtime_registry.rb +82 -0
- data/lib/active_record/sanitization.rb +254 -0
- data/lib/active_record/schema.rb +77 -0
- data/lib/active_record/schema_dumper.rb +364 -0
- data/lib/active_record/schema_migration.rb +106 -0
- data/lib/active_record/scoping/default.rb +205 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/scoping.rb +136 -0
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +66 -0
- data/lib/active_record/serialization.rb +29 -0
- data/lib/active_record/signed_id.rb +137 -0
- data/lib/active_record/statement_cache.rb +164 -0
- data/lib/active_record/store.rb +299 -0
- data/lib/active_record/suppressor.rb +59 -0
- data/lib/active_record/table_metadata.rb +85 -0
- data/lib/active_record/tasks/database_tasks.rb +681 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +321 -0
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +177 -0
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +70 -0
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +523 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type/adapter_specific_registry.rb +144 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
- data/lib/active_record/type/internal/timezone.rb +22 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +76 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +35 -0
- data/lib/active_record/type/type_map.rb +58 -0
- data/lib/active_record/type/unsigned_integer.rb +16 -0
- data/lib/active_record/type.rb +83 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +65 -0
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +45 -0
- data/lib/active_record/validations/uniqueness.rb +295 -0
- data/lib/active_record/validations.rb +101 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/active_record.rb +616 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +33 -0
- data/lib/arel/collectors/bind.rb +31 -0
- data/lib/arel/collectors/composite.rb +46 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -0
- data/lib/arel/crud.rb +48 -0
- data/lib/arel/delete_manager.rb +32 -0
- data/lib/arel/errors.rb +19 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +53 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +48 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +125 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +44 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +15 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +45 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/nary.rb +39 -0
- data/lib/arel/nodes/node.rb +161 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +32 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +35 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +46 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +75 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +260 -0
- data/lib/arel/select_manager.rb +276 -0
- data/lib/arel/table.rb +121 -0
- data/lib/arel/tree_manager.rb +65 -0
- data/lib/arel/update_manager.rb +49 -0
- data/lib/arel/visitors/dot.rb +299 -0
- data/lib/arel/visitors/mysql.rb +111 -0
- data/lib/arel/visitors/postgresql.rb +99 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +1033 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +73 -0
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
- data/lib/rails/generators/active_record/migration.rb +54 -0
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- 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
- data/lib/rails/generators/active_record.rb +19 -0
- metadata +505 -0
@@ -0,0 +1,1287 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/filters"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
# = Active Record Reflection
|
7
|
+
module Reflection # :nodoc:
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class_attribute :_reflections, instance_writer: false, default: {}
|
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
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def create(macro, name, scope, options, ar)
|
19
|
+
reflection = reflection_class_for(macro).new(name, scope, options, ar)
|
20
|
+
options[:through] ? ThroughReflection.new(reflection) : reflection
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_reflection(ar, name, reflection)
|
24
|
+
ar.clear_reflections_cache
|
25
|
+
name = name.to_sym
|
26
|
+
ar._reflections = ar._reflections.except(name).merge!(name => reflection)
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_aggregate_reflection(ar, name, reflection)
|
30
|
+
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def reflection_class_for(macro)
|
35
|
+
case macro
|
36
|
+
when :composed_of
|
37
|
+
AggregateReflection
|
38
|
+
when :has_many
|
39
|
+
HasManyReflection
|
40
|
+
when :has_one
|
41
|
+
HasOneReflection
|
42
|
+
when :belongs_to
|
43
|
+
BelongsToReflection
|
44
|
+
else
|
45
|
+
raise "Unsupported Macro: #{macro}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# = Active Record Reflection
|
51
|
+
#
|
52
|
+
# \Reflection enables the ability to examine the associations and aggregations of
|
53
|
+
# Active Record classes and objects. This information, for example,
|
54
|
+
# can be used in a form builder that takes an Active Record object
|
55
|
+
# and creates input fields for all of the attributes depending on their type
|
56
|
+
# and displays the associations to other objects.
|
57
|
+
#
|
58
|
+
# MacroReflection class has info for AggregateReflection and AssociationReflection
|
59
|
+
# classes.
|
60
|
+
module ClassMethods
|
61
|
+
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
62
|
+
def reflect_on_all_aggregations
|
63
|
+
aggregate_reflections.values
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
67
|
+
#
|
68
|
+
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
69
|
+
#
|
70
|
+
def reflect_on_aggregation(aggregation)
|
71
|
+
aggregate_reflections[aggregation.to_sym]
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
|
75
|
+
#
|
76
|
+
# Account.reflections # => {"balance" => AggregateReflection}
|
77
|
+
#
|
78
|
+
def reflections
|
79
|
+
normalized_reflections.stringify_keys
|
80
|
+
end
|
81
|
+
|
82
|
+
def normalized_reflections # :nodoc:
|
83
|
+
@__reflections ||= begin
|
84
|
+
ref = {}
|
85
|
+
|
86
|
+
_reflections.each do |name, reflection|
|
87
|
+
parent_reflection = reflection.parent_reflection
|
88
|
+
|
89
|
+
if parent_reflection
|
90
|
+
parent_name = parent_reflection.name
|
91
|
+
ref[parent_name] = parent_reflection
|
92
|
+
else
|
93
|
+
ref[name] = reflection
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
ref.freeze
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns an array of AssociationReflection objects for all the
|
102
|
+
# associations in the class. If you only want to reflect on a certain
|
103
|
+
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
|
104
|
+
# <tt>:belongs_to</tt>) as the first parameter.
|
105
|
+
#
|
106
|
+
# Example:
|
107
|
+
#
|
108
|
+
# Account.reflect_on_all_associations # returns an array of all associations
|
109
|
+
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
110
|
+
#
|
111
|
+
def reflect_on_all_associations(macro = nil)
|
112
|
+
association_reflections = normalized_reflections.values
|
113
|
+
association_reflections.select! { |reflection| reflection.macro == macro } if macro
|
114
|
+
association_reflections
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns the AssociationReflection object for the +association+ (use the symbol).
|
118
|
+
#
|
119
|
+
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
120
|
+
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
121
|
+
#
|
122
|
+
def reflect_on_association(association)
|
123
|
+
normalized_reflections[association.to_sym]
|
124
|
+
end
|
125
|
+
|
126
|
+
def _reflect_on_association(association) # :nodoc:
|
127
|
+
_reflections[association.to_sym]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
131
|
+
def reflect_on_all_autosave_associations
|
132
|
+
reflections = normalized_reflections.values
|
133
|
+
reflections.select! { |reflection| reflection.options[:autosave] }
|
134
|
+
reflections
|
135
|
+
end
|
136
|
+
|
137
|
+
def clear_reflections_cache # :nodoc:
|
138
|
+
@__reflections = nil
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
def inherited(subclass)
|
143
|
+
super
|
144
|
+
subclass.class_eval do
|
145
|
+
@__reflections = nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Holds all the methods that are shared between MacroReflection and ThroughReflection.
|
151
|
+
#
|
152
|
+
# AbstractReflection
|
153
|
+
# MacroReflection
|
154
|
+
# AggregateReflection
|
155
|
+
# AssociationReflection
|
156
|
+
# HasManyReflection
|
157
|
+
# HasOneReflection
|
158
|
+
# BelongsToReflection
|
159
|
+
# HasAndBelongsToManyReflection
|
160
|
+
# ThroughReflection
|
161
|
+
# PolymorphicReflection
|
162
|
+
# RuntimeReflection
|
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
|
+
|
172
|
+
def through_reflection?
|
173
|
+
false
|
174
|
+
end
|
175
|
+
|
176
|
+
def table_name
|
177
|
+
klass.table_name
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns a new, unsaved instance of the associated class. +attributes+ will
|
181
|
+
# be passed to the class's constructor.
|
182
|
+
def build_association(attributes, &block)
|
183
|
+
klass.new(attributes, &block)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns the class name for the macro.
|
187
|
+
#
|
188
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
|
189
|
+
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
190
|
+
def class_name
|
191
|
+
@class_name ||= -(options[:class_name] || derive_class_name).to_s
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns a list of scopes that should be applied for this Reflection
|
195
|
+
# object when querying the database.
|
196
|
+
def scopes
|
197
|
+
scope ? [scope] : []
|
198
|
+
end
|
199
|
+
|
200
|
+
def join_scope(table, foreign_table, foreign_klass)
|
201
|
+
predicate_builder = predicate_builder(table)
|
202
|
+
scope_chain_items = join_scopes(table, predicate_builder)
|
203
|
+
klass_scope = klass_join_scope(table, predicate_builder)
|
204
|
+
|
205
|
+
if type
|
206
|
+
klass_scope.where!(type => foreign_klass.polymorphic_name)
|
207
|
+
end
|
208
|
+
|
209
|
+
scope_chain_items.inject(klass_scope, &:merge!)
|
210
|
+
|
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)
|
215
|
+
|
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
|
219
|
+
|
220
|
+
if klass.finder_needs_type_condition?
|
221
|
+
klass_scope.where!(klass.send(:type_condition, table))
|
222
|
+
end
|
223
|
+
|
224
|
+
klass_scope
|
225
|
+
end
|
226
|
+
|
227
|
+
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
228
|
+
if scope
|
229
|
+
[scope_for(build_scope(table, predicate_builder, klass), record)]
|
230
|
+
else
|
231
|
+
[]
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def klass_join_scope(table, predicate_builder) # :nodoc:
|
236
|
+
relation = build_scope(table, predicate_builder)
|
237
|
+
klass.scope_for_association(relation)
|
238
|
+
end
|
239
|
+
|
240
|
+
def constraints
|
241
|
+
chain.flat_map(&:scopes)
|
242
|
+
end
|
243
|
+
|
244
|
+
def counter_cache_column
|
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")
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def inverse_of
|
259
|
+
return unless inverse_name
|
260
|
+
|
261
|
+
@inverse_of ||= klass._reflect_on_association inverse_name
|
262
|
+
end
|
263
|
+
|
264
|
+
def check_validity_of_inverse!
|
265
|
+
if !polymorphic? && has_inverse?
|
266
|
+
if inverse_of.nil?
|
267
|
+
raise InverseOfAssociationNotFoundError.new(self)
|
268
|
+
end
|
269
|
+
if inverse_of == self
|
270
|
+
raise InverseOfAssociationRecursiveError.new(self)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# We need to avoid the following situation:
|
276
|
+
#
|
277
|
+
# * An associated record is deleted via record.destroy
|
278
|
+
# * Hence the callbacks run, and they find a belongs_to on the record with a
|
279
|
+
# :counter_cache options which points back at our owner. So they update the
|
280
|
+
# counter cache.
|
281
|
+
# * In which case, we must make sure to *not* update the counter cache, or else
|
282
|
+
# it will be decremented twice.
|
283
|
+
#
|
284
|
+
# Hence this method.
|
285
|
+
def inverse_which_updates_counter_cache
|
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
|
294
|
+
end
|
295
|
+
@inverse_which_updates_counter_cache
|
296
|
+
end
|
297
|
+
alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
|
298
|
+
|
299
|
+
def inverse_updates_counter_in_memory?
|
300
|
+
inverse_of && inverse_which_updates_counter_cache == inverse_of
|
301
|
+
end
|
302
|
+
|
303
|
+
# Returns whether this association has a counter cache.
|
304
|
+
#
|
305
|
+
# The counter_cache option must be given on either the owner or inverse
|
306
|
+
# association, and the column must be present on the owner.
|
307
|
+
def has_cached_counter?
|
308
|
+
options[:counter_cache] ||
|
309
|
+
inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache] &&
|
310
|
+
active_record.has_attribute?(counter_cache_column)
|
311
|
+
end
|
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
|
+
|
324
|
+
def counter_must_be_updated_by_has_many?
|
325
|
+
!inverse_updates_counter_in_memory? && has_cached_counter?
|
326
|
+
end
|
327
|
+
|
328
|
+
def alias_candidate(name)
|
329
|
+
"#{plural_name}_#{name}"
|
330
|
+
end
|
331
|
+
|
332
|
+
def chain
|
333
|
+
collect_join_chain
|
334
|
+
end
|
335
|
+
|
336
|
+
def build_scope(table, predicate_builder = predicate_builder(table), klass = self.klass)
|
337
|
+
Relation.create(
|
338
|
+
klass,
|
339
|
+
table: table,
|
340
|
+
predicate_builder: predicate_builder
|
341
|
+
)
|
342
|
+
end
|
343
|
+
|
344
|
+
def strict_loading?
|
345
|
+
options[:strict_loading]
|
346
|
+
end
|
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
|
+
|
354
|
+
protected
|
355
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
356
|
+
self
|
357
|
+
end
|
358
|
+
|
359
|
+
private
|
360
|
+
def predicate_builder(table)
|
361
|
+
PredicateBuilder.new(TableMetadata.new(klass, table))
|
362
|
+
end
|
363
|
+
|
364
|
+
def primary_key(klass)
|
365
|
+
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
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
|
373
|
+
end
|
374
|
+
|
375
|
+
# Base class for AggregateReflection and AssociationReflection. Objects of
|
376
|
+
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
377
|
+
class MacroReflection < AbstractReflection
|
378
|
+
# Returns the name of the macro.
|
379
|
+
#
|
380
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
|
381
|
+
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
|
382
|
+
attr_reader :name
|
383
|
+
|
384
|
+
attr_reader :scope
|
385
|
+
|
386
|
+
# Returns the hash of options used for the macro.
|
387
|
+
#
|
388
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
|
389
|
+
# <tt>has_many :clients</tt> returns <tt>{}</tt>
|
390
|
+
attr_reader :options
|
391
|
+
|
392
|
+
attr_reader :active_record
|
393
|
+
|
394
|
+
attr_reader :plural_name # :nodoc:
|
395
|
+
|
396
|
+
def initialize(name, scope, options, active_record)
|
397
|
+
super()
|
398
|
+
@name = name
|
399
|
+
@scope = scope
|
400
|
+
@options = normalize_options(options)
|
401
|
+
@active_record = active_record
|
402
|
+
@klass = options[:anonymous_class]
|
403
|
+
@plural_name = active_record.pluralize_table_names ?
|
404
|
+
name.to_s.pluralize : name.to_s
|
405
|
+
end
|
406
|
+
|
407
|
+
def autosave=(autosave)
|
408
|
+
@options[:autosave] = autosave
|
409
|
+
parent_reflection = self.parent_reflection
|
410
|
+
if parent_reflection
|
411
|
+
parent_reflection.autosave = autosave
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
# Returns the class for the macro.
|
416
|
+
#
|
417
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
|
418
|
+
# <tt>has_many :clients</tt> returns the Client class
|
419
|
+
#
|
420
|
+
# class Company < ActiveRecord::Base
|
421
|
+
# has_many :clients
|
422
|
+
# end
|
423
|
+
#
|
424
|
+
# Company.reflect_on_association(:clients).klass
|
425
|
+
# # => Client
|
426
|
+
#
|
427
|
+
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
|
428
|
+
# a new association object. Use +build_association+ or +create_association+
|
429
|
+
# instead. This allows plugins to hook into association object creation.
|
430
|
+
def 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)
|
440
|
+
end
|
441
|
+
|
442
|
+
def compute_class(name)
|
443
|
+
name.constantize
|
444
|
+
end
|
445
|
+
|
446
|
+
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
447
|
+
# and +other_aggregation+ has an options hash assigned to it.
|
448
|
+
def ==(other_aggregation)
|
449
|
+
super ||
|
450
|
+
other_aggregation.kind_of?(self.class) &&
|
451
|
+
name == other_aggregation.name &&
|
452
|
+
!other_aggregation.options.nil? &&
|
453
|
+
active_record == other_aggregation.active_record
|
454
|
+
end
|
455
|
+
|
456
|
+
def scope_for(relation, owner = nil)
|
457
|
+
relation.instance_exec(owner, &scope) || relation
|
458
|
+
end
|
459
|
+
|
460
|
+
private
|
461
|
+
def derive_class_name
|
462
|
+
name.to_s.camelize
|
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
|
484
|
+
end
|
485
|
+
|
486
|
+
# Holds all the metadata about an aggregation as it was specified in the
|
487
|
+
# Active Record class.
|
488
|
+
class AggregateReflection < MacroReflection # :nodoc:
|
489
|
+
def mapping
|
490
|
+
mapping = options[:mapping] || [name, name]
|
491
|
+
mapping.first.is_a?(Array) ? mapping : [mapping]
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
# Holds all the metadata about an association as it was specified in the
|
496
|
+
# Active Record class.
|
497
|
+
class AssociationReflection < MacroReflection # :nodoc:
|
498
|
+
def compute_class(name)
|
499
|
+
if polymorphic?
|
500
|
+
raise ArgumentError, "Polymorphic associations do not support computing the class."
|
501
|
+
end
|
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
|
520
|
+
end
|
521
|
+
|
522
|
+
attr_reader :type, :foreign_type
|
523
|
+
attr_accessor :parent_reflection # Reflection
|
524
|
+
|
525
|
+
def initialize(name, scope, options, active_record)
|
526
|
+
super
|
527
|
+
@type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
|
528
|
+
@foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
|
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
|
539
|
+
|
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)
|
543
|
+
end
|
544
|
+
|
545
|
+
ensure_option_not_given_as_class!(:class_name)
|
546
|
+
end
|
547
|
+
|
548
|
+
def association_scope_cache(klass, owner, &block)
|
549
|
+
key = self
|
550
|
+
if polymorphic?
|
551
|
+
key = [key, owner._read_attribute(@foreign_type)]
|
552
|
+
end
|
553
|
+
klass.with_connection do |connection|
|
554
|
+
klass.cached_find_by_statement(connection, key, &block)
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
def join_table
|
559
|
+
@join_table ||= -(options[:join_table]&.to_s || derive_join_table)
|
560
|
+
end
|
561
|
+
|
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
|
+
if derived_fk.is_a?(Array)
|
579
|
+
derived_fk.map! { |fk| -fk.freeze }
|
580
|
+
derived_fk.freeze
|
581
|
+
else
|
582
|
+
-derived_fk.freeze
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
def association_foreign_key
|
588
|
+
@association_foreign_key ||= -(options[:association_foreign_key]&.to_s || class_name.foreign_key)
|
589
|
+
end
|
590
|
+
|
591
|
+
def association_primary_key(klass = nil)
|
592
|
+
primary_key(klass || self.klass)
|
593
|
+
end
|
594
|
+
|
595
|
+
def active_record_primary_key
|
596
|
+
custom_primary_key = options[:primary_key]
|
597
|
+
@active_record_primary_key ||= if custom_primary_key
|
598
|
+
if custom_primary_key.is_a?(Array)
|
599
|
+
custom_primary_key.map { |pk| pk.to_s.freeze }.freeze
|
600
|
+
else
|
601
|
+
custom_primary_key.to_s.freeze
|
602
|
+
end
|
603
|
+
elsif active_record.has_query_constraints? || options[:query_constraints]
|
604
|
+
active_record.query_constraints_list
|
605
|
+
elsif active_record.composite_primary_key?
|
606
|
+
# If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
|
607
|
+
primary_key = primary_key(active_record)
|
608
|
+
primary_key.include?("id") ? "id" : primary_key.freeze
|
609
|
+
else
|
610
|
+
primary_key(active_record).freeze
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
def join_primary_key(klass = nil)
|
615
|
+
foreign_key
|
616
|
+
end
|
617
|
+
|
618
|
+
def join_primary_type
|
619
|
+
type
|
620
|
+
end
|
621
|
+
|
622
|
+
def join_foreign_key
|
623
|
+
active_record_primary_key
|
624
|
+
end
|
625
|
+
|
626
|
+
def check_validity!
|
627
|
+
check_validity_of_inverse!
|
628
|
+
|
629
|
+
if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
|
630
|
+
if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
|
631
|
+
raise CompositePrimaryKeyMismatchError.new(self)
|
632
|
+
elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
|
633
|
+
raise CompositePrimaryKeyMismatchError.new(self)
|
634
|
+
end
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
def check_eager_loadable!
|
639
|
+
return unless scope
|
640
|
+
|
641
|
+
unless scope.arity == 0
|
642
|
+
raise ArgumentError, <<-MSG.squish
|
643
|
+
The association scope '#{name}' is instance dependent (the scope
|
644
|
+
block takes an argument). Eager loading instance dependent scopes
|
645
|
+
is not supported.
|
646
|
+
MSG
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
def join_id_for(owner) # :nodoc:
|
651
|
+
Array(join_foreign_key).map { |key| owner._read_attribute(key) }
|
652
|
+
end
|
653
|
+
|
654
|
+
def through_reflection
|
655
|
+
nil
|
656
|
+
end
|
657
|
+
|
658
|
+
def source_reflection
|
659
|
+
self
|
660
|
+
end
|
661
|
+
|
662
|
+
# A chain of reflections from this one back to the owner. For more see the explanation in
|
663
|
+
# ThroughReflection.
|
664
|
+
def collect_join_chain
|
665
|
+
[self]
|
666
|
+
end
|
667
|
+
|
668
|
+
# This is for clearing cache on the reflection. Useful for tests that need to compare
|
669
|
+
# SQL queries on associations.
|
670
|
+
def clear_association_scope_cache # :nodoc:
|
671
|
+
klass.initialize_find_by_cache
|
672
|
+
end
|
673
|
+
|
674
|
+
def nested?
|
675
|
+
false
|
676
|
+
end
|
677
|
+
|
678
|
+
def has_scope?
|
679
|
+
scope
|
680
|
+
end
|
681
|
+
|
682
|
+
def has_inverse?
|
683
|
+
inverse_name
|
684
|
+
end
|
685
|
+
|
686
|
+
def polymorphic_inverse_of(associated_class)
|
687
|
+
if has_inverse?
|
688
|
+
if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
|
689
|
+
inverse_relationship
|
690
|
+
else
|
691
|
+
raise InverseOfAssociationNotFoundError.new(self, associated_class)
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
# Returns the macro type.
|
697
|
+
#
|
698
|
+
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
699
|
+
def macro; raise NotImplementedError; end
|
700
|
+
|
701
|
+
# Returns whether or not this association reflection is for a collection
|
702
|
+
# association. Returns +true+ if the +macro+ is either +has_many+ or
|
703
|
+
# +has_and_belongs_to_many+, +false+ otherwise.
|
704
|
+
def collection?
|
705
|
+
false
|
706
|
+
end
|
707
|
+
|
708
|
+
# Returns whether or not the association should be validated as part of
|
709
|
+
# the parent's validation.
|
710
|
+
#
|
711
|
+
# Unless you explicitly disable validation with
|
712
|
+
# <tt>validate: false</tt>, validation will take place when:
|
713
|
+
#
|
714
|
+
# * you explicitly enable validation; <tt>validate: true</tt>
|
715
|
+
# * you use autosave; <tt>autosave: true</tt>
|
716
|
+
# * the association is a +has_many+ association
|
717
|
+
def validate?
|
718
|
+
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
|
719
|
+
end
|
720
|
+
|
721
|
+
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
722
|
+
def belongs_to?; false; end
|
723
|
+
|
724
|
+
# Returns +true+ if +self+ is a +has_one+ reflection.
|
725
|
+
def has_one?; false; end
|
726
|
+
|
727
|
+
def association_class; raise NotImplementedError; end
|
728
|
+
|
729
|
+
def polymorphic?
|
730
|
+
options[:polymorphic]
|
731
|
+
end
|
732
|
+
|
733
|
+
def polymorphic_name
|
734
|
+
active_record.polymorphic_name
|
735
|
+
end
|
736
|
+
|
737
|
+
def add_as_source(seed)
|
738
|
+
seed
|
739
|
+
end
|
740
|
+
|
741
|
+
def add_as_polymorphic_through(reflection, seed)
|
742
|
+
seed + [PolymorphicReflection.new(self, reflection)]
|
743
|
+
end
|
744
|
+
|
745
|
+
def add_as_through(seed)
|
746
|
+
seed + [self]
|
747
|
+
end
|
748
|
+
|
749
|
+
def extensions
|
750
|
+
Array(options[:extend])
|
751
|
+
end
|
752
|
+
|
753
|
+
private
|
754
|
+
# Attempts to find the inverse association name automatically.
|
755
|
+
# If it cannot find a suitable inverse association name, it returns
|
756
|
+
# +nil+.
|
757
|
+
def inverse_name
|
758
|
+
unless defined?(@inverse_name)
|
759
|
+
@inverse_name = options.fetch(:inverse_of) { automatic_inverse_of }
|
760
|
+
end
|
761
|
+
|
762
|
+
@inverse_name
|
763
|
+
end
|
764
|
+
|
765
|
+
# returns either +nil+ or the inverse association name that it finds.
|
766
|
+
def automatic_inverse_of
|
767
|
+
if can_find_inverse_of_automatically?(self)
|
768
|
+
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
|
769
|
+
|
770
|
+
begin
|
771
|
+
reflection = klass._reflect_on_association(inverse_name)
|
772
|
+
if !reflection && active_record.automatically_invert_plural_associations
|
773
|
+
plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
|
774
|
+
reflection = klass._reflect_on_association(plural_inverse_name)
|
775
|
+
end
|
776
|
+
rescue NameError => error
|
777
|
+
raise unless error.name.to_s == class_name
|
778
|
+
|
779
|
+
# Give up: we couldn't compute the klass type so we won't be able
|
780
|
+
# to find any associations either.
|
781
|
+
reflection = false
|
782
|
+
end
|
783
|
+
|
784
|
+
if valid_inverse_reflection?(reflection)
|
785
|
+
reflection.name
|
786
|
+
end
|
787
|
+
end
|
788
|
+
end
|
789
|
+
|
790
|
+
# Checks if the inverse reflection that is returned from the
|
791
|
+
# +automatic_inverse_of+ method is a valid reflection. We must
|
792
|
+
# make sure that the reflection's active_record name matches up
|
793
|
+
# with the current reflection's klass name.
|
794
|
+
def valid_inverse_reflection?(reflection)
|
795
|
+
reflection &&
|
796
|
+
reflection != self &&
|
797
|
+
foreign_key == reflection.foreign_key &&
|
798
|
+
klass <= reflection.active_record &&
|
799
|
+
can_find_inverse_of_automatically?(reflection, true)
|
800
|
+
end
|
801
|
+
|
802
|
+
# Checks to see if the reflection doesn't have any options that prevent
|
803
|
+
# us from being able to guess the inverse automatically. First, the
|
804
|
+
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
|
805
|
+
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
|
806
|
+
# Third, we must not have options such as <tt>:foreign_key</tt>
|
807
|
+
# which prevent us from correctly guessing the inverse association.
|
808
|
+
def can_find_inverse_of_automatically?(reflection, inverse_reflection = false)
|
809
|
+
reflection.options[:inverse_of] != false &&
|
810
|
+
!reflection.options[:through] &&
|
811
|
+
!reflection.options[:foreign_key] &&
|
812
|
+
scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
|
813
|
+
end
|
814
|
+
|
815
|
+
# Scopes on the potential inverse reflection prevent automatic
|
816
|
+
# <tt>inverse_of</tt>, since the scope could exclude the owner record
|
817
|
+
# we would inverse from. Scopes on the reflection itself allow for
|
818
|
+
# automatic <tt>inverse_of</tt> as long as
|
819
|
+
# <tt>config.active_record.automatic_scope_inversing<tt> is set to
|
820
|
+
# +true+ (the default for new applications).
|
821
|
+
def scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
|
822
|
+
if inverse_reflection
|
823
|
+
!reflection.scope
|
824
|
+
else
|
825
|
+
!reflection.scope || reflection.klass.automatic_scope_inversing
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
def derive_class_name
|
830
|
+
class_name = name.to_s
|
831
|
+
class_name = class_name.singularize if collection?
|
832
|
+
class_name.camelize
|
833
|
+
end
|
834
|
+
|
835
|
+
def derive_foreign_key(infer_from_inverse_of: true)
|
836
|
+
if belongs_to?
|
837
|
+
"#{name}_id"
|
838
|
+
elsif options[:as]
|
839
|
+
"#{options[:as]}_id"
|
840
|
+
elsif options[:inverse_of] && infer_from_inverse_of
|
841
|
+
inverse_of.foreign_key(infer_from_inverse_of: false)
|
842
|
+
else
|
843
|
+
active_record.model_name.to_s.foreign_key
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
def derive_fk_query_constraints(foreign_key)
|
848
|
+
primary_query_constraints = active_record.query_constraints_list
|
849
|
+
owner_pk = active_record.primary_key
|
850
|
+
|
851
|
+
if primary_query_constraints.size > 2
|
852
|
+
raise ArgumentError, <<~MSG.squish
|
853
|
+
The query constraints list on the `#{active_record}` model has more than 2
|
854
|
+
attributes. Active Record is unable to derive the query constraints
|
855
|
+
for the association. You need to explicitly define the query constraints
|
856
|
+
for this association.
|
857
|
+
MSG
|
858
|
+
end
|
859
|
+
|
860
|
+
if !primary_query_constraints.include?(owner_pk)
|
861
|
+
raise ArgumentError, <<~MSG.squish
|
862
|
+
The query constraints on the `#{active_record}` model does not include the primary
|
863
|
+
key so Active Record is unable to derive the foreign key constraints for
|
864
|
+
the association. You need to explicitly define the query constraints for this
|
865
|
+
association.
|
866
|
+
MSG
|
867
|
+
end
|
868
|
+
|
869
|
+
return foreign_key if primary_query_constraints.include?(foreign_key)
|
870
|
+
|
871
|
+
first_key, last_key = primary_query_constraints
|
872
|
+
|
873
|
+
if first_key == owner_pk
|
874
|
+
[foreign_key, last_key.to_s]
|
875
|
+
elsif last_key == owner_pk
|
876
|
+
[first_key.to_s, foreign_key]
|
877
|
+
else
|
878
|
+
raise ArgumentError, <<~MSG.squish
|
879
|
+
Active Record couldn't correctly interpret the query constraints
|
880
|
+
for the `#{active_record}` model. The query constraints on `#{active_record}` are
|
881
|
+
`#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
|
882
|
+
You need to explicitly set the query constraints for this association.
|
883
|
+
MSG
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
def derive_join_table
|
888
|
+
ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
|
889
|
+
end
|
890
|
+
end
|
891
|
+
|
892
|
+
class HasManyReflection < AssociationReflection # :nodoc:
|
893
|
+
def macro; :has_many; end
|
894
|
+
|
895
|
+
def collection?; true; end
|
896
|
+
|
897
|
+
def association_class
|
898
|
+
if options[:through]
|
899
|
+
Associations::HasManyThroughAssociation
|
900
|
+
else
|
901
|
+
Associations::HasManyAssociation
|
902
|
+
end
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
class HasOneReflection < AssociationReflection # :nodoc:
|
907
|
+
def macro; :has_one; end
|
908
|
+
|
909
|
+
def has_one?; true; end
|
910
|
+
|
911
|
+
def association_class
|
912
|
+
if options[:through]
|
913
|
+
Associations::HasOneThroughAssociation
|
914
|
+
else
|
915
|
+
Associations::HasOneAssociation
|
916
|
+
end
|
917
|
+
end
|
918
|
+
end
|
919
|
+
|
920
|
+
class BelongsToReflection < AssociationReflection # :nodoc:
|
921
|
+
def macro; :belongs_to; end
|
922
|
+
|
923
|
+
def belongs_to?; true; end
|
924
|
+
|
925
|
+
def association_class
|
926
|
+
if polymorphic?
|
927
|
+
Associations::BelongsToPolymorphicAssociation
|
928
|
+
else
|
929
|
+
Associations::BelongsToAssociation
|
930
|
+
end
|
931
|
+
end
|
932
|
+
|
933
|
+
# klass option is necessary to support loading polymorphic associations
|
934
|
+
def association_primary_key(klass = nil)
|
935
|
+
if primary_key = options[:primary_key]
|
936
|
+
@association_primary_key ||= if primary_key.is_a?(Array)
|
937
|
+
primary_key.map { |pk| pk.to_s.freeze }.freeze
|
938
|
+
else
|
939
|
+
-primary_key.to_s
|
940
|
+
end
|
941
|
+
elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
|
942
|
+
(klass || self.klass).composite_query_constraints_list
|
943
|
+
elsif (klass || self.klass).composite_primary_key?
|
944
|
+
# If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
|
945
|
+
primary_key = (klass || self.klass).primary_key
|
946
|
+
primary_key.include?("id") ? "id" : primary_key
|
947
|
+
else
|
948
|
+
primary_key(klass || self.klass)
|
949
|
+
end
|
950
|
+
end
|
951
|
+
|
952
|
+
def join_primary_key(klass = nil)
|
953
|
+
polymorphic? ? association_primary_key(klass) : association_primary_key
|
954
|
+
end
|
955
|
+
|
956
|
+
def join_foreign_key
|
957
|
+
foreign_key
|
958
|
+
end
|
959
|
+
|
960
|
+
def join_foreign_type
|
961
|
+
foreign_type
|
962
|
+
end
|
963
|
+
|
964
|
+
private
|
965
|
+
def can_find_inverse_of_automatically?(*)
|
966
|
+
!polymorphic? && super
|
967
|
+
end
|
968
|
+
end
|
969
|
+
|
970
|
+
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
|
971
|
+
def macro; :has_and_belongs_to_many; end
|
972
|
+
|
973
|
+
def collection?
|
974
|
+
true
|
975
|
+
end
|
976
|
+
end
|
977
|
+
|
978
|
+
# Holds all the metadata about a :through association as it was specified
|
979
|
+
# in the Active Record class.
|
980
|
+
class ThroughReflection < AbstractReflection # :nodoc:
|
981
|
+
delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :type,
|
982
|
+
:active_record_primary_key, :join_foreign_key, to: :source_reflection
|
983
|
+
|
984
|
+
def initialize(delegate_reflection)
|
985
|
+
super()
|
986
|
+
@delegate_reflection = delegate_reflection
|
987
|
+
@klass = delegate_reflection.options[:anonymous_class]
|
988
|
+
@source_reflection_name = delegate_reflection.options[:source]
|
989
|
+
|
990
|
+
ensure_option_not_given_as_class!(:source_type)
|
991
|
+
end
|
992
|
+
|
993
|
+
def through_reflection?
|
994
|
+
true
|
995
|
+
end
|
996
|
+
|
997
|
+
def klass
|
998
|
+
@klass ||= delegate_reflection._klass(class_name)
|
999
|
+
end
|
1000
|
+
|
1001
|
+
# Returns the source of the through reflection. It checks both a singularized
|
1002
|
+
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
1003
|
+
#
|
1004
|
+
# class Post < ActiveRecord::Base
|
1005
|
+
# has_many :taggings
|
1006
|
+
# has_many :tags, through: :taggings
|
1007
|
+
# end
|
1008
|
+
#
|
1009
|
+
# class Tagging < ActiveRecord::Base
|
1010
|
+
# belongs_to :post
|
1011
|
+
# belongs_to :tag
|
1012
|
+
# end
|
1013
|
+
#
|
1014
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
1015
|
+
# tags_reflection.source_reflection
|
1016
|
+
# # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
|
1017
|
+
#
|
1018
|
+
def source_reflection
|
1019
|
+
return unless source_reflection_name
|
1020
|
+
|
1021
|
+
through_reflection.klass._reflect_on_association(source_reflection_name)
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
|
1025
|
+
# of a HasManyThrough or HasOneThrough association.
|
1026
|
+
#
|
1027
|
+
# class Post < ActiveRecord::Base
|
1028
|
+
# has_many :taggings
|
1029
|
+
# has_many :tags, through: :taggings
|
1030
|
+
# end
|
1031
|
+
#
|
1032
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
1033
|
+
# tags_reflection.through_reflection
|
1034
|
+
# # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
|
1035
|
+
#
|
1036
|
+
def through_reflection
|
1037
|
+
active_record._reflect_on_association(options[:through])
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
# Returns an array of reflections which are involved in this association. Each item in the
|
1041
|
+
# array corresponds to a table which will be part of the query for this association.
|
1042
|
+
#
|
1043
|
+
# The chain is built by recursively calling #chain on the source reflection and the through
|
1044
|
+
# reflection. The base case for the recursion is a normal association, which just returns
|
1045
|
+
# [self] as its #chain.
|
1046
|
+
#
|
1047
|
+
# class Post < ActiveRecord::Base
|
1048
|
+
# has_many :taggings
|
1049
|
+
# has_many :tags, through: :taggings
|
1050
|
+
# end
|
1051
|
+
#
|
1052
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
1053
|
+
# tags_reflection.chain
|
1054
|
+
# # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
|
1055
|
+
# <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
|
1056
|
+
#
|
1057
|
+
def collect_join_chain
|
1058
|
+
collect_join_reflections [self]
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
# This is for clearing cache on the reflection. Useful for tests that need to compare
|
1062
|
+
# SQL queries on associations.
|
1063
|
+
def clear_association_scope_cache # :nodoc:
|
1064
|
+
delegate_reflection.clear_association_scope_cache
|
1065
|
+
source_reflection.clear_association_scope_cache
|
1066
|
+
through_reflection.clear_association_scope_cache
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
def scopes
|
1070
|
+
source_reflection.scopes + super
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
1074
|
+
source_reflection.join_scopes(table, predicate_builder, klass, record) + super
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
def has_scope?
|
1078
|
+
scope || options[:source_type] ||
|
1079
|
+
source_reflection.has_scope? ||
|
1080
|
+
through_reflection.has_scope?
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
# A through association is nested if there would be more than one join table
|
1084
|
+
def nested?
|
1085
|
+
source_reflection.through_reflection? || through_reflection.through_reflection?
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
# We want to use the klass from this reflection, rather than just delegate straight to
|
1089
|
+
# the source_reflection, because the source_reflection may be polymorphic. We still
|
1090
|
+
# need to respect the source_reflection's :primary_key option, though.
|
1091
|
+
def association_primary_key(klass = nil)
|
1092
|
+
# Get the "actual" source reflection if the immediate source reflection has a
|
1093
|
+
# source reflection itself
|
1094
|
+
if primary_key = actual_source_reflection.options[:primary_key]
|
1095
|
+
@association_primary_key ||= -primary_key.to_s
|
1096
|
+
else
|
1097
|
+
primary_key(klass || self.klass)
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
def join_primary_key(klass = self.klass)
|
1102
|
+
source_reflection.join_primary_key(klass)
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
|
1106
|
+
#
|
1107
|
+
# class Post < ActiveRecord::Base
|
1108
|
+
# has_many :taggings
|
1109
|
+
# has_many :tags, through: :taggings
|
1110
|
+
# end
|
1111
|
+
#
|
1112
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
1113
|
+
# tags_reflection.source_reflection_names
|
1114
|
+
# # => [:tag, :tags]
|
1115
|
+
#
|
1116
|
+
def source_reflection_names
|
1117
|
+
options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
def source_reflection_name # :nodoc:
|
1121
|
+
@source_reflection_name ||= begin
|
1122
|
+
names = [name.to_s.singularize, name].collect(&:to_sym).uniq
|
1123
|
+
names = names.find_all { |n|
|
1124
|
+
through_reflection.klass._reflect_on_association(n)
|
1125
|
+
}
|
1126
|
+
|
1127
|
+
if names.length > 1
|
1128
|
+
raise AmbiguousSourceReflectionForThroughAssociation.new(
|
1129
|
+
active_record.name,
|
1130
|
+
macro,
|
1131
|
+
name,
|
1132
|
+
options,
|
1133
|
+
source_reflection_names
|
1134
|
+
)
|
1135
|
+
end
|
1136
|
+
names.first
|
1137
|
+
end
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
def source_options
|
1141
|
+
source_reflection.options
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
def through_options
|
1145
|
+
through_reflection.options
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
def check_validity!
|
1149
|
+
if through_reflection.nil?
|
1150
|
+
raise HasManyThroughAssociationNotFoundError.new(active_record, self)
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
if through_reflection.polymorphic?
|
1154
|
+
if has_one?
|
1155
|
+
raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
|
1156
|
+
else
|
1157
|
+
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
|
1158
|
+
end
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
if source_reflection.nil?
|
1162
|
+
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
if options[:source_type] && !source_reflection.polymorphic?
|
1166
|
+
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
|
1167
|
+
end
|
1168
|
+
|
1169
|
+
if source_reflection.polymorphic? && options[:source_type].nil?
|
1170
|
+
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
|
1171
|
+
end
|
1172
|
+
|
1173
|
+
if has_one? && through_reflection.collection?
|
1174
|
+
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
if parent_reflection.nil?
|
1178
|
+
reflections = active_record.normalized_reflections.keys
|
1179
|
+
|
1180
|
+
if reflections.index(through_reflection.name) > reflections.index(name)
|
1181
|
+
raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
|
1182
|
+
end
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
check_validity_of_inverse!
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
def constraints
|
1189
|
+
scope_chain = source_reflection.constraints
|
1190
|
+
scope_chain << scope if scope
|
1191
|
+
scope_chain
|
1192
|
+
end
|
1193
|
+
|
1194
|
+
def add_as_source(seed)
|
1195
|
+
collect_join_reflections seed
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
def add_as_polymorphic_through(reflection, seed)
|
1199
|
+
collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
def add_as_through(seed)
|
1203
|
+
collect_join_reflections(seed + [self])
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
protected
|
1207
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
1208
|
+
source_reflection.actual_source_reflection
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
private
|
1212
|
+
attr_reader :delegate_reflection
|
1213
|
+
|
1214
|
+
def collect_join_reflections(seed)
|
1215
|
+
a = source_reflection.add_as_source seed
|
1216
|
+
if options[:source_type]
|
1217
|
+
through_reflection.add_as_polymorphic_through self, a
|
1218
|
+
else
|
1219
|
+
through_reflection.add_as_through a
|
1220
|
+
end
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
def inverse_name; delegate_reflection.send(:inverse_name); end
|
1224
|
+
|
1225
|
+
def derive_class_name
|
1226
|
+
# get the class_name of the belongs_to association of the through reflection
|
1227
|
+
options[:source_type] || source_reflection.class_name
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
delegate_methods = AssociationReflection.public_instance_methods -
|
1231
|
+
public_instance_methods
|
1232
|
+
|
1233
|
+
delegate(*delegate_methods, to: :delegate_reflection)
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
class PolymorphicReflection < AbstractReflection # :nodoc:
|
1237
|
+
delegate :klass, :scope, :plural_name, :type, :join_primary_key, :join_foreign_key,
|
1238
|
+
:name, :scope_for, to: :@reflection
|
1239
|
+
|
1240
|
+
def initialize(reflection, previous_reflection)
|
1241
|
+
super()
|
1242
|
+
@reflection = reflection
|
1243
|
+
@previous_reflection = previous_reflection
|
1244
|
+
end
|
1245
|
+
|
1246
|
+
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
1247
|
+
scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
|
1248
|
+
scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
def constraints
|
1252
|
+
@reflection.constraints + [source_type_scope]
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
private
|
1256
|
+
def source_type_scope
|
1257
|
+
type = @previous_reflection.foreign_type
|
1258
|
+
source_type = @previous_reflection.options[:source_type]
|
1259
|
+
lambda { |object| where(type => source_type) }
|
1260
|
+
end
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
class RuntimeReflection < AbstractReflection # :nodoc:
|
1264
|
+
delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
|
1265
|
+
|
1266
|
+
def initialize(reflection, association)
|
1267
|
+
super()
|
1268
|
+
@reflection = reflection
|
1269
|
+
@association = association
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
def klass
|
1273
|
+
@association.klass
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
def aliased_table
|
1277
|
+
klass.arel_table
|
1278
|
+
end
|
1279
|
+
|
1280
|
+
def join_primary_key(klass = self.klass)
|
1281
|
+
@reflection.join_primary_key(klass)
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
def all_includes; yield; end
|
1285
|
+
end
|
1286
|
+
end
|
1287
|
+
end
|