activerecord 5.2.8 → 7.0.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1393 -587
- data/MIT-LICENSE +3 -1
- data/README.rdoc +7 -5
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +10 -9
- data/lib/active_record/association_relation.rb +22 -12
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +122 -47
- data/lib/active_record/associations/association_scope.rb +24 -24
- data/lib/active_record/associations/belongs_to_association.rb +67 -49
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -7
- data/lib/active_record/associations/builder/association.rb +52 -23
- data/lib/active_record/associations/builder/belongs_to.rb +44 -61
- data/lib/active_record/associations/builder/collection_association.rb +17 -19
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
- data/lib/active_record/associations/builder/has_many.rb +10 -3
- data/lib/active_record/associations/builder/has_one.rb +35 -3
- data/lib/active_record/associations/builder/singular_association.rb +5 -3
- data/lib/active_record/associations/collection_association.rb +59 -50
- data/lib/active_record/associations/collection_proxy.rb +32 -23
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/foreign_association.rb +20 -0
- data/lib/active_record/associations/has_many_association.rb +27 -14
- data/lib/active_record/associations/has_many_through_association.rb +26 -19
- data/lib/active_record/associations/has_one_association.rb +52 -37
- data/lib/active_record/associations/has_one_through_association.rb +6 -6
- data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
- data/lib/active_record/associations/join_dependency.rb +97 -62
- data/lib/active_record/associations/preloader/association.rb +220 -60
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +147 -0
- data/lib/active_record/associations/preloader/through_association.rb +85 -40
- data/lib/active_record/associations/preloader.rb +44 -105
- data/lib/active_record/associations/singular_association.rb +9 -17
- data/lib/active_record/associations/through_association.rb +4 -4
- data/lib/active_record/associations.rb +207 -66
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +17 -19
- data/lib/active_record/attribute_methods/before_type_cast.rb +19 -8
- data/lib/active_record/attribute_methods/dirty.rb +141 -47
- data/lib/active_record/attribute_methods/primary_key.rb +22 -27
- data/lib/active_record/attribute_methods/query.rb +6 -10
- data/lib/active_record/attribute_methods/read.rb +15 -55
- data/lib/active_record/attribute_methods/serialization.rb +77 -18
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +16 -18
- data/lib/active_record/attribute_methods/write.rb +18 -37
- data/lib/active_record/attribute_methods.rb +90 -153
- data/lib/active_record/attributes.rb +38 -12
- data/lib/active_record/autosave_association.rb +50 -50
- data/lib/active_record/base.rb +23 -18
- data/lib/active_record/callbacks.rb +159 -44
- data/lib/active_record/coders/yaml_column.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +92 -464
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -51
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +209 -164
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +38 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +103 -82
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +140 -110
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -94
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +16 -5
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +456 -159
- data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -78
- data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -162
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +311 -327
- data/lib/active_record/connection_adapters/column.rb +33 -11
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +113 -45
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +71 -5
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +25 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +143 -19
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +63 -22
- data/lib/active_record/connection_adapters/pool_config.rb +73 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +53 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +56 -63
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +54 -16
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- 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 +26 -12
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -52
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +39 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +128 -91
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +149 -113
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
- data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +386 -182
- data/lib/active_record/connection_adapters/schema_cache.rb +161 -22
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +65 -18
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +92 -26
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +251 -204
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_adapters.rb +53 -0
- data/lib/active_record/connection_handling.rb +292 -38
- data/lib/active_record/core.rb +385 -158
- data/lib/active_record/counter_cache.rb +8 -30
- data/lib/active_record/database_configurations/connection_url_resolver.rb +100 -0
- data/lib/active_record/database_configurations/database_config.rb +83 -0
- data/lib/active_record/database_configurations/hash_config.rb +154 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/database_configurations.rb +256 -0
- data/lib/active_record/delegated_type.rb +250 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +4 -5
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +44 -0
- data/lib/active_record/encryption/configurable.rb +61 -0
- data/lib/active_record/encryption/context.rb +35 -0
- data/lib/active_record/encryption/contexts.rb +72 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +208 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -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 +155 -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 +160 -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 +42 -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_serializer.rb +90 -0
- data/lib/active_record/encryption/null_encryptor.rb +21 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
- data/lib/active_record/encryption/scheme.rb +99 -0
- data/lib/active_record/encryption.rb +55 -0
- data/lib/active_record/enum.rb +130 -51
- data/lib/active_record/errors.rb +129 -23
- data/lib/active_record/explain.rb +10 -6
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +22 -15
- data/lib/active_record/fixture_set/model_metadata.rb +32 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +187 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +206 -490
- data/lib/active_record/future_result.rb +139 -0
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +104 -37
- data/lib/active_record/insert_all.rb +278 -0
- data/lib/active_record/integration.rb +69 -18
- data/lib/active_record/internal_metadata.rb +24 -9
- data/lib/active_record/legacy_yaml_adapter.rb +3 -36
- data/lib/active_record/locking/optimistic.rb +41 -26
- data/lib/active_record/locking/pessimistic.rb +18 -8
- data/lib/active_record/log_subscriber.rb +46 -35
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
- data/lib/active_record/middleware/database_selector.rb +82 -0
- data/lib/active_record/middleware/shard_selector.rb +60 -0
- data/lib/active_record/migration/command_recorder.rb +96 -44
- data/lib/active_record/migration/compatibility.rb +246 -64
- data/lib/active_record/migration/join_table.rb +1 -2
- data/lib/active_record/migration.rb +266 -187
- data/lib/active_record/model_schema.rb +165 -52
- data/lib/active_record/nested_attributes.rb +17 -19
- data/lib/active_record/no_touching.rb +11 -4
- data/lib/active_record/null_relation.rb +2 -7
- data/lib/active_record/persistence.rb +467 -92
- data/lib/active_record/query_cache.rb +21 -4
- data/lib/active_record/query_logs.rb +138 -0
- data/lib/active_record/querying.rb +51 -24
- data/lib/active_record/railtie.rb +224 -57
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/controller_runtime.rb +31 -36
- data/lib/active_record/railties/databases.rake +369 -101
- data/lib/active_record/readonly_attributes.rb +15 -0
- data/lib/active_record/reflection.rb +170 -137
- data/lib/active_record/relation/batches/batch_enumerator.rb +44 -14
- data/lib/active_record/relation/batches.rb +46 -37
- data/lib/active_record/relation/calculations.rb +168 -96
- data/lib/active_record/relation/delegation.rb +37 -52
- data/lib/active_record/relation/finder_methods.rb +79 -58
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +50 -51
- data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
- data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +58 -46
- data/lib/active_record/relation/query_attribute.rb +9 -10
- data/lib/active_record/relation/query_methods.rb +685 -208
- data/lib/active_record/relation/record_fetch_warning.rb +9 -11
- data/lib/active_record/relation/spawn_methods.rb +10 -10
- data/lib/active_record/relation/where_clause.rb +108 -64
- data/lib/active_record/relation.rb +515 -151
- data/lib/active_record/result.rb +78 -42
- data/lib/active_record/runtime_registry.rb +9 -13
- data/lib/active_record/sanitization.rb +29 -44
- data/lib/active_record/schema.rb +37 -31
- data/lib/active_record/schema_dumper.rb +74 -23
- data/lib/active_record/schema_migration.rb +7 -9
- data/lib/active_record/scoping/default.rb +62 -17
- data/lib/active_record/scoping/named.rb +17 -32
- data/lib/active_record/scoping.rb +70 -41
- data/lib/active_record/secure_token.rb +16 -8
- data/lib/active_record/serialization.rb +6 -4
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +49 -6
- data/lib/active_record/store.rb +88 -9
- data/lib/active_record/suppressor.rb +13 -17
- data/lib/active_record/table_metadata.rb +42 -43
- data/lib/active_record/tasks/database_tasks.rb +352 -94
- data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
- data/lib/active_record/tasks/postgresql_database_tasks.rb +41 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +287 -0
- data/lib/active_record/timestamp.rb +44 -34
- data/lib/active_record/touch_later.rb +23 -22
- data/lib/active_record/transactions.rb +67 -128
- data/lib/active_record/translation.rb +3 -3
- data/lib/active_record/type/adapter_specific_registry.rb +34 -19
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
- data/lib/active_record/type/internal/timezone.rb +2 -2
- data/lib/active_record/type/serialized.rb +7 -4
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/type/type_map.rb +17 -21
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +9 -5
- data/lib/active_record/type_caster/connection.rb +15 -15
- data/lib/active_record/type_caster/map.rb +8 -8
- data/lib/active_record/validations/associated.rb +2 -3
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +39 -31
- data/lib/active_record/validations.rb +4 -3
- data/lib/active_record.rb +209 -32
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +33 -0
- data/lib/arel/collectors/bind.rb +29 -0
- data/lib/arel/collectors/composite.rb +39 -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 +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -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/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +126 -0
- data/lib/arel/nodes/bind_param.rb +44 -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/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/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 +76 -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/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +51 -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 +19 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +31 -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 +71 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +258 -0
- data/lib/arel/select_manager.rb +276 -0
- data/lib/arel/table.rb +117 -0
- data/lib/arel/tree_manager.rb +60 -0
- data/lib/arel/update_manager.rb +48 -0
- data/lib/arel/visitors/dot.rb +298 -0
- data/lib/arel/visitors/mysql.rb +99 -0
- data/lib/arel/visitors/postgresql.rb +110 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +955 -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 +55 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
- data/lib/rails/generators/active_record/migration.rb +19 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
- 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 +10 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +162 -32
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
- data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -5,12 +5,13 @@ module ActiveRecord
|
|
5
5
|
class Relation
|
6
6
|
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
|
7
7
|
:order, :joins, :left_outer_joins, :references,
|
8
|
-
:extending, :unscope]
|
8
|
+
:extending, :unscope, :optimizer_hints, :annotate]
|
9
9
|
|
10
|
-
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
|
10
|
+
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :strict_loading,
|
11
11
|
:reverse_order, :distinct, :create_with, :skip_query_cache]
|
12
|
+
|
12
13
|
CLAUSE_METHODS = [:where, :having, :from]
|
13
|
-
INVALID_METHODS_FOR_DELETE_ALL = [:distinct
|
14
|
+
INVALID_METHODS_FOR_DELETE_ALL = [:distinct]
|
14
15
|
|
15
16
|
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
|
16
17
|
|
@@ -18,6 +19,7 @@ module ActiveRecord
|
|
18
19
|
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
|
19
20
|
|
20
21
|
attr_reader :table, :klass, :loaded, :predicate_builder
|
22
|
+
attr_accessor :skip_preloading_value
|
21
23
|
alias :model :klass
|
22
24
|
alias :loaded? :loaded
|
23
25
|
alias :locked? :lock_value
|
@@ -26,10 +28,11 @@ module ActiveRecord
|
|
26
28
|
@klass = klass
|
27
29
|
@table = table
|
28
30
|
@values = values
|
29
|
-
@offsets = {}
|
30
31
|
@loaded = false
|
31
32
|
@predicate_builder = predicate_builder
|
32
33
|
@delegate_to_klass = false
|
34
|
+
@future_result = nil
|
35
|
+
@records = nil
|
33
36
|
end
|
34
37
|
|
35
38
|
def initialize_copy(other)
|
@@ -37,8 +40,15 @@ module ActiveRecord
|
|
37
40
|
reset
|
38
41
|
end
|
39
42
|
|
40
|
-
def
|
41
|
-
klass.
|
43
|
+
def bind_attribute(name, value) # :nodoc:
|
44
|
+
if reflection = klass._reflect_on_association(name)
|
45
|
+
name = reflection.foreign_key
|
46
|
+
value = value.read_attribute(reflection.klass.primary_key) unless value.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
attr = table[name]
|
50
|
+
bind = predicate_builder.build_bind_attribute(attr.name, value)
|
51
|
+
yield attr, bind
|
42
52
|
end
|
43
53
|
|
44
54
|
# Initializes new record from relation while maintaining the current
|
@@ -54,9 +64,13 @@ module ActiveRecord
|
|
54
64
|
# user = users.new { |user| user.name = 'Oscar' }
|
55
65
|
# user.name # => Oscar
|
56
66
|
def new(attributes = nil, &block)
|
57
|
-
|
67
|
+
if attributes.is_a?(Array)
|
68
|
+
attributes.collect { |attr| new(attr, &block) }
|
69
|
+
else
|
70
|
+
block = current_scope_restoring_block(&block)
|
71
|
+
scoping { _new(attributes, &block) }
|
72
|
+
end
|
58
73
|
end
|
59
|
-
|
60
74
|
alias build new
|
61
75
|
|
62
76
|
# Tries to create a new record with the same scoped attributes
|
@@ -82,7 +96,8 @@ module ActiveRecord
|
|
82
96
|
if attributes.is_a?(Array)
|
83
97
|
attributes.collect { |attr| create(attr, &block) }
|
84
98
|
else
|
85
|
-
|
99
|
+
block = current_scope_restoring_block(&block)
|
100
|
+
scoping { _create(attributes, &block) }
|
86
101
|
end
|
87
102
|
end
|
88
103
|
|
@@ -96,7 +111,8 @@ module ActiveRecord
|
|
96
111
|
if attributes.is_a?(Array)
|
97
112
|
attributes.collect { |attr| create!(attr, &block) }
|
98
113
|
else
|
99
|
-
|
114
|
+
block = current_scope_restoring_block(&block)
|
115
|
+
scoping { _create!(attributes, &block) }
|
100
116
|
end
|
101
117
|
end
|
102
118
|
|
@@ -133,7 +149,7 @@ module ActiveRecord
|
|
133
149
|
# above can be alternatively written this way:
|
134
150
|
#
|
135
151
|
# # Find the first user named "Scarlett" or create a new one with a
|
136
|
-
# #
|
152
|
+
# # particular last name.
|
137
153
|
# User.find_or_create_by(first_name: 'Scarlett') do |user|
|
138
154
|
# user.last_name = 'Johansson'
|
139
155
|
# end
|
@@ -143,23 +159,12 @@ module ActiveRecord
|
|
143
159
|
# failed due to validation errors it won't be persisted, you get what
|
144
160
|
# #create returns in such situation.
|
145
161
|
#
|
146
|
-
# Please note
|
162
|
+
# Please note <b>this method is not atomic</b>, it runs first a SELECT, and if
|
147
163
|
# there are no results an INSERT is attempted. If there are other threads
|
148
164
|
# or processes there is a race condition between both calls and it could
|
149
165
|
# be the case that you end up with two similar records.
|
150
166
|
#
|
151
|
-
#
|
152
|
-
# application, but in the particular case in which rows have a UNIQUE
|
153
|
-
# constraint an exception may be raised, just retry:
|
154
|
-
#
|
155
|
-
# begin
|
156
|
-
# CreditAccount.transaction(requires_new: true) do
|
157
|
-
# CreditAccount.find_or_create_by(user_id: user.id)
|
158
|
-
# end
|
159
|
-
# rescue ActiveRecord::RecordNotUnique
|
160
|
-
# retry
|
161
|
-
# end
|
162
|
-
#
|
167
|
+
# If this might be a problem for your application, please see #create_or_find_by.
|
163
168
|
def find_or_create_by(attributes, &block)
|
164
169
|
find_by(attributes) || create(attributes, &block)
|
165
170
|
end
|
@@ -171,6 +176,51 @@ module ActiveRecord
|
|
171
176
|
find_by(attributes) || create!(attributes, &block)
|
172
177
|
end
|
173
178
|
|
179
|
+
# Attempts to create a record with the given attributes in a table that has a unique database constraint
|
180
|
+
# on one or several of its columns. If a row already exists with one or several of these
|
181
|
+
# unique constraints, the exception such an insertion would normally raise is caught,
|
182
|
+
# and the existing record with those attributes is found using #find_by!.
|
183
|
+
#
|
184
|
+
# This is similar to #find_or_create_by, but avoids the problem of stale reads between the SELECT
|
185
|
+
# and the INSERT, as that method needs to first query the table, then attempt to insert a row
|
186
|
+
# if none is found.
|
187
|
+
#
|
188
|
+
# There are several drawbacks to #create_or_find_by, though:
|
189
|
+
#
|
190
|
+
# * The underlying table must have the relevant columns defined with unique database constraints.
|
191
|
+
# * A unique constraint violation may be triggered by only one, or at least less than all,
|
192
|
+
# of the given attributes. This means that the subsequent #find_by! may fail to find a
|
193
|
+
# matching record, which will then raise an <tt>ActiveRecord::RecordNotFound</tt> exception,
|
194
|
+
# rather than a record with the given attributes.
|
195
|
+
# * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by,
|
196
|
+
# we actually have another race condition between INSERT -> SELECT, which can be triggered
|
197
|
+
# if a DELETE between those two statements is run by another client. But for most applications,
|
198
|
+
# that's a significantly less likely condition to hit.
|
199
|
+
# * It relies on exception handling to handle control flow, which may be marginally slower.
|
200
|
+
# * The primary key may auto-increment on each create, even if it fails. This can accelerate
|
201
|
+
# the problem of running out of integers, if the underlying table is still stuck on a primary
|
202
|
+
# key of type int (note: All Rails apps since 5.1+ have defaulted to bigint, which is not liable
|
203
|
+
# to this problem).
|
204
|
+
#
|
205
|
+
# This method will return a record if all given attributes are covered by unique constraints
|
206
|
+
# (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted
|
207
|
+
# and failed due to validation errors it won't be persisted, you get what #create returns in
|
208
|
+
# such situation.
|
209
|
+
def create_or_find_by(attributes, &block)
|
210
|
+
transaction(requires_new: true) { create(attributes, &block) }
|
211
|
+
rescue ActiveRecord::RecordNotUnique
|
212
|
+
find_by!(attributes)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Like #create_or_find_by, but calls
|
216
|
+
# {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
|
217
|
+
# is raised if the created record is invalid.
|
218
|
+
def create_or_find_by!(attributes, &block)
|
219
|
+
transaction(requires_new: true) { create!(attributes, &block) }
|
220
|
+
rescue ActiveRecord::RecordNotUnique
|
221
|
+
find_by!(attributes)
|
222
|
+
end
|
223
|
+
|
174
224
|
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
|
175
225
|
# instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
|
176
226
|
def find_or_initialize_by(attributes, &block)
|
@@ -185,7 +235,7 @@ module ActiveRecord
|
|
185
235
|
# are needed by the next ones when eager loading is going on.
|
186
236
|
#
|
187
237
|
# Please see further details in the
|
188
|
-
# {Active Record Query Interface guide}[
|
238
|
+
# {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
|
189
239
|
def explain
|
190
240
|
exec_explain(collecting_queries_for_explain { exec_queries })
|
191
241
|
end
|
@@ -208,13 +258,20 @@ module ActiveRecord
|
|
208
258
|
|
209
259
|
# Returns size of the records.
|
210
260
|
def size
|
211
|
-
loaded?
|
261
|
+
if loaded?
|
262
|
+
records.length
|
263
|
+
else
|
264
|
+
count(:all)
|
265
|
+
end
|
212
266
|
end
|
213
267
|
|
214
268
|
# Returns true if there are no records.
|
215
269
|
def empty?
|
216
|
-
|
217
|
-
|
270
|
+
if loaded?
|
271
|
+
records.empty?
|
272
|
+
else
|
273
|
+
!exists?
|
274
|
+
end
|
218
275
|
end
|
219
276
|
|
220
277
|
# Returns true if there are no records.
|
@@ -232,39 +289,119 @@ module ActiveRecord
|
|
232
289
|
# Returns true if there is exactly one record.
|
233
290
|
def one?
|
234
291
|
return super if block_given?
|
235
|
-
|
292
|
+
return records.one? if loaded?
|
293
|
+
limited_count == 1
|
236
294
|
end
|
237
295
|
|
238
296
|
# Returns true if there is more than one record.
|
239
297
|
def many?
|
240
298
|
return super if block_given?
|
241
|
-
|
299
|
+
return records.many? if loaded?
|
300
|
+
limited_count > 1
|
242
301
|
end
|
243
302
|
|
244
|
-
# Returns a cache key that can be used to identify
|
245
|
-
#
|
246
|
-
# the number of records matched by the query and a timestamp of the last
|
247
|
-
# updated record. When a new record comes to match the query, or any of
|
248
|
-
# the existing records is updated or deleted, the cache key changes.
|
303
|
+
# Returns a stable cache key that can be used to identify this query.
|
304
|
+
# The cache key is built with a fingerprint of the SQL query.
|
249
305
|
#
|
250
|
-
#
|
251
|
-
#
|
306
|
+
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
|
307
|
+
# # => "products/query-1850ab3d302391b85b8693e941286659"
|
252
308
|
#
|
253
|
-
# If
|
254
|
-
#
|
309
|
+
# If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
|
310
|
+
# in Rails 6.0 and earlier, the cache key will also include a version.
|
255
311
|
#
|
256
|
-
#
|
312
|
+
# ActiveRecord::Base.collection_cache_versioning = false
|
313
|
+
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
|
314
|
+
# # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
|
257
315
|
#
|
258
316
|
# You can also pass a custom timestamp column to fetch the timestamp of the
|
259
317
|
# last updated record.
|
260
318
|
#
|
261
319
|
# Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
|
262
|
-
|
263
|
-
# You can customize the strategy to generate the key on a per model basis
|
264
|
-
# overriding ActiveRecord::Base#collection_cache_key.
|
265
|
-
def cache_key(timestamp_column = :updated_at)
|
320
|
+
def cache_key(timestamp_column = "updated_at")
|
266
321
|
@cache_keys ||= {}
|
267
|
-
@cache_keys[timestamp_column] ||=
|
322
|
+
@cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
|
323
|
+
end
|
324
|
+
|
325
|
+
def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
|
326
|
+
query_signature = ActiveSupport::Digest.hexdigest(to_sql)
|
327
|
+
key = "#{klass.model_name.cache_key}/query-#{query_signature}"
|
328
|
+
|
329
|
+
if collection_cache_versioning
|
330
|
+
key
|
331
|
+
else
|
332
|
+
"#{key}-#{compute_cache_version(timestamp_column)}"
|
333
|
+
end
|
334
|
+
end
|
335
|
+
private :compute_cache_key
|
336
|
+
|
337
|
+
# Returns a cache version that can be used together with the cache key to form
|
338
|
+
# a recyclable caching scheme. The cache version is built with the number of records
|
339
|
+
# matching the query, and the timestamp of the last updated record. When a new record
|
340
|
+
# comes to match the query, or any of the existing records is updated or deleted,
|
341
|
+
# the cache version changes.
|
342
|
+
#
|
343
|
+
# If the collection is loaded, the method will iterate through the records
|
344
|
+
# to generate the timestamp, otherwise it will trigger one SQL query like:
|
345
|
+
#
|
346
|
+
# SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
|
347
|
+
def cache_version(timestamp_column = :updated_at)
|
348
|
+
if collection_cache_versioning
|
349
|
+
@cache_versions ||= {}
|
350
|
+
@cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def compute_cache_version(timestamp_column) # :nodoc:
|
355
|
+
timestamp_column = timestamp_column.to_s
|
356
|
+
|
357
|
+
if loaded?
|
358
|
+
size = records.size
|
359
|
+
if size > 0
|
360
|
+
timestamp = records.map { |record| record.read_attribute(timestamp_column) }.max
|
361
|
+
end
|
362
|
+
else
|
363
|
+
collection = eager_loading? ? apply_join_dependency : self
|
364
|
+
|
365
|
+
column = connection.visitor.compile(table[timestamp_column])
|
366
|
+
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
|
367
|
+
|
368
|
+
if collection.has_limit_or_offset?
|
369
|
+
query = collection.select("#{column} AS collection_cache_key_timestamp")
|
370
|
+
query._select!(table[Arel.star]) if distinct_value && collection.select_values.empty?
|
371
|
+
subquery_alias = "subquery_for_cache_key"
|
372
|
+
subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
|
373
|
+
arel = query.build_subquery(subquery_alias, select_values % subquery_column)
|
374
|
+
else
|
375
|
+
query = collection.unscope(:order)
|
376
|
+
query.select_values = [select_values % column]
|
377
|
+
arel = query.arel
|
378
|
+
end
|
379
|
+
|
380
|
+
size, timestamp = connection.select_rows(arel, nil).first
|
381
|
+
|
382
|
+
if size
|
383
|
+
column_type = klass.type_for_attribute(timestamp_column)
|
384
|
+
timestamp = column_type.deserialize(timestamp)
|
385
|
+
else
|
386
|
+
size = 0
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
if timestamp
|
391
|
+
"#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
|
392
|
+
else
|
393
|
+
"#{size}"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
private :compute_cache_version
|
397
|
+
|
398
|
+
# Returns a cache key along with the version.
|
399
|
+
def cache_key_with_version
|
400
|
+
if version = cache_version
|
401
|
+
"#{cache_key}-#{version}"
|
402
|
+
else
|
403
|
+
cache_key
|
404
|
+
end
|
268
405
|
end
|
269
406
|
|
270
407
|
# Scope all queries to the current scope.
|
@@ -274,18 +411,28 @@ module ActiveRecord
|
|
274
411
|
# end
|
275
412
|
# # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
|
276
413
|
#
|
414
|
+
# If <tt>all_queries: true</tt> is passed, scoping will apply to all queries
|
415
|
+
# for the relation including +update+ and +delete+ on instances.
|
416
|
+
# Once +all_queries+ is set to true it cannot be set to false in a
|
417
|
+
# nested block.
|
418
|
+
#
|
277
419
|
# Please check unscoped if you want to remove all previous scopes (including
|
278
420
|
# the default_scope) during the execution of a block.
|
279
|
-
def scoping
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
421
|
+
def scoping(all_queries: nil, &block)
|
422
|
+
registry = klass.scope_registry
|
423
|
+
if global_scope?(registry) && all_queries == false
|
424
|
+
raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
|
425
|
+
elsif already_in_scope?(registry)
|
426
|
+
yield
|
427
|
+
else
|
428
|
+
_scoping(self, registry, all_queries, &block)
|
429
|
+
end
|
284
430
|
end
|
285
431
|
|
286
432
|
def _exec_scope(*args, &block) # :nodoc:
|
287
433
|
@delegate_to_klass = true
|
288
|
-
|
434
|
+
registry = klass.scope_registry
|
435
|
+
_scoping(nil, registry) { instance_exec(*args, &block) || self }
|
289
436
|
ensure
|
290
437
|
@delegate_to_klass = false
|
291
438
|
end
|
@@ -293,7 +440,9 @@ module ActiveRecord
|
|
293
440
|
# Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
|
294
441
|
# statement and sends it straight to the database. It does not instantiate the involved models and it does not
|
295
442
|
# trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
|
296
|
-
# Active Record's normal type casting and serialization.
|
443
|
+
# Active Record's normal type casting and serialization. Returns the number of rows affected.
|
444
|
+
#
|
445
|
+
# Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns.
|
297
446
|
#
|
298
447
|
# ==== Parameters
|
299
448
|
#
|
@@ -315,26 +464,25 @@ module ActiveRecord
|
|
315
464
|
def update_all(updates)
|
316
465
|
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
|
317
466
|
|
318
|
-
if
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
stmt.table(table)
|
327
|
-
|
328
|
-
if has_join_values? || offset_value
|
329
|
-
@klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
|
467
|
+
if updates.is_a?(Hash)
|
468
|
+
if klass.locking_enabled? &&
|
469
|
+
!updates.key?(klass.locking_column) &&
|
470
|
+
!updates.key?(klass.locking_column.to_sym)
|
471
|
+
attr = table[klass.locking_column]
|
472
|
+
updates[attr.name] = _increment_attribute(attr)
|
473
|
+
end
|
474
|
+
values = _substitute_values(updates)
|
330
475
|
else
|
331
|
-
|
332
|
-
stmt.take(arel.limit)
|
333
|
-
stmt.order(*arel.orders)
|
334
|
-
stmt.wheres = arel.constraints
|
476
|
+
values = Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
|
335
477
|
end
|
336
478
|
|
337
|
-
|
479
|
+
arel = eager_loading? ? apply_join_dependency.arel : build_arel
|
480
|
+
arel.source.left = table
|
481
|
+
|
482
|
+
group_values_arel_columns = arel_columns(group_values.uniq)
|
483
|
+
having_clause_ast = having_clause.ast unless having_clause.empty?
|
484
|
+
stmt = arel.compile_update(values, table[primary_key], having_clause_ast, group_values_arel_columns)
|
485
|
+
klass.connection.update(stmt, "#{klass} Update All").tap { reset }
|
338
486
|
end
|
339
487
|
|
340
488
|
def update(id = :all, attributes) # :nodoc:
|
@@ -345,6 +493,73 @@ module ActiveRecord
|
|
345
493
|
end
|
346
494
|
end
|
347
495
|
|
496
|
+
def update!(id = :all, attributes) # :nodoc:
|
497
|
+
if id == :all
|
498
|
+
each { |record| record.update!(attributes) }
|
499
|
+
else
|
500
|
+
klass.update!(id, attributes)
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
# Updates the counters of the records in the current relation.
|
505
|
+
#
|
506
|
+
# ==== Parameters
|
507
|
+
#
|
508
|
+
# * +counter+ - A Hash containing the names of the fields to update as keys and the amount to update as values.
|
509
|
+
# * <tt>:touch</tt> option - Touch the timestamp columns when updating.
|
510
|
+
# * If attributes names are passed, they are updated along with update_at/on attributes.
|
511
|
+
#
|
512
|
+
# ==== Examples
|
513
|
+
#
|
514
|
+
# # For Posts by a given author increment the comment_count by 1.
|
515
|
+
# Post.where(author_id: author.id).update_counters(comment_count: 1)
|
516
|
+
def update_counters(counters)
|
517
|
+
touch = counters.delete(:touch)
|
518
|
+
|
519
|
+
updates = {}
|
520
|
+
counters.each do |counter_name, value|
|
521
|
+
attr = table[counter_name]
|
522
|
+
updates[attr.name] = _increment_attribute(attr, value)
|
523
|
+
end
|
524
|
+
|
525
|
+
if touch
|
526
|
+
names = touch if touch != true
|
527
|
+
names = Array.wrap(names)
|
528
|
+
options = names.extract_options!
|
529
|
+
touch_updates = klass.touch_attributes_with_time(*names, **options)
|
530
|
+
updates.merge!(touch_updates) unless touch_updates.empty?
|
531
|
+
end
|
532
|
+
|
533
|
+
update_all updates
|
534
|
+
end
|
535
|
+
|
536
|
+
# Touches all records in the current relation, setting the +updated_at+/+updated_on+ attributes to the current time or the time specified.
|
537
|
+
# It does not instantiate the involved models, and it does not trigger Active Record callbacks or validations.
|
538
|
+
# This method can be passed attribute names and an optional time argument.
|
539
|
+
# If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
|
540
|
+
# If no time argument is passed, the current time is used as default.
|
541
|
+
#
|
542
|
+
# === Examples
|
543
|
+
#
|
544
|
+
# # Touch all records
|
545
|
+
# Person.all.touch_all
|
546
|
+
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'"
|
547
|
+
#
|
548
|
+
# # Touch multiple records with a custom attribute
|
549
|
+
# Person.all.touch_all(:created_at)
|
550
|
+
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'"
|
551
|
+
#
|
552
|
+
# # Touch multiple records with a specified time
|
553
|
+
# Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
|
554
|
+
# # => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'"
|
555
|
+
#
|
556
|
+
# # Touch records with scope
|
557
|
+
# Person.where(name: 'David').touch_all
|
558
|
+
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
|
559
|
+
def touch_all(*names, time: nil)
|
560
|
+
update_all klass.touch_attributes_with_time(*names, time: time)
|
561
|
+
end
|
562
|
+
|
348
563
|
# Destroys the records by instantiating each
|
349
564
|
# record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
|
350
565
|
# Each object's callbacks are executed (including <tt>:dependent</tt> association options).
|
@@ -385,31 +600,88 @@ module ActiveRecord
|
|
385
600
|
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
|
386
601
|
def delete_all
|
387
602
|
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
|
388
|
-
value =
|
389
|
-
|
603
|
+
value = @values[method]
|
604
|
+
method == :distinct ? value : value&.any?
|
390
605
|
end
|
391
606
|
if invalid_methods.any?
|
392
607
|
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
|
393
608
|
end
|
394
609
|
|
395
|
-
|
396
|
-
|
397
|
-
return relation.delete_all
|
398
|
-
end
|
610
|
+
arel = eager_loading? ? apply_join_dependency.arel : build_arel
|
611
|
+
arel.source.left = table
|
399
612
|
|
400
|
-
|
401
|
-
|
613
|
+
group_values_arel_columns = arel_columns(group_values.uniq)
|
614
|
+
having_clause_ast = having_clause.ast unless having_clause.empty?
|
615
|
+
stmt = arel.compile_delete(table[primary_key], having_clause_ast, group_values_arel_columns)
|
402
616
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
617
|
+
klass.connection.delete(stmt, "#{klass} Delete All").tap { reset }
|
618
|
+
end
|
619
|
+
|
620
|
+
# Finds and destroys all records matching the specified conditions.
|
621
|
+
# This is short-hand for <tt>relation.where(condition).destroy_all</tt>.
|
622
|
+
# Returns the collection of objects that were destroyed.
|
623
|
+
#
|
624
|
+
# If no record is found, returns empty array.
|
625
|
+
#
|
626
|
+
# Person.destroy_by(id: 13)
|
627
|
+
# Person.destroy_by(name: 'Spartacus', rating: 4)
|
628
|
+
# Person.destroy_by("published_at < ?", 2.weeks.ago)
|
629
|
+
def destroy_by(*args)
|
630
|
+
where(*args).destroy_all
|
631
|
+
end
|
632
|
+
|
633
|
+
# Finds and deletes all records matching the specified conditions.
|
634
|
+
# This is short-hand for <tt>relation.where(condition).delete_all</tt>.
|
635
|
+
# Returns the number of rows affected.
|
636
|
+
#
|
637
|
+
# If no record is found, returns <tt>0</tt> as zero rows were affected.
|
638
|
+
#
|
639
|
+
# Person.delete_by(id: 13)
|
640
|
+
# Person.delete_by(name: 'Spartacus', rating: 4)
|
641
|
+
# Person.delete_by("published_at < ?", 2.weeks.ago)
|
642
|
+
def delete_by(*args)
|
643
|
+
where(*args).delete_all
|
644
|
+
end
|
645
|
+
|
646
|
+
# Schedule the query to be performed from a background thread pool.
|
647
|
+
#
|
648
|
+
# Post.where(published: true).load_async # => #<ActiveRecord::Relation>
|
649
|
+
#
|
650
|
+
# When the +Relation+ is iterated, if the background query wasn't executed yet,
|
651
|
+
# it will be performed by the foreground thread.
|
652
|
+
#
|
653
|
+
# Note that {config.active_record.async_query_executor}[https://guides.rubyonrails.org/configuring.html#config-active-record-async-query-executor] must be configured
|
654
|
+
# for queries to actually be executed concurrently. Otherwise it defaults to
|
655
|
+
# executing them in the foreground.
|
656
|
+
#
|
657
|
+
# +load_async+ will also fallback to executing in the foreground in the test environment when transactional
|
658
|
+
# fixtures are enabled.
|
659
|
+
#
|
660
|
+
# If the query was actually executed in the background, the Active Record logs will show
|
661
|
+
# it by prefixing the log line with <tt>ASYNC</tt>:
|
662
|
+
#
|
663
|
+
# ASYNC Post Load (0.0ms) (db time 2ms) SELECT "posts".* FROM "posts" LIMIT 100
|
664
|
+
def load_async
|
665
|
+
return load if !connection.async_enabled?
|
666
|
+
|
667
|
+
unless loaded?
|
668
|
+
result = exec_main_query(async: connection.current_transaction.closed?)
|
669
|
+
|
670
|
+
if result.is_a?(Array)
|
671
|
+
@records = result
|
672
|
+
else
|
673
|
+
@future_result = result
|
674
|
+
end
|
675
|
+
@loaded = true
|
407
676
|
end
|
408
677
|
|
409
|
-
|
678
|
+
self
|
679
|
+
end
|
410
680
|
|
411
|
-
|
412
|
-
|
681
|
+
# Returns <tt>true</tt> if the relation was scheduled on the background
|
682
|
+
# thread pool.
|
683
|
+
def scheduled?
|
684
|
+
!!@future_result
|
413
685
|
end
|
414
686
|
|
415
687
|
# Causes the records to be loaded from the database if they have not
|
@@ -419,7 +691,10 @@ module ActiveRecord
|
|
419
691
|
#
|
420
692
|
# Post.where(published: true).load # => #<ActiveRecord::Relation>
|
421
693
|
def load(&block)
|
422
|
-
|
694
|
+
if !loaded? || scheduled?
|
695
|
+
@records = exec_queries(&block)
|
696
|
+
@loaded = true
|
697
|
+
end
|
423
698
|
|
424
699
|
self
|
425
700
|
end
|
@@ -431,10 +706,13 @@ module ActiveRecord
|
|
431
706
|
end
|
432
707
|
|
433
708
|
def reset
|
709
|
+
@future_result&.cancel
|
710
|
+
@future_result = nil
|
434
711
|
@delegate_to_klass = false
|
435
712
|
@to_sql = @arel = @loaded = @should_eager_load = nil
|
436
|
-
@
|
437
|
-
@
|
713
|
+
@offsets = @take = nil
|
714
|
+
@cache_keys = nil
|
715
|
+
@records = nil
|
438
716
|
self
|
439
717
|
end
|
440
718
|
|
@@ -443,16 +721,14 @@ module ActiveRecord
|
|
443
721
|
# User.where(name: 'Oscar').to_sql
|
444
722
|
# # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
|
445
723
|
def to_sql
|
446
|
-
@to_sql ||=
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
relation.to_sql
|
451
|
-
end
|
452
|
-
else
|
453
|
-
conn = klass.connection
|
454
|
-
conn.unprepared_statement { conn.to_sql(arel) }
|
724
|
+
@to_sql ||= if eager_loading?
|
725
|
+
apply_join_dependency do |relation, join_dependency|
|
726
|
+
relation = join_dependency.apply_column_aliases(relation)
|
727
|
+
relation.to_sql
|
455
728
|
end
|
729
|
+
else
|
730
|
+
conn = klass.connection
|
731
|
+
conn.unprepared_statement { conn.to_sql(arel) }
|
456
732
|
end
|
457
733
|
end
|
458
734
|
|
@@ -465,7 +741,9 @@ module ActiveRecord
|
|
465
741
|
end
|
466
742
|
|
467
743
|
def scope_for_create
|
468
|
-
|
744
|
+
hash = where_clause.to_h(klass.table_name, equality_only: true)
|
745
|
+
create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
|
746
|
+
hash
|
469
747
|
end
|
470
748
|
|
471
749
|
# Returns true if relation needs eager loading.
|
@@ -508,8 +786,12 @@ module ActiveRecord
|
|
508
786
|
@values.dup
|
509
787
|
end
|
510
788
|
|
789
|
+
def values_for_queries # :nodoc:
|
790
|
+
@values.except(:extending, :skip_query_cache, :strict_loading)
|
791
|
+
end
|
792
|
+
|
511
793
|
def inspect
|
512
|
-
subject = loaded? ? records :
|
794
|
+
subject = loaded? ? records : annotate("loading for inspect")
|
513
795
|
entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)
|
514
796
|
|
515
797
|
entries[10] = "..." if entries.size == 11
|
@@ -526,104 +808,186 @@ module ActiveRecord
|
|
526
808
|
end
|
527
809
|
|
528
810
|
def alias_tracker(joins = [], aliases = nil) # :nodoc:
|
529
|
-
|
530
|
-
ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins)
|
811
|
+
ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins, aliases)
|
531
812
|
end
|
532
813
|
|
533
|
-
|
814
|
+
class StrictLoadingScope # :nodoc:
|
815
|
+
def self.empty_scope?
|
816
|
+
true
|
817
|
+
end
|
818
|
+
|
819
|
+
def self.strict_loading_value
|
820
|
+
true
|
821
|
+
end
|
822
|
+
end
|
534
823
|
|
824
|
+
def preload_associations(records) # :nodoc:
|
825
|
+
preload = preload_values
|
826
|
+
preload += includes_values unless eager_loading?
|
827
|
+
scope = strict_loading_value ? StrictLoadingScope : nil
|
828
|
+
preload.each do |associations|
|
829
|
+
ActiveRecord::Associations::Preloader.new(records: records, associations: associations, scope: scope).call
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
833
|
+
protected
|
535
834
|
def load_records(records)
|
536
835
|
@records = records.freeze
|
537
836
|
@loaded = true
|
538
837
|
end
|
539
838
|
|
839
|
+
def null_relation? # :nodoc:
|
840
|
+
is_a?(NullRelation)
|
841
|
+
end
|
842
|
+
|
540
843
|
private
|
844
|
+
def already_in_scope?(registry)
|
845
|
+
@delegate_to_klass && registry.current_scope(klass, true)
|
846
|
+
end
|
847
|
+
|
848
|
+
def global_scope?(registry)
|
849
|
+
registry.global_current_scope(klass, true)
|
850
|
+
end
|
851
|
+
|
852
|
+
def current_scope_restoring_block(&block)
|
853
|
+
current_scope = klass.current_scope(true)
|
854
|
+
-> record do
|
855
|
+
klass.current_scope = current_scope
|
856
|
+
yield record if block_given?
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
def _new(attributes, &block)
|
861
|
+
klass.new(attributes, &block)
|
862
|
+
end
|
863
|
+
|
864
|
+
def _create(attributes, &block)
|
865
|
+
klass.create(attributes, &block)
|
866
|
+
end
|
867
|
+
|
868
|
+
def _create!(attributes, &block)
|
869
|
+
klass.create!(attributes, &block)
|
870
|
+
end
|
871
|
+
|
872
|
+
def _scoping(scope, registry, all_queries = false)
|
873
|
+
previous = registry.current_scope(klass, true)
|
874
|
+
registry.set_current_scope(klass, scope)
|
875
|
+
|
876
|
+
if all_queries
|
877
|
+
previous_global = registry.global_current_scope(klass, true)
|
878
|
+
registry.set_global_current_scope(klass, scope)
|
879
|
+
end
|
880
|
+
yield
|
881
|
+
ensure
|
882
|
+
registry.set_current_scope(klass, previous)
|
883
|
+
if all_queries
|
884
|
+
registry.set_global_current_scope(klass, previous_global)
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
def _substitute_values(values)
|
889
|
+
values.map do |name, value|
|
890
|
+
attr = table[name]
|
891
|
+
unless Arel.arel_node?(value)
|
892
|
+
type = klass.type_for_attribute(attr.name)
|
893
|
+
value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
|
894
|
+
end
|
895
|
+
[attr, value]
|
896
|
+
end
|
897
|
+
end
|
541
898
|
|
542
|
-
def
|
543
|
-
|
899
|
+
def _increment_attribute(attribute, value = 1)
|
900
|
+
bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
|
901
|
+
expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
|
902
|
+
expr = value < 0 ? expr - bind : expr + bind
|
903
|
+
expr.expr
|
544
904
|
end
|
545
905
|
|
546
906
|
def exec_queries(&block)
|
547
907
|
skip_query_cache_if_necessary do
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
relation = join_dependency.apply_column_aliases(relation)
|
555
|
-
rows = connection.select_all(relation.arel, "SQL")
|
556
|
-
join_dependency.instantiate(rows, &block)
|
557
|
-
end.freeze
|
558
|
-
end
|
559
|
-
else
|
560
|
-
klass.find_by_sql(arel, &block).freeze
|
561
|
-
end
|
562
|
-
|
563
|
-
preload = preload_values
|
564
|
-
preload += includes_values unless eager_loading?
|
565
|
-
preloader = nil
|
566
|
-
preload.each do |associations|
|
567
|
-
preloader ||= build_preloader
|
568
|
-
preloader.preload @records, associations
|
908
|
+
rows = if scheduled?
|
909
|
+
future = @future_result
|
910
|
+
@future_result = nil
|
911
|
+
future.result
|
912
|
+
else
|
913
|
+
exec_main_query
|
569
914
|
end
|
570
915
|
|
571
|
-
|
916
|
+
records = instantiate_records(rows, &block)
|
917
|
+
preload_associations(records) unless skip_preloading_value
|
918
|
+
|
919
|
+
records.each(&:readonly!) if readonly_value
|
920
|
+
records.each(&:strict_loading!) if strict_loading_value
|
572
921
|
|
573
|
-
|
574
|
-
@records
|
922
|
+
records
|
575
923
|
end
|
576
924
|
end
|
577
925
|
|
578
|
-
def
|
579
|
-
|
580
|
-
|
581
|
-
|
926
|
+
def exec_main_query(async: false)
|
927
|
+
skip_query_cache_if_necessary do
|
928
|
+
if where_clause.contradiction?
|
929
|
+
[].freeze
|
930
|
+
elsif eager_loading?
|
931
|
+
apply_join_dependency do |relation, join_dependency|
|
932
|
+
if relation.null_relation?
|
933
|
+
[].freeze
|
934
|
+
else
|
935
|
+
relation = join_dependency.apply_column_aliases(relation)
|
936
|
+
@_join_dependency = join_dependency
|
937
|
+
connection.select_all(relation.arel, "SQL", async: async)
|
938
|
+
end
|
939
|
+
end
|
940
|
+
else
|
941
|
+
klass._query_by_sql(arel, async: async)
|
582
942
|
end
|
943
|
+
end
|
944
|
+
end
|
945
|
+
|
946
|
+
def instantiate_records(rows, &block)
|
947
|
+
return [].freeze if rows.empty?
|
948
|
+
if eager_loading?
|
949
|
+
records = @_join_dependency.instantiate(rows, strict_loading_value, &block).freeze
|
950
|
+
@_join_dependency = nil
|
951
|
+
records
|
583
952
|
else
|
584
|
-
|
953
|
+
klass._load_from_sql(rows, &block).freeze
|
585
954
|
end
|
586
955
|
end
|
587
956
|
|
588
|
-
def
|
589
|
-
|
957
|
+
def skip_query_cache_if_necessary(&block)
|
958
|
+
if skip_query_cache_value
|
959
|
+
uncached(&block)
|
960
|
+
else
|
961
|
+
yield
|
962
|
+
end
|
590
963
|
end
|
591
964
|
|
592
965
|
def references_eager_loaded_tables?
|
593
|
-
joined_tables =
|
966
|
+
joined_tables = build_joins([]).flat_map do |join|
|
594
967
|
if join.is_a?(Arel::Nodes::StringJoin)
|
595
968
|
tables_in_string(join.left)
|
596
969
|
else
|
597
|
-
|
970
|
+
join.left.name
|
598
971
|
end
|
599
972
|
end
|
600
973
|
|
601
|
-
joined_tables
|
974
|
+
joined_tables << table.name
|
602
975
|
|
603
976
|
# always convert table names to downcase as in Oracle quoted table names are in uppercase
|
604
|
-
joined_tables
|
977
|
+
joined_tables.map!(&:downcase)
|
605
978
|
|
606
|
-
(references_values - joined_tables).
|
979
|
+
!(references_values.map(&:to_s) - joined_tables).empty?
|
607
980
|
end
|
608
981
|
|
609
982
|
def tables_in_string(string)
|
610
983
|
return [] if string.blank?
|
611
984
|
# always convert table names to downcase as in Oracle quoted table names are in uppercase
|
612
985
|
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
|
613
|
-
string.scan(/
|
986
|
+
string.scan(/[a-zA-Z_][.\w]+(?=.?\.)/).map!(&:downcase) - ["raw_sql_"]
|
614
987
|
end
|
615
988
|
|
616
|
-
def
|
617
|
-
|
618
|
-
|
619
|
-
# NOTE: if there are same keys in both create_with and result, create_with should be used.
|
620
|
-
# This is to make sure nested attributes don't get passed to the klass.new,
|
621
|
-
# while keeping the precedence of the duplicate keys in create_with.
|
622
|
-
create_with_value.stringify_keys.each do |k, v|
|
623
|
-
result[k] = v if result.key?(k)
|
624
|
-
end
|
625
|
-
|
626
|
-
result
|
989
|
+
def limited_count
|
990
|
+
limit_value ? count : limit(2).count
|
627
991
|
end
|
628
992
|
end
|
629
993
|
end
|