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,309 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require "active_record/database_configurations/database_config"
|
|
5
|
+
require "active_record/database_configurations/hash_config"
|
|
6
|
+
require "active_record/database_configurations/url_config"
|
|
7
|
+
require "active_record/database_configurations/connection_url_resolver"
|
|
8
|
+
|
|
9
|
+
module ActiveRecord
|
|
10
|
+
# = Active Record Database Configurations
|
|
11
|
+
#
|
|
12
|
+
# +ActiveRecord::DatabaseConfigurations+ returns an array of +DatabaseConfig+
|
|
13
|
+
# objects that are constructed from the application's database
|
|
14
|
+
# configuration hash or URL string.
|
|
15
|
+
#
|
|
16
|
+
# The array of +DatabaseConfig+ objects in an application default to either a
|
|
17
|
+
# HashConfig or UrlConfig. You can retrieve your application's config by using
|
|
18
|
+
# ActiveRecord::Base.configurations.
|
|
19
|
+
#
|
|
20
|
+
# If you register a custom handler, objects will be created according to the
|
|
21
|
+
# conditions of the handler. See ::register_db_config_handler for more on
|
|
22
|
+
# registering custom handlers.
|
|
23
|
+
class DatabaseConfigurations
|
|
24
|
+
class InvalidConfigurationError < StandardError; end
|
|
25
|
+
|
|
26
|
+
attr_reader :configurations
|
|
27
|
+
delegate :any?, to: :configurations
|
|
28
|
+
|
|
29
|
+
singleton_class.attr_accessor :db_config_handlers # :nodoc:
|
|
30
|
+
self.db_config_handlers = [] # :nodoc:
|
|
31
|
+
|
|
32
|
+
# Allows an application to register a custom handler for database configuration
|
|
33
|
+
# objects. This is useful for creating a custom handler that responds to
|
|
34
|
+
# methods your application needs but Active Record doesn't implement. For
|
|
35
|
+
# example if you are using Vitess, you may want your Vitess configurations
|
|
36
|
+
# to respond to `sharded?`. To implement this define the following in an
|
|
37
|
+
# initializer:
|
|
38
|
+
#
|
|
39
|
+
# ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
|
|
40
|
+
# next unless config.key?(:vitess)
|
|
41
|
+
# VitessConfig.new(env_name, name, config)
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# Note: applications must handle the condition in which custom config should be
|
|
45
|
+
# created in your handler registration otherwise all objects will use the custom
|
|
46
|
+
# handler.
|
|
47
|
+
#
|
|
48
|
+
# Then define your +VitessConfig+ to respond to the methods your application
|
|
49
|
+
# needs. It is recommended that you inherit from one of the existing
|
|
50
|
+
# database config classes to avoid having to reimplement all methods. Custom
|
|
51
|
+
# config handlers should only implement methods Active Record does not.
|
|
52
|
+
#
|
|
53
|
+
# class VitessConfig < ActiveRecord::DatabaseConfigurations::UrlConfig
|
|
54
|
+
# def sharded?
|
|
55
|
+
# configuration_hash.fetch("sharded", false)
|
|
56
|
+
# end
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# For configs that have a +:vitess+ key, a +VitessConfig+ object will be
|
|
60
|
+
# created instead of a +UrlConfig+.
|
|
61
|
+
def self.register_db_config_handler(&block)
|
|
62
|
+
db_config_handlers << block
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
register_db_config_handler do |env_name, name, url, config|
|
|
66
|
+
if url
|
|
67
|
+
UrlConfig.new(env_name, name, url, config)
|
|
68
|
+
else
|
|
69
|
+
HashConfig.new(env_name, name, config)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def initialize(configurations = {})
|
|
74
|
+
@configurations = build_configs(configurations)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Collects the configs for the environment and optionally the specification
|
|
78
|
+
# name passed in. To include replica configurations pass <tt>include_hidden: true</tt>.
|
|
79
|
+
#
|
|
80
|
+
# If a name is provided a single +DatabaseConfig+ object will be
|
|
81
|
+
# returned, otherwise an array of +DatabaseConfig+ objects will be
|
|
82
|
+
# returned that corresponds with the environment and type requested.
|
|
83
|
+
#
|
|
84
|
+
# ==== Options
|
|
85
|
+
#
|
|
86
|
+
# * <tt>env_name:</tt> The environment name. Defaults to +nil+ which will collect
|
|
87
|
+
# configs for all environments.
|
|
88
|
+
# * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
|
|
89
|
+
# to +nil+. If no +env_name+ is specified the config for the default env and the
|
|
90
|
+
# passed +name+ will be returned.
|
|
91
|
+
# * <tt>config_key:</tt> Selects configs that contain a particular key in the configuration
|
|
92
|
+
# hash. Useful for selecting configs that use a custom db config handler or finding
|
|
93
|
+
# configs with hashes that contain a particular key.
|
|
94
|
+
# * <tt>include_hidden:</tt> Determines whether to include replicas and configurations
|
|
95
|
+
# hidden by <tt>database_tasks: false</tt> in the returned list. Most of the time we're only
|
|
96
|
+
# iterating over the primary connections (i.e. migrations don't need to run for the
|
|
97
|
+
# write and read connection). Defaults to +false+.
|
|
98
|
+
def configs_for(env_name: nil, name: nil, config_key: nil, include_hidden: false)
|
|
99
|
+
env_name ||= default_env if name
|
|
100
|
+
configs = env_with_configs(env_name)
|
|
101
|
+
|
|
102
|
+
unless include_hidden
|
|
103
|
+
configs = configs.select do |db_config|
|
|
104
|
+
db_config.database_tasks?
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if config_key
|
|
109
|
+
configs = configs.select do |db_config|
|
|
110
|
+
db_config.configuration_hash.key?(config_key)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if name
|
|
115
|
+
configs.find do |db_config|
|
|
116
|
+
db_config.name == name.to_s
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
configs
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns a single +DatabaseConfig+ object based on the requested environment.
|
|
124
|
+
#
|
|
125
|
+
# If the application has multiple databases +find_db_config+ will return
|
|
126
|
+
# the first +DatabaseConfig+ for the environment.
|
|
127
|
+
def find_db_config(env)
|
|
128
|
+
env = env.to_s
|
|
129
|
+
configurations.find do |db_config|
|
|
130
|
+
db_config.for_current_env? && (db_config.env_name == env || db_config.name == env)
|
|
131
|
+
end || configurations.find do |db_config|
|
|
132
|
+
db_config.env_name == env
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# A primary configuration is one that is named primary or if there is
|
|
137
|
+
# no primary, the first configuration for an environment will be treated
|
|
138
|
+
# as primary. This is used as the "default" configuration and is used
|
|
139
|
+
# when the application needs to treat one configuration differently. For
|
|
140
|
+
# example, when Rails dumps the schema, the primary configuration's schema
|
|
141
|
+
# file will be named `schema.rb` instead of `primary_schema.rb`.
|
|
142
|
+
def primary?(name) # :nodoc:
|
|
143
|
+
return true if name == "primary"
|
|
144
|
+
|
|
145
|
+
first_config = find_db_config(default_env)
|
|
146
|
+
first_config && name == first_config.name
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Checks if the application's configurations are empty.
|
|
150
|
+
def empty?
|
|
151
|
+
configurations.empty?
|
|
152
|
+
end
|
|
153
|
+
alias :blank? :empty?
|
|
154
|
+
|
|
155
|
+
# Returns fully resolved connection, accepts hash, string or symbol.
|
|
156
|
+
# Always returns a DatabaseConfiguration::DatabaseConfig
|
|
157
|
+
#
|
|
158
|
+
# == Examples
|
|
159
|
+
#
|
|
160
|
+
# Symbol representing current environment.
|
|
161
|
+
#
|
|
162
|
+
# DatabaseConfigurations.new("production" => {}).resolve(:production)
|
|
163
|
+
# # => DatabaseConfigurations::HashConfig.new(env_name: "production", config: {})
|
|
164
|
+
#
|
|
165
|
+
# One layer deep hash of connection values.
|
|
166
|
+
#
|
|
167
|
+
# DatabaseConfigurations.new({}).resolve("adapter" => "sqlite3")
|
|
168
|
+
# # => DatabaseConfigurations::HashConfig.new(config: {"adapter" => "sqlite3"})
|
|
169
|
+
#
|
|
170
|
+
# Connection URL.
|
|
171
|
+
#
|
|
172
|
+
# DatabaseConfigurations.new({}).resolve("postgresql://localhost/foo")
|
|
173
|
+
# # => DatabaseConfigurations::UrlConfig.new(config: {"adapter" => "postgresql", "host" => "localhost", "database" => "foo"})
|
|
174
|
+
def resolve(config) # :nodoc:
|
|
175
|
+
return config if DatabaseConfigurations::DatabaseConfig === config
|
|
176
|
+
|
|
177
|
+
case config
|
|
178
|
+
when Symbol
|
|
179
|
+
resolve_symbol_connection(config)
|
|
180
|
+
when Hash, String
|
|
181
|
+
build_db_config_from_raw_config(default_env, "primary", config)
|
|
182
|
+
else
|
|
183
|
+
raise TypeError, "Invalid type for configuration. Expected Symbol, String, or Hash. Got #{config.inspect}"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
def default_env
|
|
189
|
+
ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def env_with_configs(env = nil)
|
|
193
|
+
if env
|
|
194
|
+
configurations.select { |db_config| db_config.env_name == env }
|
|
195
|
+
else
|
|
196
|
+
configurations
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def build_configs(configs)
|
|
201
|
+
return configs.configurations if configs.is_a?(DatabaseConfigurations)
|
|
202
|
+
return configs if configs.is_a?(Array)
|
|
203
|
+
|
|
204
|
+
db_configs = configs.flat_map do |env_name, config|
|
|
205
|
+
if config.is_a?(Hash) && config.values.all?(Hash)
|
|
206
|
+
walk_configs(env_name.to_s, config)
|
|
207
|
+
else
|
|
208
|
+
build_db_config_from_raw_config(env_name.to_s, "primary", config)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
unless db_configs.find(&:for_current_env?)
|
|
213
|
+
db_configs << environment_url_config(default_env, "primary", {})
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
merge_db_environment_variables(default_env, db_configs.compact)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def walk_configs(env_name, config)
|
|
220
|
+
config.map do |name, sub_config|
|
|
221
|
+
build_db_config_from_raw_config(env_name, name.to_s, sub_config)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def resolve_symbol_connection(name)
|
|
226
|
+
if db_config = find_db_config(name)
|
|
227
|
+
db_config
|
|
228
|
+
else
|
|
229
|
+
raise AdapterNotSpecified, <<~MSG
|
|
230
|
+
The `#{name}` database is not configured for the `#{default_env}` environment.
|
|
231
|
+
|
|
232
|
+
Available database configurations are:
|
|
233
|
+
|
|
234
|
+
#{build_configuration_sentence}
|
|
235
|
+
MSG
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def build_configuration_sentence
|
|
240
|
+
configs = configs_for(include_hidden: true)
|
|
241
|
+
|
|
242
|
+
configs.group_by(&:env_name).map do |env, config|
|
|
243
|
+
names = config.map(&:name)
|
|
244
|
+
if names.size > 1
|
|
245
|
+
"#{env}: #{names.join(", ")}"
|
|
246
|
+
else
|
|
247
|
+
env
|
|
248
|
+
end
|
|
249
|
+
end.join("\n")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def build_db_config_from_raw_config(env_name, name, config)
|
|
253
|
+
case config
|
|
254
|
+
when String
|
|
255
|
+
build_db_config_from_string(env_name, name, config)
|
|
256
|
+
when Hash
|
|
257
|
+
build_db_config_from_hash(env_name, name, config.symbolize_keys)
|
|
258
|
+
else
|
|
259
|
+
raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def build_db_config_from_string(env_name, name, config)
|
|
264
|
+
url = config
|
|
265
|
+
uri = URI.parse(url)
|
|
266
|
+
if uri.scheme
|
|
267
|
+
UrlConfig.new(env_name, name, url)
|
|
268
|
+
else
|
|
269
|
+
raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def build_db_config_from_hash(env_name, name, config)
|
|
274
|
+
url = config[:url]
|
|
275
|
+
config_without_url = config.dup
|
|
276
|
+
config_without_url.delete :url
|
|
277
|
+
|
|
278
|
+
DatabaseConfigurations.db_config_handlers.reverse_each do |handler|
|
|
279
|
+
config = handler.call(env_name, name, url, config_without_url)
|
|
280
|
+
return config if config
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
nil
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def merge_db_environment_variables(current_env, configs)
|
|
287
|
+
configs.map do |config|
|
|
288
|
+
next config if config.is_a?(UrlConfig) || config.env_name != current_env
|
|
289
|
+
|
|
290
|
+
url_config = environment_url_config(current_env, config.name, config.configuration_hash)
|
|
291
|
+
url_config || config
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def environment_url_config(env, name, config)
|
|
296
|
+
url = environment_value_for(name)
|
|
297
|
+
return unless url
|
|
298
|
+
|
|
299
|
+
UrlConfig.new(env, name, url, config)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def environment_value_for(name)
|
|
303
|
+
name_env_key = "#{name.upcase}_DATABASE_URL"
|
|
304
|
+
url = ENV[name_env_key]
|
|
305
|
+
url ||= ENV["DATABASE_URL"] if name == "primary"
|
|
306
|
+
url
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/string/inquiry"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
# = Delegated types
|
|
7
|
+
#
|
|
8
|
+
# Class hierarchies can map to relational database tables in many ways. Active Record, for example, offers
|
|
9
|
+
# purely abstract classes, where the superclass doesn't persist any attributes, and single-table inheritance,
|
|
10
|
+
# where all attributes from all levels of the hierarchy are represented in a single table. Both have their
|
|
11
|
+
# places, but neither are without their drawbacks.
|
|
12
|
+
#
|
|
13
|
+
# The problem with purely abstract classes is that all concrete subclasses must persist all the shared
|
|
14
|
+
# attributes themselves in their own tables (also known as class-table inheritance). This makes it hard to
|
|
15
|
+
# do queries across the hierarchy. For example, imagine you have the following hierarchy:
|
|
16
|
+
#
|
|
17
|
+
# Entry < ApplicationRecord
|
|
18
|
+
# Message < Entry
|
|
19
|
+
# Comment < Entry
|
|
20
|
+
#
|
|
21
|
+
# How do you show a feed that has both +Message+ and +Comment+ records, which can be easily paginated?
|
|
22
|
+
# Well, you can't! Messages are backed by a messages table and comments by a comments table. You can't
|
|
23
|
+
# pull from both tables at once and use a consistent OFFSET/LIMIT scheme.
|
|
24
|
+
#
|
|
25
|
+
# You can get around the pagination problem by using single-table inheritance, but now you're forced into
|
|
26
|
+
# a single mega table with all the attributes from all subclasses. No matter how divergent. If a Message
|
|
27
|
+
# has a subject, but the comment does not, well, now the comment does anyway! So STI works best when there's
|
|
28
|
+
# little divergence between the subclasses and their attributes.
|
|
29
|
+
#
|
|
30
|
+
# But there's a third way: Delegated types. With this approach, the "superclass" is a concrete class
|
|
31
|
+
# that is represented by its own table, where all the superclass attributes that are shared amongst all the
|
|
32
|
+
# "subclasses" are stored. And then each of the subclasses have their own individual tables for additional
|
|
33
|
+
# attributes that are particular to their implementation. This is similar to what's called multi-table
|
|
34
|
+
# inheritance in Django, but instead of actual inheritance, this approach uses delegation to form the
|
|
35
|
+
# hierarchy and share responsibilities.
|
|
36
|
+
#
|
|
37
|
+
# Let's look at that entry/message/comment example using delegated types:
|
|
38
|
+
#
|
|
39
|
+
# # Schema: entries[ id, account_id, creator_id, entryable_type, entryable_id, created_at, updated_at ]
|
|
40
|
+
# class Entry < ApplicationRecord
|
|
41
|
+
# belongs_to :account
|
|
42
|
+
# belongs_to :creator
|
|
43
|
+
# delegated_type :entryable, types: %w[ Message Comment ]
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# module Entryable
|
|
47
|
+
# extend ActiveSupport::Concern
|
|
48
|
+
#
|
|
49
|
+
# included do
|
|
50
|
+
# has_one :entry, as: :entryable, touch: true
|
|
51
|
+
# end
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# # Schema: messages[ id, subject, body, created_at, updated_at ]
|
|
55
|
+
# class Message < ApplicationRecord
|
|
56
|
+
# include Entryable
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# # Schema: comments[ id, content, created_at, updated_at ]
|
|
60
|
+
# class Comment < ApplicationRecord
|
|
61
|
+
# include Entryable
|
|
62
|
+
# end
|
|
63
|
+
#
|
|
64
|
+
# As you can see, neither +Message+ nor +Comment+ are meant to stand alone. Crucial metadata for both classes
|
|
65
|
+
# resides in the +Entry+ "superclass". But the +Entry+ absolutely can stand alone in terms of querying capacity
|
|
66
|
+
# in particular. You can now easily do things like:
|
|
67
|
+
#
|
|
68
|
+
# Account.find(1).entries.order(created_at: :desc).limit(50)
|
|
69
|
+
#
|
|
70
|
+
# Which is exactly what you want when displaying both comments and messages together. The entry itself can
|
|
71
|
+
# be rendered as its delegated type easily, like so:
|
|
72
|
+
#
|
|
73
|
+
# # entries/_entry.html.erb
|
|
74
|
+
# <%= render "entries/entryables/#{entry.entryable_name}", entry: entry %>
|
|
75
|
+
#
|
|
76
|
+
# # entries/entryables/_message.html.erb
|
|
77
|
+
# <div class="message">
|
|
78
|
+
# <div class="subject"><%= entry.message.subject %></div>
|
|
79
|
+
# <p><%= entry.message.body %></p>
|
|
80
|
+
# <i>Posted on <%= entry.created_at %> by <%= entry.creator.name %></i>
|
|
81
|
+
# </div>
|
|
82
|
+
#
|
|
83
|
+
# # entries/entryables/_comment.html.erb
|
|
84
|
+
# <div class="comment">
|
|
85
|
+
# <%= entry.creator.name %> said: <%= entry.comment.content %>
|
|
86
|
+
# </div>
|
|
87
|
+
#
|
|
88
|
+
# == Sharing behavior with concerns and controllers
|
|
89
|
+
#
|
|
90
|
+
# The entry "superclass" also serves as a perfect place to put all that shared logic that applies to both
|
|
91
|
+
# messages and comments, and which acts primarily on the shared attributes. Imagine:
|
|
92
|
+
#
|
|
93
|
+
# class Entry < ApplicationRecord
|
|
94
|
+
# include Eventable, Forwardable, Redeliverable
|
|
95
|
+
# end
|
|
96
|
+
#
|
|
97
|
+
# Which allows you to have controllers for things like +ForwardsController+ and +RedeliverableController+
|
|
98
|
+
# that both act on entries, and thus provide the shared functionality to both messages and comments.
|
|
99
|
+
#
|
|
100
|
+
# == Creating new records
|
|
101
|
+
#
|
|
102
|
+
# You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
|
|
103
|
+
# like so:
|
|
104
|
+
#
|
|
105
|
+
# Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user, account: Current.account
|
|
106
|
+
#
|
|
107
|
+
# If you need more complicated composition, or you need to perform dependent validation, you should build a factory
|
|
108
|
+
# method or class to take care of the complicated needs. This could be as simple as:
|
|
109
|
+
#
|
|
110
|
+
# class Entry < ApplicationRecord
|
|
111
|
+
# def self.create_with_comment(content, creator: Current.user, account: Current.account)
|
|
112
|
+
# create! entryable: Comment.new(content: content), creator: creator, account: account
|
|
113
|
+
# end
|
|
114
|
+
# end
|
|
115
|
+
#
|
|
116
|
+
# == Querying across records
|
|
117
|
+
#
|
|
118
|
+
# A consequence of delegated types is that querying attributes spread across multiple classes becomes slightly more
|
|
119
|
+
# tricky, but not impossible.
|
|
120
|
+
#
|
|
121
|
+
# The simplest method is to join the "superclass" to the "subclass" and apply the query parameters (i.e. <tt>#where</tt>)
|
|
122
|
+
# in appropriate places:
|
|
123
|
+
#
|
|
124
|
+
# Comment.joins(:entry).where(comments: { content: 'Hello!' }, entry: { creator: Current.user } )
|
|
125
|
+
#
|
|
126
|
+
# For convenience, add a scope on the concern. Now all classes that implement the concern will automatically include
|
|
127
|
+
# the method:
|
|
128
|
+
#
|
|
129
|
+
# # app/models/concerns/entryable.rb
|
|
130
|
+
# scope :with_entry, ->(attrs) { joins(:entry).where(entry: attrs) }
|
|
131
|
+
#
|
|
132
|
+
# Now the query can be shortened significantly:
|
|
133
|
+
#
|
|
134
|
+
# Comment.where(content: 'Hello!').with_entry(creator: Current.user)
|
|
135
|
+
#
|
|
136
|
+
# == Adding further delegation
|
|
137
|
+
#
|
|
138
|
+
# The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
|
|
139
|
+
# an anti-pattern most of the time. The reason you're building this hierarchy is to take advantage of polymorphism.
|
|
140
|
+
# So here's a simple example of that:
|
|
141
|
+
#
|
|
142
|
+
# class Entry < ApplicationRecord
|
|
143
|
+
# delegated_type :entryable, types: %w[ Message Comment ]
|
|
144
|
+
# delegate :title, to: :entryable
|
|
145
|
+
# end
|
|
146
|
+
#
|
|
147
|
+
# class Message < ApplicationRecord
|
|
148
|
+
# def title
|
|
149
|
+
# subject
|
|
150
|
+
# end
|
|
151
|
+
# end
|
|
152
|
+
#
|
|
153
|
+
# class Comment < ApplicationRecord
|
|
154
|
+
# def title
|
|
155
|
+
# content.truncate(20)
|
|
156
|
+
# end
|
|
157
|
+
# end
|
|
158
|
+
#
|
|
159
|
+
# Now you can list a bunch of entries, call <tt>Entry#title</tt>, and polymorphism will provide you with the answer.
|
|
160
|
+
#
|
|
161
|
+
# == Nested \Attributes
|
|
162
|
+
#
|
|
163
|
+
# Enabling nested attributes on a delegated_type association allows you to
|
|
164
|
+
# create the entry and message in one go:
|
|
165
|
+
#
|
|
166
|
+
# class Entry < ApplicationRecord
|
|
167
|
+
# delegated_type :entryable, types: %w[ Message Comment ]
|
|
168
|
+
# accepts_nested_attributes_for :entryable
|
|
169
|
+
# end
|
|
170
|
+
#
|
|
171
|
+
# params = { entry: { entryable_type: 'Message', entryable_attributes: { subject: 'Smiling' } } }
|
|
172
|
+
# entry = Entry.create(params[:entry])
|
|
173
|
+
# entry.entryable.id # => 2
|
|
174
|
+
# entry.entryable.subject # => 'Smiling'
|
|
175
|
+
module DelegatedType
|
|
176
|
+
# Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+.
|
|
177
|
+
# That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated
|
|
178
|
+
# type convenience methods:
|
|
179
|
+
#
|
|
180
|
+
# class Entry < ApplicationRecord
|
|
181
|
+
# delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
|
|
182
|
+
# end
|
|
183
|
+
#
|
|
184
|
+
# Entry#entryable_class # => +Message+ or +Comment+
|
|
185
|
+
# Entry#entryable_name # => "message" or "comment"
|
|
186
|
+
# Entry.messages # => Entry.where(entryable_type: "Message")
|
|
187
|
+
# Entry#message? # => true when entryable_type == "Message"
|
|
188
|
+
# Entry#message # => returns the message record, when entryable_type == "Message", otherwise nil
|
|
189
|
+
# Entry#message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil
|
|
190
|
+
# Entry.comments # => Entry.where(entryable_type: "Comment")
|
|
191
|
+
# Entry#comment? # => true when entryable_type == "Comment"
|
|
192
|
+
# Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
|
|
193
|
+
# Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
|
|
194
|
+
#
|
|
195
|
+
# You can also declare namespaced types:
|
|
196
|
+
#
|
|
197
|
+
# class Entry < ApplicationRecord
|
|
198
|
+
# delegated_type :entryable, types: %w[ Message Comment Access::NoticeMessage ], dependent: :destroy
|
|
199
|
+
# end
|
|
200
|
+
#
|
|
201
|
+
# Entry.access_notice_messages
|
|
202
|
+
# entry.access_notice_message
|
|
203
|
+
# entry.access_notice_message?
|
|
204
|
+
#
|
|
205
|
+
# === Options
|
|
206
|
+
#
|
|
207
|
+
# The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
|
|
208
|
+
# The following options can be included to specialize the behavior of the delegated type convenience methods.
|
|
209
|
+
#
|
|
210
|
+
# [:foreign_key]
|
|
211
|
+
# Specify the foreign key used for the convenience methods. By default this is guessed to be the passed
|
|
212
|
+
# +role+ with an "_id" suffix. So a class that defines a
|
|
213
|
+
# <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
|
|
214
|
+
# the default <tt>:foreign_key</tt>.
|
|
215
|
+
# [:foreign_type]
|
|
216
|
+
# Specify the column used to store the associated object's type. By default this is inferred to be the passed
|
|
217
|
+
# +role+ with a "_type" suffix. A class that defines a
|
|
218
|
+
# <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_type" as
|
|
219
|
+
# the default <tt>:foreign_type</tt>.
|
|
220
|
+
# [:primary_key]
|
|
221
|
+
# Specify the method that returns the primary key of associated object used for the convenience methods.
|
|
222
|
+
# By default this is +id+.
|
|
223
|
+
# [+:inverse_of+]
|
|
224
|
+
# Specifies the name of the #belongs_to association on the associated object
|
|
225
|
+
# that is the inverse of this #has_one association. By default, the class
|
|
226
|
+
# singularized class name is used unless a <tt>:foreign_key</tt> option is
|
|
227
|
+
# also provided. For example, a call to
|
|
228
|
+
# <tt>Entry.delegated_type</tt> will default to <tt>inverse_of: :entry</tt>.
|
|
229
|
+
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional
|
|
230
|
+
# associations for more detail.
|
|
231
|
+
#
|
|
232
|
+
# Option examples:
|
|
233
|
+
# class Entry < ApplicationRecord
|
|
234
|
+
# delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
|
|
235
|
+
# end
|
|
236
|
+
#
|
|
237
|
+
# Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
|
|
238
|
+
# Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
|
|
239
|
+
def delegated_type(role, types:, **options)
|
|
240
|
+
options[:inverse_of] = model_name.singular unless options.key?(:inverse_of) || options.key?(:foreign_key)
|
|
241
|
+
|
|
242
|
+
belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
|
|
243
|
+
define_delegated_type_methods role, types: types, options: options
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
private
|
|
247
|
+
def define_delegated_type_methods(role, types:, options:)
|
|
248
|
+
primary_key = options[:primary_key] || "id"
|
|
249
|
+
role_type = options[:foreign_type] || "#{role}_type"
|
|
250
|
+
role_id = options[:foreign_key] || "#{role}_id"
|
|
251
|
+
|
|
252
|
+
define_singleton_method "#{role}_types" do
|
|
253
|
+
types.map(&:to_s)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
define_method "#{role}_class" do
|
|
257
|
+
public_send(role_type).constantize
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
define_method "#{role}_name" do
|
|
261
|
+
public_send("#{role}_class").model_name.singular.inquiry
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
define_method "build_#{role}" do |*params|
|
|
265
|
+
public_send("#{role}=", public_send("#{role}_class").new(*params))
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
types.each do |type|
|
|
269
|
+
scope_name = type.tableize.tr("/", "_")
|
|
270
|
+
singular = scope_name.singularize
|
|
271
|
+
query = "#{singular}?"
|
|
272
|
+
|
|
273
|
+
scope scope_name, -> { where(role_type => type) }
|
|
274
|
+
|
|
275
|
+
define_method query do
|
|
276
|
+
public_send(role_type) == type
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
define_method singular do
|
|
280
|
+
public_send(role) if public_send(query)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
define_method "#{singular}_#{primary_key}" do
|
|
284
|
+
public_send(role_id) if public_send(query)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
class DestroyAssociationAsyncError < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# = Active Record Destroy Association Async Job
|
|
8
|
+
#
|
|
9
|
+
# Job to destroy the records associated with a destroyed record in background.
|
|
10
|
+
class DestroyAssociationAsyncJob < ActiveJob::Base
|
|
11
|
+
queue_as { ActiveRecord.queues[:destroy] }
|
|
12
|
+
|
|
13
|
+
discard_on ActiveJob::DeserializationError
|
|
14
|
+
|
|
15
|
+
def perform(
|
|
16
|
+
owner_model_name: nil, owner_id: nil,
|
|
17
|
+
association_class: nil, association_ids: nil, association_primary_key_column: nil,
|
|
18
|
+
ensuring_owner_was_method: nil
|
|
19
|
+
)
|
|
20
|
+
association_model = association_class.constantize
|
|
21
|
+
owner_class = owner_model_name.constantize
|
|
22
|
+
owner = owner_class.find_by(owner_class.primary_key => [owner_id])
|
|
23
|
+
|
|
24
|
+
if !owner_destroyed?(owner, ensuring_owner_was_method)
|
|
25
|
+
raise DestroyAssociationAsyncError, "owner record not destroyed"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
association_model.where(association_primary_key_column => association_ids).find_each do |r|
|
|
29
|
+
r.destroy
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
def owner_destroyed?(owner, ensuring_owner_was_method)
|
|
35
|
+
!owner || (ensuring_owner_was_method && owner.public_send(ensuring_owner_was_method))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
class DisableJoinsAssociationRelation < Relation # :nodoc:
|
|
5
|
+
attr_reader :ids, :key
|
|
6
|
+
|
|
7
|
+
def initialize(klass, key, ids)
|
|
8
|
+
@ids = ids.uniq
|
|
9
|
+
@key = key
|
|
10
|
+
super(klass)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def limit(value)
|
|
14
|
+
records.take(value)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def first(limit = nil)
|
|
18
|
+
if limit
|
|
19
|
+
records.limit(limit).first
|
|
20
|
+
else
|
|
21
|
+
records.first
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def load
|
|
26
|
+
super
|
|
27
|
+
records = @records
|
|
28
|
+
|
|
29
|
+
records_by_id = records.group_by do |record|
|
|
30
|
+
record[key]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
records = ids.flat_map { |id| records_by_id[id] }
|
|
34
|
+
records.compact!
|
|
35
|
+
|
|
36
|
+
@records = records
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|