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,328 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
class InsertAll # :nodoc:
|
|
7
|
+
attr_reader :model, :connection, :inserts, :keys
|
|
8
|
+
attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def execute(relation, ...)
|
|
12
|
+
relation.model.with_connection do |c|
|
|
13
|
+
new(relation, c, ...).execute
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(relation, connection, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
|
|
19
|
+
@relation = relation
|
|
20
|
+
@model, @connection, @inserts = relation.model, connection, inserts.map(&:stringify_keys)
|
|
21
|
+
@on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
|
|
22
|
+
@record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
|
|
23
|
+
|
|
24
|
+
disallow_raw_sql!(on_duplicate)
|
|
25
|
+
disallow_raw_sql!(returning)
|
|
26
|
+
|
|
27
|
+
if @inserts.empty?
|
|
28
|
+
@keys = []
|
|
29
|
+
else
|
|
30
|
+
resolve_sti
|
|
31
|
+
resolve_attribute_aliases
|
|
32
|
+
@keys = @inserts.first.keys
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@scope_attributes = relation.scope_for_create.except(@model.inheritance_column)
|
|
36
|
+
@keys |= @scope_attributes.keys
|
|
37
|
+
@keys = @keys.to_set
|
|
38
|
+
|
|
39
|
+
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
|
|
40
|
+
@returning = false if @returning == []
|
|
41
|
+
|
|
42
|
+
@unique_by = find_unique_index_for(@unique_by)
|
|
43
|
+
|
|
44
|
+
configure_on_duplicate_update_logic
|
|
45
|
+
ensure_valid_options_for_connection!
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def execute
|
|
49
|
+
return ActiveRecord::Result.empty if inserts.empty?
|
|
50
|
+
|
|
51
|
+
message = +"#{model} "
|
|
52
|
+
message << "Bulk " if inserts.many?
|
|
53
|
+
message << (on_duplicate == :update ? "Upsert" : "Insert")
|
|
54
|
+
connection.exec_insert_all to_sql, message
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def updatable_columns
|
|
58
|
+
@updatable_columns ||= keys - readonly_columns - unique_by_columns
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def primary_keys
|
|
62
|
+
Array(@model.schema_cache.primary_keys(model.table_name))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def skip_duplicates?
|
|
66
|
+
on_duplicate == :skip
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def update_duplicates?
|
|
70
|
+
on_duplicate == :update
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def map_key_with_value
|
|
74
|
+
inserts.map do |attributes|
|
|
75
|
+
attributes = attributes.stringify_keys
|
|
76
|
+
attributes.merge!(@scope_attributes)
|
|
77
|
+
attributes.reverse_merge!(timestamps_for_create) if record_timestamps?
|
|
78
|
+
|
|
79
|
+
verify_attributes(attributes)
|
|
80
|
+
|
|
81
|
+
keys_including_timestamps.map do |key|
|
|
82
|
+
yield key, attributes[key]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def record_timestamps?
|
|
88
|
+
@record_timestamps
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# TODO: Consider renaming this method, as it only conditionally extends keys, not always
|
|
92
|
+
def keys_including_timestamps
|
|
93
|
+
@keys_including_timestamps ||= if record_timestamps?
|
|
94
|
+
keys + model.all_timestamp_attributes_in_model
|
|
95
|
+
else
|
|
96
|
+
keys
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
def has_attribute_aliases?(attributes)
|
|
102
|
+
attributes.keys.any? { |attribute| model.attribute_alias?(attribute) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def resolve_sti
|
|
106
|
+
return if model.descends_from_active_record?
|
|
107
|
+
|
|
108
|
+
sti_type = model.sti_name
|
|
109
|
+
@inserts = @inserts.map do |insert|
|
|
110
|
+
insert.reverse_merge(model.inheritance_column.to_s => sti_type)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def resolve_attribute_aliases
|
|
115
|
+
return unless has_attribute_aliases?(@inserts.first)
|
|
116
|
+
|
|
117
|
+
@inserts = @inserts.map do |insert|
|
|
118
|
+
insert.transform_keys { |attribute| resolve_attribute_alias(attribute) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@update_only = Array(@update_only).map { |attribute| resolve_attribute_alias(attribute) } if @update_only
|
|
122
|
+
@unique_by = Array(@unique_by).map { |attribute| resolve_attribute_alias(attribute) } if @unique_by
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def resolve_attribute_alias(attribute)
|
|
126
|
+
model.attribute_alias(attribute) || attribute
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def configure_on_duplicate_update_logic
|
|
130
|
+
if custom_update_sql_provided? && update_only.present?
|
|
131
|
+
raise ArgumentError, "You can't set :update_only and provide custom update SQL via :on_duplicate at the same time"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if update_only.present?
|
|
135
|
+
@updatable_columns = Array(update_only)
|
|
136
|
+
@on_duplicate = :update
|
|
137
|
+
elsif custom_update_sql_provided?
|
|
138
|
+
@update_sql = on_duplicate
|
|
139
|
+
@on_duplicate = :update
|
|
140
|
+
elsif @on_duplicate == :update && updatable_columns.empty?
|
|
141
|
+
@on_duplicate = :skip
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def custom_update_sql_provided?
|
|
146
|
+
@custom_update_sql_provided ||= Arel.arel_node?(on_duplicate)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def find_unique_index_for(unique_by)
|
|
150
|
+
if !connection.supports_insert_conflict_target?
|
|
151
|
+
return if unique_by.nil?
|
|
152
|
+
|
|
153
|
+
raise ArgumentError, "#{connection.class} does not support :unique_by"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
name_or_columns = unique_by || model.primary_key
|
|
157
|
+
match = Array(name_or_columns).map(&:to_s)
|
|
158
|
+
sorted_match = match.sort
|
|
159
|
+
|
|
160
|
+
if index = unique_indexes.find { |i| match.include?(i.name) || Array(i.columns).sort == sorted_match }
|
|
161
|
+
index
|
|
162
|
+
elsif match == primary_keys
|
|
163
|
+
unique_by.nil? ? nil : ActiveRecord::ConnectionAdapters::IndexDefinition.new(model.table_name, "#{model.table_name}_primary_key", true, match)
|
|
164
|
+
else
|
|
165
|
+
raise ArgumentError, "No unique index found for #{name_or_columns}"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def unique_indexes
|
|
170
|
+
@model.schema_cache.indexes(model.table_name).select(&:unique)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def ensure_valid_options_for_connection!
|
|
174
|
+
if returning && !connection.supports_insert_returning?
|
|
175
|
+
raise ArgumentError, "#{connection.class} does not support :returning"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
if skip_duplicates? && !connection.supports_insert_on_duplicate_skip?
|
|
179
|
+
raise ArgumentError, "#{connection.class} does not support skipping duplicates"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
if update_duplicates? && !connection.supports_insert_on_duplicate_update?
|
|
183
|
+
raise ArgumentError, "#{connection.class} does not support upsert"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
if unique_by && !connection.supports_insert_conflict_target?
|
|
187
|
+
raise ArgumentError, "#{connection.class} does not support :unique_by"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def to_sql
|
|
193
|
+
connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(self))
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def readonly_columns
|
|
198
|
+
primary_keys + model.readonly_attributes
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def unique_by_columns
|
|
202
|
+
Array(unique_by&.columns)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def verify_attributes(attributes)
|
|
207
|
+
if keys_including_timestamps != attributes.keys.to_set
|
|
208
|
+
raise ArgumentError, "All objects being inserted must have the same keys"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def disallow_raw_sql!(value)
|
|
213
|
+
return if !value.is_a?(String) || Arel.arel_node?(value)
|
|
214
|
+
|
|
215
|
+
raise ArgumentError, "Dangerous query method (method whose arguments are used as raw " \
|
|
216
|
+
"SQL) called: #{value}. " \
|
|
217
|
+
"Known-safe values can be passed " \
|
|
218
|
+
"by wrapping them in Arel.sql()."
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def timestamps_for_create
|
|
222
|
+
model.all_timestamp_attributes_in_model.index_with(connection.high_precision_current_timestamp)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
class Builder # :nodoc:
|
|
226
|
+
attr_reader :model
|
|
227
|
+
|
|
228
|
+
delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
|
|
229
|
+
|
|
230
|
+
def initialize(insert_all)
|
|
231
|
+
@insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def into
|
|
235
|
+
"INTO #{model.quoted_table_name} (#{columns_list})"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def values_list
|
|
239
|
+
types = extract_types_from_columns_on(model.table_name, keys: keys_including_timestamps)
|
|
240
|
+
|
|
241
|
+
values_list = insert_all.map_key_with_value do |key, value|
|
|
242
|
+
next value if Arel::Nodes::SqlLiteral === value
|
|
243
|
+
connection.with_yaml_fallback(types[key].serialize(value))
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def returning
|
|
250
|
+
return unless insert_all.returning
|
|
251
|
+
|
|
252
|
+
if insert_all.returning.is_a?(String)
|
|
253
|
+
insert_all.returning
|
|
254
|
+
else
|
|
255
|
+
Array(insert_all.returning).map do |attribute|
|
|
256
|
+
if model.attribute_alias?(attribute)
|
|
257
|
+
"#{quote_column(model.attribute_alias(attribute))} AS #{quote_column(attribute)}"
|
|
258
|
+
else
|
|
259
|
+
quote_column(attribute)
|
|
260
|
+
end
|
|
261
|
+
end.join(",")
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def conflict_target
|
|
266
|
+
if index = insert_all.unique_by
|
|
267
|
+
sql = +"(#{format_columns(index.columns)})"
|
|
268
|
+
sql << " WHERE #{index.where}" if index.where
|
|
269
|
+
sql
|
|
270
|
+
elsif update_duplicates?
|
|
271
|
+
"(#{format_columns(insert_all.primary_keys)})"
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def updatable_columns
|
|
276
|
+
quote_columns(insert_all.updatable_columns)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def touch_model_timestamps_unless(&block)
|
|
280
|
+
return "" unless update_duplicates? && record_timestamps?
|
|
281
|
+
|
|
282
|
+
model.timestamp_attributes_for_update_in_model.filter_map do |column_name|
|
|
283
|
+
if touch_timestamp_attribute?(column_name)
|
|
284
|
+
"#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE #{connection.high_precision_current_timestamp} END),"
|
|
285
|
+
end
|
|
286
|
+
end.join
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def raw_update_sql
|
|
290
|
+
insert_all.update_sql
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
alias raw_update_sql? raw_update_sql
|
|
294
|
+
|
|
295
|
+
private
|
|
296
|
+
attr_reader :connection, :insert_all
|
|
297
|
+
|
|
298
|
+
def touch_timestamp_attribute?(column_name)
|
|
299
|
+
insert_all.updatable_columns.exclude?(column_name)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def columns_list
|
|
303
|
+
format_columns(insert_all.keys_including_timestamps)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def extract_types_from_columns_on(table_name, keys:)
|
|
307
|
+
columns = @model.schema_cache.columns_hash(table_name)
|
|
308
|
+
|
|
309
|
+
unknown_column = (keys - columns.keys).first
|
|
310
|
+
raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
|
|
311
|
+
|
|
312
|
+
keys.index_with { |key| model.type_for_attribute(key) }
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def format_columns(columns)
|
|
316
|
+
columns.respond_to?(:map) ? quote_columns(columns).join(",") : columns
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def quote_columns(columns)
|
|
320
|
+
columns.map { |column| quote_column(column) }
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def quote_column(column)
|
|
324
|
+
connection.quote_column_name(column)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/string/filters"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module Integration
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
##
|
|
11
|
+
# :singleton-method:
|
|
12
|
+
# Indicates the format used to generate the timestamp in the cache key, if
|
|
13
|
+
# versioning is off. Accepts any of the symbols in +Time::DATE_FORMATS+.
|
|
14
|
+
#
|
|
15
|
+
# This is +:usec+, by default.
|
|
16
|
+
class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# :singleton-method:
|
|
20
|
+
# Indicates whether to use a stable #cache_key method that is accompanied
|
|
21
|
+
# by a changing version in the #cache_version method.
|
|
22
|
+
#
|
|
23
|
+
# This is +true+, by default on \Rails 5.2 and above.
|
|
24
|
+
class_attribute :cache_versioning, instance_writer: false, default: false
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# :singleton-method:
|
|
28
|
+
# Indicates whether to use a stable #cache_key method that is accompanied
|
|
29
|
+
# by a changing version in the #cache_version method on collections.
|
|
30
|
+
#
|
|
31
|
+
# This is +false+, by default until \Rails 6.1.
|
|
32
|
+
class_attribute :collection_cache_versioning, instance_writer: false, default: false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns a +String+, which Action Pack uses for constructing a URL to this
|
|
36
|
+
# object. The default implementation returns this record's id as a +String+,
|
|
37
|
+
# or +nil+ if this record's unsaved.
|
|
38
|
+
#
|
|
39
|
+
# For example, suppose that you have a User model, and that you have a
|
|
40
|
+
# <tt>resources :users</tt> route. Normally, +user_path+ will
|
|
41
|
+
# construct a path with the user object's 'id' in it:
|
|
42
|
+
#
|
|
43
|
+
# user = User.find_by(name: 'Phusion')
|
|
44
|
+
# user_path(user) # => "/users/1"
|
|
45
|
+
#
|
|
46
|
+
# You can override +to_param+ in your model to make +user_path+ construct
|
|
47
|
+
# a path using the user's name instead of the user's id:
|
|
48
|
+
#
|
|
49
|
+
# class User < ActiveRecord::Base
|
|
50
|
+
# def to_param # overridden
|
|
51
|
+
# name
|
|
52
|
+
# end
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# user = User.find_by(name: 'Phusion')
|
|
56
|
+
# user_path(user) # => "/users/Phusion"
|
|
57
|
+
def to_param
|
|
58
|
+
return unless id
|
|
59
|
+
Array(id).join(self.class.param_delimiter)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns a stable cache key that can be used to identify this record.
|
|
63
|
+
#
|
|
64
|
+
# Product.new.cache_key # => "products/new"
|
|
65
|
+
# Product.find(5).cache_key # => "products/5"
|
|
66
|
+
#
|
|
67
|
+
# If ActiveRecord::Base.cache_versioning is turned off, as it was in \Rails 5.1 and earlier,
|
|
68
|
+
# the cache key will also include a version.
|
|
69
|
+
#
|
|
70
|
+
# Product.cache_versioning = false
|
|
71
|
+
# Product.find(5).cache_key # => "products/5-20071224150000" (updated_at available)
|
|
72
|
+
def cache_key
|
|
73
|
+
if new_record?
|
|
74
|
+
"#{model_name.cache_key}/new"
|
|
75
|
+
else
|
|
76
|
+
if cache_version
|
|
77
|
+
"#{model_name.cache_key}/#{id}"
|
|
78
|
+
else
|
|
79
|
+
timestamp = max_updated_column_timestamp
|
|
80
|
+
|
|
81
|
+
if timestamp
|
|
82
|
+
timestamp = timestamp.utc.to_fs(cache_timestamp_format)
|
|
83
|
+
"#{model_name.cache_key}/#{id}-#{timestamp}"
|
|
84
|
+
else
|
|
85
|
+
"#{model_name.cache_key}/#{id}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns a cache version that can be used together with the cache key to form
|
|
92
|
+
# a recyclable caching scheme. By default, the #updated_at column is used for the
|
|
93
|
+
# cache_version, but this method can be overwritten to return something else.
|
|
94
|
+
#
|
|
95
|
+
# Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
|
|
96
|
+
# +false+.
|
|
97
|
+
def cache_version
|
|
98
|
+
return unless cache_versioning
|
|
99
|
+
|
|
100
|
+
if has_attribute?("updated_at")
|
|
101
|
+
timestamp = updated_at_before_type_cast
|
|
102
|
+
if can_use_fast_cache_version?(timestamp)
|
|
103
|
+
raw_timestamp_to_cache_version(timestamp)
|
|
104
|
+
|
|
105
|
+
elsif timestamp = updated_at
|
|
106
|
+
timestamp.utc.to_fs(cache_timestamp_format)
|
|
107
|
+
end
|
|
108
|
+
elsif self.class.has_attribute?("updated_at")
|
|
109
|
+
raise ActiveModel::MissingAttributeError, "missing attribute 'updated_at' for #{self.class}"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns a cache key along with the version.
|
|
114
|
+
def cache_key_with_version
|
|
115
|
+
if version = cache_version
|
|
116
|
+
"#{cache_key}-#{version}"
|
|
117
|
+
else
|
|
118
|
+
cache_key
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
module ClassMethods
|
|
123
|
+
# Defines your model's +to_param+ method to generate "pretty" URLs
|
|
124
|
+
# using +method_name+, which can be any attribute or method that
|
|
125
|
+
# responds to +to_s+.
|
|
126
|
+
#
|
|
127
|
+
# class User < ActiveRecord::Base
|
|
128
|
+
# to_param :name
|
|
129
|
+
# end
|
|
130
|
+
#
|
|
131
|
+
# user = User.find_by(name: 'Fancy Pants')
|
|
132
|
+
# user.id # => 123
|
|
133
|
+
# user_path(user) # => "/users/123-fancy-pants"
|
|
134
|
+
#
|
|
135
|
+
# Values longer than 20 characters will be truncated. The value
|
|
136
|
+
# is truncated word by word.
|
|
137
|
+
#
|
|
138
|
+
# user = User.find_by(name: 'David Heinemeier Hansson')
|
|
139
|
+
# user.id # => 125
|
|
140
|
+
# user_path(user) # => "/users/125-david-heinemeier"
|
|
141
|
+
#
|
|
142
|
+
# Because the generated param begins with the record's +id+, it is
|
|
143
|
+
# suitable for passing to +find+. In a controller, for example:
|
|
144
|
+
#
|
|
145
|
+
# params[:id] # => "123-fancy-pants"
|
|
146
|
+
# User.find(params[:id]).id # => 123
|
|
147
|
+
def to_param(method_name = nil)
|
|
148
|
+
if method_name.nil?
|
|
149
|
+
super()
|
|
150
|
+
else
|
|
151
|
+
define_method :to_param do
|
|
152
|
+
if (default = super()) &&
|
|
153
|
+
(result = send(method_name).to_s).present? &&
|
|
154
|
+
(param = result.squish.parameterize.truncate(20, separator: /-/, omission: "")).present?
|
|
155
|
+
"#{default}-#{param}"
|
|
156
|
+
else
|
|
157
|
+
default
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
|
|
164
|
+
collection.send(:compute_cache_key, timestamp_column)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
# Detects if the value before type cast
|
|
170
|
+
# can be used to generate a cache_version.
|
|
171
|
+
#
|
|
172
|
+
# The fast cache version only works with a
|
|
173
|
+
# string value directly from the database.
|
|
174
|
+
#
|
|
175
|
+
# We also must check if the timestamp format has been changed
|
|
176
|
+
# or if the timezone is not set to UTC then
|
|
177
|
+
# we cannot apply our transformations correctly.
|
|
178
|
+
def can_use_fast_cache_version?(timestamp)
|
|
179
|
+
timestamp.is_a?(String) &&
|
|
180
|
+
cache_timestamp_format == :usec &&
|
|
181
|
+
# FIXME: checking out a connection for this is wasteful
|
|
182
|
+
# we should store/cache this information in the schema cache
|
|
183
|
+
# or similar.
|
|
184
|
+
self.class.with_connection(&:default_timezone) == :utc &&
|
|
185
|
+
!updated_at_came_from_user?
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Converts a raw database string to `:usec`
|
|
189
|
+
# format.
|
|
190
|
+
#
|
|
191
|
+
# Example:
|
|
192
|
+
#
|
|
193
|
+
# timestamp = "2018-10-15 20:02:15.266505"
|
|
194
|
+
# raw_timestamp_to_cache_version(timestamp)
|
|
195
|
+
# # => "20181015200215266505"
|
|
196
|
+
#
|
|
197
|
+
# PostgreSQL truncates trailing zeros,
|
|
198
|
+
# https://github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214
|
|
199
|
+
# to account for this we pad the output with zeros
|
|
200
|
+
def raw_timestamp_to_cache_version(timestamp)
|
|
201
|
+
key = timestamp.delete("- :.")
|
|
202
|
+
if key.length < 20
|
|
203
|
+
key.ljust(20, "0")
|
|
204
|
+
else
|
|
205
|
+
key
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/scoping/default"
|
|
4
|
+
require "active_record/scoping/named"
|
|
5
|
+
|
|
6
|
+
module ActiveRecord
|
|
7
|
+
# This class is used to create a table that keeps track of values and keys such
|
|
8
|
+
# as which environment migrations were run in.
|
|
9
|
+
#
|
|
10
|
+
# This is enabled by default. To disable this functionality set
|
|
11
|
+
# `use_metadata_table` to false in your database configuration.
|
|
12
|
+
class InternalMetadata # :nodoc:
|
|
13
|
+
class NullInternalMetadata # :nodoc:
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :arel_table
|
|
17
|
+
|
|
18
|
+
def initialize(pool)
|
|
19
|
+
@pool = pool
|
|
20
|
+
@arel_table = Arel::Table.new(table_name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def primary_key
|
|
24
|
+
"key"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def value_key
|
|
28
|
+
"value"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def table_name
|
|
32
|
+
"#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def enabled?
|
|
36
|
+
@pool.db_config.use_metadata_table?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def []=(key, value)
|
|
40
|
+
return unless enabled?
|
|
41
|
+
|
|
42
|
+
@pool.with_connection do |connection|
|
|
43
|
+
update_or_create_entry(connection, key, value)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def [](key)
|
|
48
|
+
return unless enabled?
|
|
49
|
+
|
|
50
|
+
@pool.with_connection do |connection|
|
|
51
|
+
if entry = select_entry(connection, key)
|
|
52
|
+
entry[value_key]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def delete_all_entries
|
|
58
|
+
dm = Arel::DeleteManager.new(arel_table)
|
|
59
|
+
|
|
60
|
+
@pool.with_connection do |connection|
|
|
61
|
+
connection.delete(dm, "#{self.class} Destroy")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def count
|
|
66
|
+
sm = Arel::SelectManager.new(arel_table)
|
|
67
|
+
sm.project(*Arel::Nodes::Count.new([Arel.star]))
|
|
68
|
+
|
|
69
|
+
@pool.with_connection do |connection|
|
|
70
|
+
connection.select_values(sm, "#{self.class} Count").first
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def create_table_and_set_flags(environment, schema_sha1 = nil)
|
|
75
|
+
return unless enabled?
|
|
76
|
+
|
|
77
|
+
@pool.with_connection do |connection|
|
|
78
|
+
create_table
|
|
79
|
+
update_or_create_entry(connection, :environment, environment)
|
|
80
|
+
update_or_create_entry(connection, :schema_sha1, schema_sha1) if schema_sha1
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Creates an internal metadata table with columns +key+ and +value+
|
|
85
|
+
def create_table
|
|
86
|
+
return unless enabled?
|
|
87
|
+
|
|
88
|
+
@pool.with_connection do |connection|
|
|
89
|
+
unless connection.table_exists?(table_name)
|
|
90
|
+
connection.create_table(table_name, id: false) do |t|
|
|
91
|
+
t.string :key, **connection.internal_string_options_for_primary_key
|
|
92
|
+
t.string :value
|
|
93
|
+
t.timestamps
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def drop_table
|
|
100
|
+
return unless enabled?
|
|
101
|
+
|
|
102
|
+
@pool.with_connection do |connection|
|
|
103
|
+
connection.drop_table table_name, if_exists: true
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def table_exists?
|
|
108
|
+
@pool.schema_cache.data_source_exists?(table_name)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
def update_or_create_entry(connection, key, value)
|
|
113
|
+
entry = select_entry(connection, key)
|
|
114
|
+
|
|
115
|
+
if entry
|
|
116
|
+
if entry[value_key] != value
|
|
117
|
+
update_entry(connection, key, value)
|
|
118
|
+
else
|
|
119
|
+
entry[value_key]
|
|
120
|
+
end
|
|
121
|
+
else
|
|
122
|
+
create_entry(connection, key, value)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def current_time(connection)
|
|
127
|
+
connection.default_timezone == :utc ? Time.now.utc : Time.now
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def create_entry(connection, key, value)
|
|
131
|
+
im = Arel::InsertManager.new(arel_table)
|
|
132
|
+
im.insert [
|
|
133
|
+
[arel_table[primary_key], key],
|
|
134
|
+
[arel_table[value_key], value],
|
|
135
|
+
[arel_table[:created_at], current_time(connection)],
|
|
136
|
+
[arel_table[:updated_at], current_time(connection)]
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
connection.insert(im, "#{self.class} Create", primary_key, key)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def update_entry(connection, key, new_value)
|
|
143
|
+
um = Arel::UpdateManager.new(arel_table)
|
|
144
|
+
um.set [
|
|
145
|
+
[arel_table[value_key], new_value],
|
|
146
|
+
[arel_table[:updated_at], current_time(connection)]
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
um.where(arel_table[primary_key].eq(key))
|
|
150
|
+
|
|
151
|
+
connection.update(um, "#{self.class} Update")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def select_entry(connection, key)
|
|
155
|
+
sm = Arel::SelectManager.new(arel_table)
|
|
156
|
+
sm.project(Arel::Nodes::SqlLiteral.new("*", retryable: true))
|
|
157
|
+
sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
|
|
158
|
+
sm.order(arel_table[primary_key].asc)
|
|
159
|
+
sm.limit = 1
|
|
160
|
+
|
|
161
|
+
connection.select_all(sm, "#{self.class} Load").first
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|