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
@@ -3,20 +3,16 @@
|
|
3
3
|
require "active_record/relation/from_clause"
|
4
4
|
require "active_record/relation/query_attribute"
|
5
5
|
require "active_record/relation/where_clause"
|
6
|
-
require "active_record/relation/where_clause_factory"
|
7
6
|
require "active_model/forbidden_attributes_protection"
|
7
|
+
require "active_support/core_ext/array/wrap"
|
8
8
|
|
9
9
|
module ActiveRecord
|
10
10
|
module QueryMethods
|
11
|
-
extend ActiveSupport::Concern
|
12
|
-
|
13
11
|
include ActiveModel::ForbiddenAttributesProtection
|
14
12
|
|
15
13
|
# WhereChain objects act as placeholder for queries in which #where does not have any parameter.
|
16
14
|
# In this case, #where must be chained with #not to return a new relation.
|
17
15
|
class WhereChain
|
18
|
-
include ActiveModel::ForbiddenAttributesProtection
|
19
|
-
|
20
16
|
def initialize(scope)
|
21
17
|
@scope = scope
|
22
18
|
end
|
@@ -43,36 +39,104 @@ module ActiveRecord
|
|
43
39
|
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
44
40
|
#
|
45
41
|
# User.where.not(name: "Jon", role: "admin")
|
46
|
-
# # SELECT * FROM users WHERE name
|
42
|
+
# # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
|
47
43
|
def not(opts, *rest)
|
48
|
-
|
44
|
+
where_clause = @scope.send(:build_where_clause, opts, rest)
|
49
45
|
|
50
|
-
where_clause = @scope.send(:where_clause_factory).build(opts, rest)
|
51
|
-
|
52
|
-
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
|
53
46
|
@scope.where_clause += where_clause.invert
|
47
|
+
|
48
|
+
@scope
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a new relation with joins and where clause to identify
|
52
|
+
# associated relations.
|
53
|
+
#
|
54
|
+
# For example, posts that are associated to a related author:
|
55
|
+
#
|
56
|
+
# Post.where.associated(:author)
|
57
|
+
# # SELECT "posts".* FROM "posts"
|
58
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
59
|
+
# # WHERE "authors"."id" IS NOT NULL
|
60
|
+
#
|
61
|
+
# Additionally, multiple relations can be combined. This will return posts
|
62
|
+
# associated to both an author and any comments:
|
63
|
+
#
|
64
|
+
# Post.where.associated(:author, :comments)
|
65
|
+
# # SELECT "posts".* FROM "posts"
|
66
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
67
|
+
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
68
|
+
# # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
|
69
|
+
def associated(*associations)
|
70
|
+
associations.each do |association|
|
71
|
+
reflection = scope_association_reflection(association)
|
72
|
+
@scope.joins!(association)
|
73
|
+
self.not(reflection.table_name => { reflection.association_primary_key => nil })
|
74
|
+
end
|
75
|
+
|
76
|
+
@scope
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns a new relation with left outer joins and where clause to identify
|
80
|
+
# missing relations.
|
81
|
+
#
|
82
|
+
# For example, posts that are missing a related author:
|
83
|
+
#
|
84
|
+
# Post.where.missing(:author)
|
85
|
+
# # SELECT "posts".* FROM "posts"
|
86
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
87
|
+
# # WHERE "authors"."id" IS NULL
|
88
|
+
#
|
89
|
+
# Additionally, multiple relations can be combined. This will return posts
|
90
|
+
# that are missing both an author and any comments:
|
91
|
+
#
|
92
|
+
# Post.where.missing(:author, :comments)
|
93
|
+
# # SELECT "posts".* FROM "posts"
|
94
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
95
|
+
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
96
|
+
# # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
|
97
|
+
def missing(*associations)
|
98
|
+
associations.each do |association|
|
99
|
+
reflection = scope_association_reflection(association)
|
100
|
+
@scope.left_outer_joins!(association)
|
101
|
+
@scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
|
102
|
+
end
|
103
|
+
|
54
104
|
@scope
|
55
105
|
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def scope_association_reflection(association)
|
109
|
+
reflection = @scope.klass._reflect_on_association(association)
|
110
|
+
unless reflection
|
111
|
+
raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
|
112
|
+
end
|
113
|
+
reflection
|
114
|
+
end
|
56
115
|
end
|
57
116
|
|
58
117
|
FROZEN_EMPTY_ARRAY = [].freeze
|
59
118
|
FROZEN_EMPTY_HASH = {}.freeze
|
60
119
|
|
61
120
|
Relation::VALUE_METHODS.each do |name|
|
62
|
-
method_name =
|
121
|
+
method_name, default =
|
63
122
|
case name
|
64
|
-
when *Relation::MULTI_VALUE_METHODS
|
65
|
-
|
66
|
-
when *Relation::
|
123
|
+
when *Relation::MULTI_VALUE_METHODS
|
124
|
+
["#{name}_values", "FROZEN_EMPTY_ARRAY"]
|
125
|
+
when *Relation::SINGLE_VALUE_METHODS
|
126
|
+
["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
|
127
|
+
when *Relation::CLAUSE_METHODS
|
128
|
+
["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
|
67
129
|
end
|
68
|
-
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
69
|
-
def #{method_name} # def includes_values
|
70
|
-
get_value(#{name.inspect}) # get_value(:includes)
|
71
|
-
end # end
|
72
130
|
|
73
|
-
|
74
|
-
|
75
|
-
|
131
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
132
|
+
def #{method_name} # def includes_values
|
133
|
+
@values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
|
134
|
+
end # end
|
135
|
+
|
136
|
+
def #{method_name}=(value) # def includes_values=(value)
|
137
|
+
assert_mutability! # assert_mutability!
|
138
|
+
@values[:#{name}] = value # @values[:includes] = value
|
139
|
+
end # end
|
76
140
|
CODE
|
77
141
|
end
|
78
142
|
|
@@ -100,7 +164,7 @@ module ActiveRecord
|
|
100
164
|
#
|
101
165
|
# === conditions
|
102
166
|
#
|
103
|
-
# If you want to add conditions to your included models you'll have
|
167
|
+
# If you want to add string conditions to your included models, you'll have
|
104
168
|
# to explicitly reference them. For example:
|
105
169
|
#
|
106
170
|
# User.includes(:posts).where('posts.name = ?', 'example')
|
@@ -111,15 +175,18 @@ module ActiveRecord
|
|
111
175
|
#
|
112
176
|
# Note that #includes works with association names while #references needs
|
113
177
|
# the actual table name.
|
178
|
+
#
|
179
|
+
# If you pass the conditions via hash, you don't need to call #references
|
180
|
+
# explicitly, as #where references the tables for you. For example, this
|
181
|
+
# will work correctly:
|
182
|
+
#
|
183
|
+
# User.includes(:posts).where(posts: { name: 'example' })
|
114
184
|
def includes(*args)
|
115
|
-
check_if_method_has_arguments!(
|
185
|
+
check_if_method_has_arguments!(__callee__, args)
|
116
186
|
spawn.includes!(*args)
|
117
187
|
end
|
118
188
|
|
119
189
|
def includes!(*args) # :nodoc:
|
120
|
-
args.reject!(&:blank?)
|
121
|
-
args.flatten!
|
122
|
-
|
123
190
|
self.includes_values |= args
|
124
191
|
self
|
125
192
|
end
|
@@ -131,12 +198,12 @@ module ActiveRecord
|
|
131
198
|
# # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
|
132
199
|
# # "users"."id"
|
133
200
|
def eager_load(*args)
|
134
|
-
check_if_method_has_arguments!(
|
201
|
+
check_if_method_has_arguments!(__callee__, args)
|
135
202
|
spawn.eager_load!(*args)
|
136
203
|
end
|
137
204
|
|
138
205
|
def eager_load!(*args) # :nodoc:
|
139
|
-
self.eager_load_values
|
206
|
+
self.eager_load_values |= args
|
140
207
|
self
|
141
208
|
end
|
142
209
|
|
@@ -145,15 +212,28 @@ module ActiveRecord
|
|
145
212
|
# User.preload(:posts)
|
146
213
|
# # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
|
147
214
|
def preload(*args)
|
148
|
-
check_if_method_has_arguments!(
|
215
|
+
check_if_method_has_arguments!(__callee__, args)
|
149
216
|
spawn.preload!(*args)
|
150
217
|
end
|
151
218
|
|
152
219
|
def preload!(*args) # :nodoc:
|
153
|
-
self.preload_values
|
220
|
+
self.preload_values |= args
|
154
221
|
self
|
155
222
|
end
|
156
223
|
|
224
|
+
# Extracts a named +association+ from the relation. The named association is first preloaded,
|
225
|
+
# then the individual association records are collected from the relation. Like so:
|
226
|
+
#
|
227
|
+
# account.memberships.extract_associated(:user)
|
228
|
+
# # => Returns collection of User records
|
229
|
+
#
|
230
|
+
# This is short-hand for:
|
231
|
+
#
|
232
|
+
# account.memberships.preload(:user).collect(&:user)
|
233
|
+
def extract_associated(association)
|
234
|
+
preload(association).collect(&association)
|
235
|
+
end
|
236
|
+
|
157
237
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
158
238
|
# and should therefore be JOINed in any query rather than loaded separately.
|
159
239
|
# This method only works in conjunction with #includes.
|
@@ -165,14 +245,11 @@ module ActiveRecord
|
|
165
245
|
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
|
166
246
|
# # Query now knows the string references posts, so adds a JOIN
|
167
247
|
def references(*table_names)
|
168
|
-
check_if_method_has_arguments!(
|
248
|
+
check_if_method_has_arguments!(__callee__, table_names)
|
169
249
|
spawn.references!(*table_names)
|
170
250
|
end
|
171
251
|
|
172
252
|
def references!(*table_names) # :nodoc:
|
173
|
-
table_names.flatten!
|
174
|
-
table_names.map!(&:to_s)
|
175
|
-
|
176
253
|
self.references_values |= table_names
|
177
254
|
self
|
178
255
|
end
|
@@ -226,13 +303,33 @@ module ActiveRecord
|
|
226
303
|
return super()
|
227
304
|
end
|
228
305
|
|
229
|
-
|
306
|
+
check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
|
230
307
|
spawn._select!(*fields)
|
231
308
|
end
|
232
309
|
|
233
310
|
def _select!(*fields) # :nodoc:
|
234
|
-
fields
|
235
|
-
self
|
311
|
+
self.select_values |= fields
|
312
|
+
self
|
313
|
+
end
|
314
|
+
|
315
|
+
# Allows you to change a previously set select statement.
|
316
|
+
#
|
317
|
+
# Post.select(:title, :body)
|
318
|
+
# # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
|
319
|
+
#
|
320
|
+
# Post.select(:title, :body).reselect(:created_at)
|
321
|
+
# # SELECT `posts`.`created_at` FROM `posts`
|
322
|
+
#
|
323
|
+
# This is short-hand for <tt>unscope(:select).select(fields)</tt>.
|
324
|
+
# Note that we're unscoping the entire select statement.
|
325
|
+
def reselect(*args)
|
326
|
+
check_if_method_has_arguments!(__callee__, args)
|
327
|
+
spawn.reselect!(*args)
|
328
|
+
end
|
329
|
+
|
330
|
+
# Same as #reselect but operates on relation in-place instead of copying.
|
331
|
+
def reselect!(*args) # :nodoc:
|
332
|
+
self.select_values = args
|
236
333
|
self
|
237
334
|
end
|
238
335
|
|
@@ -257,28 +354,46 @@ module ActiveRecord
|
|
257
354
|
# User.select([:id, :first_name]).group(:id, :first_name).first(3)
|
258
355
|
# # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
|
259
356
|
def group(*args)
|
260
|
-
check_if_method_has_arguments!(
|
357
|
+
check_if_method_has_arguments!(__callee__, args)
|
261
358
|
spawn.group!(*args)
|
262
359
|
end
|
263
360
|
|
264
361
|
def group!(*args) # :nodoc:
|
265
|
-
args.flatten!
|
266
|
-
|
267
362
|
self.group_values += args
|
268
363
|
self
|
269
364
|
end
|
270
365
|
|
271
|
-
#
|
366
|
+
# Applies an <code>ORDER BY</code> clause to a query.
|
367
|
+
#
|
368
|
+
# #order accepts arguments in one of several formats.
|
369
|
+
#
|
370
|
+
# === symbols
|
371
|
+
#
|
372
|
+
# The symbol represents the name of the column you want to order the results by.
|
272
373
|
#
|
273
374
|
# User.order(:name)
|
274
375
|
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
|
275
376
|
#
|
377
|
+
# By default, the order is ascending. If you want descending order, you can
|
378
|
+
# map the column name symbol to +:desc+.
|
379
|
+
#
|
276
380
|
# User.order(email: :desc)
|
277
381
|
# # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
|
278
382
|
#
|
383
|
+
# Multiple columns can be passed this way, and they will be applied in the order specified.
|
384
|
+
#
|
279
385
|
# User.order(:name, email: :desc)
|
280
386
|
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
|
281
387
|
#
|
388
|
+
# === strings
|
389
|
+
#
|
390
|
+
# Strings are passed directly to the database, allowing you to specify
|
391
|
+
# simple SQL expressions.
|
392
|
+
#
|
393
|
+
# This could be a source of SQL injection, so only strings composed of plain
|
394
|
+
# column names and simple <code>function(column_name)</code> expressions
|
395
|
+
# with optional +ASC+/+DESC+ modifiers are allowed.
|
396
|
+
#
|
282
397
|
# User.order('name')
|
283
398
|
# # SELECT "users".* FROM "users" ORDER BY name
|
284
399
|
#
|
@@ -287,19 +402,56 @@ module ActiveRecord
|
|
287
402
|
#
|
288
403
|
# User.order('name DESC, email')
|
289
404
|
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
|
405
|
+
#
|
406
|
+
# === Arel
|
407
|
+
#
|
408
|
+
# If you need to pass in complicated expressions that you have verified
|
409
|
+
# are safe for the database, you can use Arel.
|
410
|
+
#
|
411
|
+
# User.order(Arel.sql('end_date - start_date'))
|
412
|
+
# # SELECT "users".* FROM "users" ORDER BY end_date - start_date
|
413
|
+
#
|
414
|
+
# Custom query syntax, like JSON columns for Postgres, is supported in this way.
|
415
|
+
#
|
416
|
+
# User.order(Arel.sql("payload->>'kind'"))
|
417
|
+
# # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
|
290
418
|
def order(*args)
|
291
|
-
check_if_method_has_arguments!(
|
419
|
+
check_if_method_has_arguments!(__callee__, args) do
|
420
|
+
sanitize_order_arguments(args)
|
421
|
+
end
|
292
422
|
spawn.order!(*args)
|
293
423
|
end
|
294
424
|
|
295
425
|
# Same as #order but operates on relation in-place instead of copying.
|
296
426
|
def order!(*args) # :nodoc:
|
297
|
-
preprocess_order_args(args)
|
298
|
-
|
299
|
-
self.order_values += args
|
427
|
+
preprocess_order_args(args) unless args.empty?
|
428
|
+
self.order_values |= args
|
300
429
|
self
|
301
430
|
end
|
302
431
|
|
432
|
+
# Allows to specify an order by a specific set of values. Depending on your
|
433
|
+
# adapter this will either use a CASE statement or a built-in function.
|
434
|
+
#
|
435
|
+
# User.in_order_of(:id, [1, 5, 3])
|
436
|
+
# # SELECT "users".* FROM "users"
|
437
|
+
# # ORDER BY FIELD("users"."id", 1, 5, 3)
|
438
|
+
# # WHERE "users"."id" IN (1, 5, 3)
|
439
|
+
#
|
440
|
+
def in_order_of(column, values)
|
441
|
+
klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
|
442
|
+
return spawn.none! if values.empty?
|
443
|
+
|
444
|
+
references = column_references([column])
|
445
|
+
self.references_values |= references unless references.empty?
|
446
|
+
|
447
|
+
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
448
|
+
arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
|
449
|
+
|
450
|
+
spawn
|
451
|
+
.order!(connection.field_ordered_value(arel_column, values))
|
452
|
+
.where!(arel_column.in(values))
|
453
|
+
end
|
454
|
+
|
303
455
|
# Replaces any existing order defined on the relation with the specified order.
|
304
456
|
#
|
305
457
|
# User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
|
@@ -310,22 +462,24 @@ module ActiveRecord
|
|
310
462
|
#
|
311
463
|
# generates a query with 'ORDER BY id ASC, name ASC'.
|
312
464
|
def reorder(*args)
|
313
|
-
check_if_method_has_arguments!(
|
465
|
+
check_if_method_has_arguments!(__callee__, args) do
|
466
|
+
sanitize_order_arguments(args)
|
467
|
+
end
|
314
468
|
spawn.reorder!(*args)
|
315
469
|
end
|
316
470
|
|
317
471
|
# Same as #reorder but operates on relation in-place instead of copying.
|
318
472
|
def reorder!(*args) # :nodoc:
|
319
473
|
preprocess_order_args(args)
|
320
|
-
|
474
|
+
args.uniq!
|
321
475
|
self.reordering_value = true
|
322
476
|
self.order_values = args
|
323
477
|
self
|
324
478
|
end
|
325
479
|
|
326
480
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
327
|
-
:limit, :offset, :joins, :left_outer_joins,
|
328
|
-
:includes, :from, :readonly, :having])
|
481
|
+
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
482
|
+
:includes, :from, :readonly, :having, :optimizer_hints])
|
329
483
|
|
330
484
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
331
485
|
# This is useful when passing around chains of relations and would like to
|
@@ -361,12 +515,11 @@ module ActiveRecord
|
|
361
515
|
# has_many :comments, -> { unscope(where: :trashed) }
|
362
516
|
#
|
363
517
|
def unscope(*args)
|
364
|
-
check_if_method_has_arguments!(
|
518
|
+
check_if_method_has_arguments!(__callee__, args)
|
365
519
|
spawn.unscope!(*args)
|
366
520
|
end
|
367
521
|
|
368
522
|
def unscope!(*args) # :nodoc:
|
369
|
-
args.flatten!
|
370
523
|
self.unscope_values += args
|
371
524
|
|
372
525
|
args.each do |scope|
|
@@ -376,14 +529,15 @@ module ActiveRecord
|
|
376
529
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
377
530
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
378
531
|
end
|
379
|
-
|
532
|
+
assert_mutability!
|
533
|
+
@values.delete(scope)
|
380
534
|
when Hash
|
381
535
|
scope.each do |key, target_value|
|
382
536
|
if key != :where
|
383
537
|
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
|
384
538
|
end
|
385
539
|
|
386
|
-
target_values = Array(target_value)
|
540
|
+
target_values = resolve_arel_attributes(Array.wrap(target_value))
|
387
541
|
self.where_clause = where_clause.except(*target_values)
|
388
542
|
end
|
389
543
|
else
|
@@ -394,7 +548,7 @@ module ActiveRecord
|
|
394
548
|
self
|
395
549
|
end
|
396
550
|
|
397
|
-
# Performs
|
551
|
+
# Performs JOINs on +args+. The given symbol(s) should match the name of
|
398
552
|
# the association(s).
|
399
553
|
#
|
400
554
|
# User.joins(:posts)
|
@@ -416,26 +570,23 @@ module ActiveRecord
|
|
416
570
|
# # SELECT "users".*
|
417
571
|
# # FROM "users"
|
418
572
|
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
419
|
-
# # INNER JOIN "comments" "
|
420
|
-
# # ON "comments_posts"."post_id" = "posts"."id"
|
573
|
+
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
421
574
|
#
|
422
575
|
# You can use strings in order to customize your joins:
|
423
576
|
#
|
424
577
|
# User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
|
425
578
|
# # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
|
426
579
|
def joins(*args)
|
427
|
-
check_if_method_has_arguments!(
|
580
|
+
check_if_method_has_arguments!(__callee__, args)
|
428
581
|
spawn.joins!(*args)
|
429
582
|
end
|
430
583
|
|
431
584
|
def joins!(*args) # :nodoc:
|
432
|
-
args
|
433
|
-
args.flatten!
|
434
|
-
self.joins_values += args
|
585
|
+
self.joins_values |= args
|
435
586
|
self
|
436
587
|
end
|
437
588
|
|
438
|
-
# Performs
|
589
|
+
# Performs LEFT OUTER JOINs on +args+:
|
439
590
|
#
|
440
591
|
# User.left_outer_joins(:posts)
|
441
592
|
# => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
@@ -447,9 +598,7 @@ module ActiveRecord
|
|
447
598
|
alias :left_joins :left_outer_joins
|
448
599
|
|
449
600
|
def left_outer_joins!(*args) # :nodoc:
|
450
|
-
args
|
451
|
-
args.flatten!
|
452
|
-
self.left_outer_joins_values += args
|
601
|
+
self.left_outer_joins_values |= args
|
453
602
|
self
|
454
603
|
end
|
455
604
|
|
@@ -519,13 +668,13 @@ module ActiveRecord
|
|
519
668
|
#
|
520
669
|
# Fields can be symbols or strings. Values can be single values, arrays, or ranges.
|
521
670
|
#
|
522
|
-
# User.where(
|
671
|
+
# User.where(name: "Joe", email: "joe@example.com")
|
523
672
|
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
|
524
673
|
#
|
525
|
-
# User.where(
|
674
|
+
# User.where(name: ["Alice", "Bob"])
|
526
675
|
# # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
|
527
676
|
#
|
528
|
-
# User.where(
|
677
|
+
# User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
|
529
678
|
# # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
|
530
679
|
#
|
531
680
|
# In the case of a belongs_to relationship, an association key can be used
|
@@ -555,8 +704,8 @@ module ActiveRecord
|
|
555
704
|
#
|
556
705
|
# For hash conditions, you can either use the table name in the key, or use a sub-hash.
|
557
706
|
#
|
558
|
-
# User.joins(:posts).where(
|
559
|
-
# User.joins(:posts).where(
|
707
|
+
# User.joins(:posts).where("posts.published" => true)
|
708
|
+
# User.joins(:posts).where(posts: { published: true })
|
560
709
|
#
|
561
710
|
# === no argument
|
562
711
|
#
|
@@ -572,20 +721,18 @@ module ActiveRecord
|
|
572
721
|
#
|
573
722
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
574
723
|
# the current relation.
|
575
|
-
def where(
|
576
|
-
if
|
724
|
+
def where(*args)
|
725
|
+
if args.empty?
|
577
726
|
WhereChain.new(spawn)
|
578
|
-
elsif
|
727
|
+
elsif args.length == 1 && args.first.blank?
|
579
728
|
self
|
580
729
|
else
|
581
|
-
spawn.where!(
|
730
|
+
spawn.where!(*args)
|
582
731
|
end
|
583
732
|
end
|
584
733
|
|
585
734
|
def where!(opts, *rest) # :nodoc:
|
586
|
-
|
587
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
588
|
-
self.where_clause += where_clause_factory.build(opts, rest)
|
735
|
+
self.where_clause += build_where_clause(opts, rest)
|
589
736
|
self
|
590
737
|
end
|
591
738
|
|
@@ -603,7 +750,97 @@ module ActiveRecord
|
|
603
750
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
604
751
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
605
752
|
def rewhere(conditions)
|
606
|
-
|
753
|
+
scope = spawn
|
754
|
+
where_clause = scope.build_where_clause(conditions)
|
755
|
+
|
756
|
+
scope.unscope!(where: where_clause.extract_attributes)
|
757
|
+
scope.where_clause += where_clause
|
758
|
+
scope
|
759
|
+
end
|
760
|
+
|
761
|
+
# Allows you to invert an entire where clause instead of manually applying conditions.
|
762
|
+
#
|
763
|
+
# class User
|
764
|
+
# scope :active, -> { where(accepted: true, locked: false) }
|
765
|
+
# end
|
766
|
+
#
|
767
|
+
# User.where(accepted: true)
|
768
|
+
# # WHERE `accepted` = 1
|
769
|
+
#
|
770
|
+
# User.where(accepted: true).invert_where
|
771
|
+
# # WHERE `accepted` != 1
|
772
|
+
#
|
773
|
+
# User.active
|
774
|
+
# # WHERE `accepted` = 1 AND `locked` = 0
|
775
|
+
#
|
776
|
+
# User.active.invert_where
|
777
|
+
# # WHERE NOT (`accepted` = 1 AND `locked` = 0)
|
778
|
+
#
|
779
|
+
# Be careful because this inverts all conditions before +invert_where+ call.
|
780
|
+
#
|
781
|
+
# class User
|
782
|
+
# scope :active, -> { where(accepted: true, locked: false) }
|
783
|
+
# scope :inactive, -> { active.invert_where } # Do not attempt it
|
784
|
+
# end
|
785
|
+
#
|
786
|
+
# # It also inverts `where(role: 'admin')` unexpectedly.
|
787
|
+
# User.where(role: 'admin').inactive
|
788
|
+
# # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
|
789
|
+
#
|
790
|
+
def invert_where
|
791
|
+
spawn.invert_where!
|
792
|
+
end
|
793
|
+
|
794
|
+
def invert_where! # :nodoc:
|
795
|
+
self.where_clause = where_clause.invert
|
796
|
+
self
|
797
|
+
end
|
798
|
+
|
799
|
+
# Checks whether the given relation is structurally compatible with this relation, to determine
|
800
|
+
# if it's possible to use the #and and #or methods without raising an error. Structurally
|
801
|
+
# compatible is defined as: they must be scoping the same model, and they must differ only by
|
802
|
+
# #where (if no #group has been defined) or #having (if a #group is present).
|
803
|
+
#
|
804
|
+
# Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
|
805
|
+
# # => true
|
806
|
+
#
|
807
|
+
# Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
|
808
|
+
# # => false
|
809
|
+
#
|
810
|
+
def structurally_compatible?(other)
|
811
|
+
structurally_incompatible_values_for(other).empty?
|
812
|
+
end
|
813
|
+
|
814
|
+
# Returns a new relation, which is the logical intersection of this relation and the one passed
|
815
|
+
# as an argument.
|
816
|
+
#
|
817
|
+
# The two relations must be structurally compatible: they must be scoping the same model, and
|
818
|
+
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
819
|
+
# present).
|
820
|
+
#
|
821
|
+
# Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
|
822
|
+
# # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
|
823
|
+
#
|
824
|
+
def and(other)
|
825
|
+
if other.is_a?(Relation)
|
826
|
+
spawn.and!(other)
|
827
|
+
else
|
828
|
+
raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
def and!(other) # :nodoc:
|
833
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
834
|
+
|
835
|
+
unless incompatible_values.empty?
|
836
|
+
raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
|
837
|
+
end
|
838
|
+
|
839
|
+
self.where_clause |= other.where_clause
|
840
|
+
self.having_clause |= other.having_clause
|
841
|
+
self.references_values |= other.references_values
|
842
|
+
|
843
|
+
self
|
607
844
|
end
|
608
845
|
|
609
846
|
# Returns a new relation, which is the logical union of this relation and the one passed as an
|
@@ -611,21 +848,21 @@ module ActiveRecord
|
|
611
848
|
#
|
612
849
|
# The two relations must be structurally compatible: they must be scoping the same model, and
|
613
850
|
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
614
|
-
# present).
|
851
|
+
# present).
|
615
852
|
#
|
616
853
|
# Post.where("id = 1").or(Post.where("author_id = 3"))
|
617
854
|
# # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
|
618
855
|
#
|
619
856
|
def or(other)
|
620
|
-
|
857
|
+
if other.is_a?(Relation)
|
858
|
+
spawn.or!(other)
|
859
|
+
else
|
621
860
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
622
861
|
end
|
623
|
-
|
624
|
-
spawn.or!(other)
|
625
862
|
end
|
626
863
|
|
627
864
|
def or!(other) # :nodoc:
|
628
|
-
incompatible_values =
|
865
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
629
866
|
|
630
867
|
unless incompatible_values.empty?
|
631
868
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
@@ -633,7 +870,7 @@ module ActiveRecord
|
|
633
870
|
|
634
871
|
self.where_clause = self.where_clause.or(other.where_clause)
|
635
872
|
self.having_clause = having_clause.or(other.having_clause)
|
636
|
-
self.references_values
|
873
|
+
self.references_values |= other.references_values
|
637
874
|
|
638
875
|
self
|
639
876
|
end
|
@@ -647,10 +884,7 @@ module ActiveRecord
|
|
647
884
|
end
|
648
885
|
|
649
886
|
def having!(opts, *rest) # :nodoc:
|
650
|
-
|
651
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
652
|
-
|
653
|
-
self.having_clause += having_clause_factory.build(opts, rest)
|
887
|
+
self.having_clause += build_having_clause(opts, rest)
|
654
888
|
self
|
655
889
|
end
|
656
890
|
|
@@ -752,6 +986,21 @@ module ActiveRecord
|
|
752
986
|
self
|
753
987
|
end
|
754
988
|
|
989
|
+
# Sets the returned relation to strict_loading mode. This will raise an error
|
990
|
+
# if the record tries to lazily load an association.
|
991
|
+
#
|
992
|
+
# user = User.strict_loading.first
|
993
|
+
# user.comments.to_a
|
994
|
+
# => ActiveRecord::StrictLoadingViolationError
|
995
|
+
def strict_loading(value = true)
|
996
|
+
spawn.strict_loading!(value)
|
997
|
+
end
|
998
|
+
|
999
|
+
def strict_loading!(value = true) # :nodoc:
|
1000
|
+
self.strict_loading_value = value
|
1001
|
+
self
|
1002
|
+
end
|
1003
|
+
|
755
1004
|
# Sets attributes to be used when creating new records from a
|
756
1005
|
# relation object.
|
757
1006
|
#
|
@@ -780,7 +1029,7 @@ module ActiveRecord
|
|
780
1029
|
self
|
781
1030
|
end
|
782
1031
|
|
783
|
-
# Specifies table from which the records will be fetched. For example:
|
1032
|
+
# Specifies the table from which the records will be fetched. For example:
|
784
1033
|
#
|
785
1034
|
# Topic.select('title').from('posts')
|
786
1035
|
# # SELECT title FROM posts
|
@@ -790,9 +1039,26 @@ module ActiveRecord
|
|
790
1039
|
# Topic.select('title').from(Topic.approved)
|
791
1040
|
# # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
|
792
1041
|
#
|
1042
|
+
# Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
|
1043
|
+
#
|
793
1044
|
# Topic.select('a.title').from(Topic.approved, :a)
|
794
1045
|
# # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
|
795
1046
|
#
|
1047
|
+
# It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
|
1048
|
+
#
|
1049
|
+
# Topic.select('title').from(Topic.approved).from(Topic.inactive)
|
1050
|
+
# # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
|
1051
|
+
#
|
1052
|
+
# For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
|
1053
|
+
#
|
1054
|
+
# color = "red"
|
1055
|
+
# Color
|
1056
|
+
# .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
|
1057
|
+
# .where("colorvalue->>'color' = ?", color)
|
1058
|
+
# .select("c.*").to_a
|
1059
|
+
# # SELECT c.*
|
1060
|
+
# # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
|
1061
|
+
# # WHERE (colorvalue->>'color' = 'red')
|
796
1062
|
def from(value, subquery_name = nil)
|
797
1063
|
spawn.from!(value, subquery_name)
|
798
1064
|
end
|
@@ -876,6 +1142,27 @@ module ActiveRecord
|
|
876
1142
|
self
|
877
1143
|
end
|
878
1144
|
|
1145
|
+
# Specify optimizer hints to be used in the SELECT statement.
|
1146
|
+
#
|
1147
|
+
# Example (for MySQL):
|
1148
|
+
#
|
1149
|
+
# Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
|
1150
|
+
# # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
|
1151
|
+
#
|
1152
|
+
# Example (for PostgreSQL with pg_hint_plan):
|
1153
|
+
#
|
1154
|
+
# Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
|
1155
|
+
# # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
|
1156
|
+
def optimizer_hints(*args)
|
1157
|
+
check_if_method_has_arguments!(__callee__, args)
|
1158
|
+
spawn.optimizer_hints!(*args)
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
def optimizer_hints!(*args) # :nodoc:
|
1162
|
+
self.optimizer_hints_values |= args
|
1163
|
+
self
|
1164
|
+
end
|
1165
|
+
|
879
1166
|
# Reverse the existing order clause on the relation.
|
880
1167
|
#
|
881
1168
|
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
|
@@ -884,8 +1171,7 @@ module ActiveRecord
|
|
884
1171
|
end
|
885
1172
|
|
886
1173
|
def reverse_order! # :nodoc:
|
887
|
-
orders = order_values.
|
888
|
-
orders.reject!(&:blank?)
|
1174
|
+
orders = order_values.compact_blank
|
889
1175
|
self.order_values = reverse_sql_order(orders)
|
890
1176
|
self
|
891
1177
|
end
|
@@ -895,68 +1181,189 @@ module ActiveRecord
|
|
895
1181
|
self
|
896
1182
|
end
|
897
1183
|
|
1184
|
+
def skip_preloading! # :nodoc:
|
1185
|
+
self.skip_preloading_value = true
|
1186
|
+
self
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
# Adds an SQL comment to queries generated from this relation. For example:
|
1190
|
+
#
|
1191
|
+
# User.annotate("selecting user names").select(:name)
|
1192
|
+
# # SELECT "users"."name" FROM "users" /* selecting user names */
|
1193
|
+
#
|
1194
|
+
# User.annotate("selecting", "user", "names").select(:name)
|
1195
|
+
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
1196
|
+
#
|
1197
|
+
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
1198
|
+
def annotate(*args)
|
1199
|
+
check_if_method_has_arguments!(__callee__, args)
|
1200
|
+
spawn.annotate!(*args)
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
# Like #annotate, but modifies relation in place.
|
1204
|
+
def annotate!(*args) # :nodoc:
|
1205
|
+
self.annotate_values += args
|
1206
|
+
self
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
# Deduplicate multiple values.
|
1210
|
+
def uniq!(name)
|
1211
|
+
if values = @values[name]
|
1212
|
+
values.uniq! if values.is_a?(Array) && !values.empty?
|
1213
|
+
end
|
1214
|
+
self
|
1215
|
+
end
|
1216
|
+
|
1217
|
+
# Excludes the specified record (or collection of records) from the resulting
|
1218
|
+
# relation. For example:
|
1219
|
+
#
|
1220
|
+
# Post.excluding(post)
|
1221
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
|
1222
|
+
#
|
1223
|
+
# Post.excluding(post_one, post_two)
|
1224
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
|
1225
|
+
#
|
1226
|
+
# This can also be called on associations. As with the above example, either
|
1227
|
+
# a single record of collection thereof may be specified:
|
1228
|
+
#
|
1229
|
+
# post = Post.find(1)
|
1230
|
+
# comment = Comment.find(2)
|
1231
|
+
# post.comments.excluding(comment)
|
1232
|
+
# # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
|
1233
|
+
#
|
1234
|
+
# This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
|
1235
|
+
#
|
1236
|
+
# An <tt>ArgumentError</tt> will be raised if either no records are
|
1237
|
+
# specified, or if any of the records in the collection (if a collection
|
1238
|
+
# is passed in) are not instances of the same model that the relation is
|
1239
|
+
# scoping.
|
1240
|
+
def excluding(*records)
|
1241
|
+
records.flatten!(1)
|
1242
|
+
records.compact!
|
1243
|
+
|
1244
|
+
unless records.all?(klass)
|
1245
|
+
raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
spawn.excluding!(records)
|
1249
|
+
end
|
1250
|
+
alias :without :excluding
|
1251
|
+
|
1252
|
+
def excluding!(records) # :nodoc:
|
1253
|
+
predicates = [ predicate_builder[primary_key, records].invert ]
|
1254
|
+
self.where_clause += Relation::WhereClause.new(predicates)
|
1255
|
+
self
|
1256
|
+
end
|
1257
|
+
|
898
1258
|
# Returns the Arel object associated with the relation.
|
899
1259
|
def arel(aliases = nil) # :nodoc:
|
900
1260
|
@arel ||= build_arel(aliases)
|
901
1261
|
end
|
902
1262
|
|
903
|
-
|
904
|
-
|
905
|
-
|
1263
|
+
def construct_join_dependency(associations, join_type) # :nodoc:
|
1264
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1265
|
+
klass, table, associations, join_type
|
1266
|
+
)
|
906
1267
|
end
|
907
1268
|
|
908
1269
|
protected
|
1270
|
+
def build_subquery(subquery_alias, select_value) # :nodoc:
|
1271
|
+
subquery = except(:optimizer_hints).arel.as(subquery_alias)
|
909
1272
|
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
@values[name] = value
|
1273
|
+
Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
|
1274
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1275
|
+
end
|
914
1276
|
end
|
915
1277
|
|
1278
|
+
def build_where_clause(opts, rest = []) # :nodoc:
|
1279
|
+
opts = sanitize_forbidden_attributes(opts)
|
1280
|
+
|
1281
|
+
case opts
|
1282
|
+
when String, Array
|
1283
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1284
|
+
when Hash
|
1285
|
+
opts = opts.transform_keys do |key|
|
1286
|
+
key = key.to_s
|
1287
|
+
klass.attribute_aliases[key] || key
|
1288
|
+
end
|
1289
|
+
references = PredicateBuilder.references(opts)
|
1290
|
+
self.references_values |= references unless references.empty?
|
1291
|
+
|
1292
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
1293
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
1294
|
+
end
|
1295
|
+
when Arel::Nodes::Node
|
1296
|
+
parts = [opts]
|
1297
|
+
else
|
1298
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
Relation::WhereClause.new(parts)
|
1302
|
+
end
|
1303
|
+
alias :build_having_clause :build_where_clause
|
1304
|
+
|
916
1305
|
private
|
1306
|
+
def lookup_table_klass_from_join_dependencies(table_name)
|
1307
|
+
each_join_dependencies do |join|
|
1308
|
+
return join.base_klass if table_name == join.table_name
|
1309
|
+
end
|
1310
|
+
nil
|
1311
|
+
end
|
1312
|
+
|
1313
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
|
1314
|
+
join_dependencies.each do |join_dependency|
|
1315
|
+
join_dependency.each(&block)
|
1316
|
+
end
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
def build_join_dependencies
|
1320
|
+
associations = joins_values | left_outer_joins_values
|
1321
|
+
associations |= eager_load_values unless eager_load_values.empty?
|
1322
|
+
associations |= includes_values unless includes_values.empty?
|
1323
|
+
|
1324
|
+
join_dependencies = []
|
1325
|
+
join_dependencies.unshift construct_join_dependency(
|
1326
|
+
select_association_list(associations, join_dependencies), nil
|
1327
|
+
)
|
1328
|
+
end
|
917
1329
|
|
918
1330
|
def assert_mutability!
|
919
1331
|
raise ImmutableRelation if @loaded
|
920
1332
|
raise ImmutableRelation if defined?(@arel) && @arel
|
921
1333
|
end
|
922
1334
|
|
923
|
-
def build_arel(aliases)
|
1335
|
+
def build_arel(aliases = nil)
|
924
1336
|
arel = Arel::SelectManager.new(table)
|
925
1337
|
|
926
|
-
|
927
|
-
build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
|
1338
|
+
build_joins(arel.join_sources, aliases)
|
928
1339
|
|
929
1340
|
arel.where(where_clause.ast) unless where_clause.empty?
|
930
1341
|
arel.having(having_clause.ast) unless having_clause.empty?
|
931
|
-
if limit_value
|
932
|
-
|
933
|
-
|
934
|
-
connection.sanitize_limit(limit_value),
|
935
|
-
Type.default_value,
|
936
|
-
)
|
937
|
-
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
|
938
|
-
end
|
939
|
-
if offset_value
|
940
|
-
offset_attribute = ActiveModel::Attribute.with_cast_value(
|
941
|
-
"OFFSET".freeze,
|
942
|
-
offset_value.to_i,
|
943
|
-
Type.default_value,
|
944
|
-
)
|
945
|
-
arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
|
946
|
-
end
|
947
|
-
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
|
1342
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
1343
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
1344
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
948
1345
|
|
949
1346
|
build_order(arel)
|
950
|
-
|
951
1347
|
build_select(arel)
|
952
1348
|
|
1349
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
953
1350
|
arel.distinct(distinct_value)
|
954
1351
|
arel.from(build_from) unless from_clause.empty?
|
955
1352
|
arel.lock(lock_value) if lock_value
|
956
1353
|
|
1354
|
+
unless annotate_values.empty?
|
1355
|
+
annotates = annotate_values
|
1356
|
+
annotates = annotates.uniq if annotates.size > 1
|
1357
|
+
arel.comment(*annotates)
|
1358
|
+
end
|
1359
|
+
|
957
1360
|
arel
|
958
1361
|
end
|
959
1362
|
|
1363
|
+
def build_cast_value(name, value)
|
1364
|
+
ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1365
|
+
end
|
1366
|
+
|
960
1367
|
def build_from
|
961
1368
|
opts = from_clause.value
|
962
1369
|
name = from_clause.name
|
@@ -972,75 +1379,101 @@ module ActiveRecord
|
|
972
1379
|
end
|
973
1380
|
end
|
974
1381
|
|
975
|
-
def
|
976
|
-
|
977
|
-
|
1382
|
+
def select_association_list(associations, stashed_joins = nil)
|
1383
|
+
result = []
|
1384
|
+
associations.each do |association|
|
1385
|
+
case association
|
978
1386
|
when Hash, Symbol, Array
|
979
|
-
|
1387
|
+
result << association
|
980
1388
|
when ActiveRecord::Associations::JoinDependency
|
981
|
-
|
1389
|
+
stashed_joins&.<< association
|
982
1390
|
else
|
983
|
-
|
1391
|
+
yield association if block_given?
|
984
1392
|
end
|
985
1393
|
end
|
1394
|
+
result
|
1395
|
+
end
|
986
1396
|
|
987
|
-
|
1397
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
988
1398
|
end
|
989
1399
|
|
990
|
-
def
|
991
|
-
buckets =
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
:
|
1400
|
+
def build_join_buckets
|
1401
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
1402
|
+
|
1403
|
+
unless left_outer_joins_values.empty?
|
1404
|
+
stashed_left_joins = []
|
1405
|
+
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
|
1406
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1407
|
+
end
|
1408
|
+
|
1409
|
+
if joins_values.empty?
|
1410
|
+
buckets[:association_join] = left_joins
|
1411
|
+
buckets[:stashed_join] = stashed_left_joins
|
1412
|
+
return buckets, Arel::Nodes::OuterJoin
|
1413
|
+
else
|
1414
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1415
|
+
end
|
1416
|
+
end
|
1417
|
+
|
1418
|
+
joins = joins_values.dup
|
1419
|
+
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1420
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1421
|
+
end
|
1422
|
+
|
1423
|
+
joins.each_with_index do |join, i|
|
1424
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
1425
|
+
end
|
1426
|
+
|
1427
|
+
while joins.first.is_a?(Arel::Nodes::Join)
|
1428
|
+
join_node = joins.shift
|
1429
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
1430
|
+
buckets[:join_node] << join_node
|
1431
|
+
else
|
1432
|
+
buckets[:leading_join] << join_node
|
1433
|
+
end
|
1434
|
+
end
|
1435
|
+
|
1436
|
+
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
|
1437
|
+
if join.is_a?(Arel::Nodes::Join)
|
1438
|
+
buckets[:join_node] << join
|
1001
1439
|
else
|
1002
1440
|
raise "unknown class: %s" % join.class.name
|
1003
1441
|
end
|
1004
1442
|
end
|
1005
1443
|
|
1006
|
-
|
1444
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1445
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
1446
|
+
|
1447
|
+
return buckets, Arel::Nodes::InnerJoin
|
1007
1448
|
end
|
1008
1449
|
|
1009
|
-
def
|
1010
|
-
|
1450
|
+
def build_joins(join_sources, aliases = nil)
|
1451
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
1452
|
+
|
1453
|
+
buckets, join_type = build_join_buckets
|
1011
1454
|
|
1012
1455
|
association_joins = buckets[:association_join]
|
1013
1456
|
stashed_joins = buckets[:stashed_join]
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
join_list = join_nodes + convert_join_strings_to_ast(string_joins)
|
1018
|
-
alias_tracker = alias_tracker(join_list, aliases)
|
1019
|
-
|
1020
|
-
join_dependency = ActiveRecord::Associations::JoinDependency.new(
|
1021
|
-
klass, table, association_joins
|
1022
|
-
)
|
1457
|
+
leading_joins = buckets[:leading_join]
|
1458
|
+
join_nodes = buckets[:join_node]
|
1023
1459
|
|
1024
|
-
|
1025
|
-
joins.each { |join| manager.from(join) }
|
1460
|
+
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1026
1461
|
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1462
|
+
unless association_joins.empty? && stashed_joins.empty?
|
1463
|
+
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1464
|
+
join_dependency = construct_join_dependency(association_joins, join_type)
|
1465
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1466
|
+
end
|
1031
1467
|
|
1032
|
-
|
1033
|
-
|
1034
|
-
.flatten
|
1035
|
-
.reject(&:blank?)
|
1036
|
-
.map { |join| table.create_string_join(Arel.sql(join)) }
|
1468
|
+
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1469
|
+
join_sources
|
1037
1470
|
end
|
1038
1471
|
|
1039
1472
|
def build_select(arel)
|
1040
1473
|
if select_values.any?
|
1041
|
-
arel.project(*arel_columns(select_values
|
1042
|
-
elsif klass.ignored_columns.any?
|
1043
|
-
arel.project(*klass.column_names.map { |field|
|
1474
|
+
arel.project(*arel_columns(select_values))
|
1475
|
+
elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
|
1476
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
1044
1477
|
else
|
1045
1478
|
arel.project(table[Arel.star])
|
1046
1479
|
end
|
@@ -1064,23 +1497,30 @@ module ActiveRecord
|
|
1064
1497
|
end
|
1065
1498
|
|
1066
1499
|
def arel_column(field)
|
1067
|
-
field = klass.
|
1500
|
+
field = klass.attribute_aliases[field] || field
|
1068
1501
|
from = from_clause.name || from_clause.value
|
1069
1502
|
|
1070
1503
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1071
|
-
|
1504
|
+
table[field]
|
1505
|
+
elsif field.match?(/\A\w+\.\w+\z/)
|
1506
|
+
table, column = field.split(".")
|
1507
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
1508
|
+
lookup_table_klass_from_join_dependencies(table)
|
1509
|
+
end
|
1072
1510
|
else
|
1073
1511
|
yield field
|
1074
1512
|
end
|
1075
1513
|
end
|
1076
1514
|
|
1077
1515
|
def table_name_matches?(from)
|
1078
|
-
|
1516
|
+
table_name = Regexp.escape(table.name)
|
1517
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
1518
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1079
1519
|
end
|
1080
1520
|
|
1081
1521
|
def reverse_sql_order(order_query)
|
1082
1522
|
if order_query.empty?
|
1083
|
-
return [
|
1523
|
+
return [table[primary_key].desc] if primary_key
|
1084
1524
|
raise IrreversibleOrderError,
|
1085
1525
|
"Relation has no current order and table has no primary key to be used as default order"
|
1086
1526
|
end
|
@@ -1091,9 +1531,11 @@ module ActiveRecord
|
|
1091
1531
|
o.desc
|
1092
1532
|
when Arel::Nodes::Ordering
|
1093
1533
|
o.reverse
|
1534
|
+
when Arel::Nodes::NodeExpression
|
1535
|
+
o.desc
|
1094
1536
|
when String
|
1095
1537
|
if does_not_support_reverse?(o)
|
1096
|
-
raise IrreversibleOrderError, "Order #{o.inspect}
|
1538
|
+
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
1097
1539
|
end
|
1098
1540
|
o.split(",").map! do |s|
|
1099
1541
|
s.strip!
|
@@ -1113,13 +1555,11 @@ module ActiveRecord
|
|
1113
1555
|
# Uses SQL function with multiple arguments.
|
1114
1556
|
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
|
1115
1557
|
# Uses "nulls first" like construction.
|
1116
|
-
|
1558
|
+
/\bnulls\s+(?:first|last)\b/i.match?(order)
|
1117
1559
|
end
|
1118
1560
|
|
1119
1561
|
def build_order(arel)
|
1120
|
-
orders = order_values.
|
1121
|
-
orders.reject!(&:blank?)
|
1122
|
-
|
1562
|
+
orders = order_values.compact_blank
|
1123
1563
|
arel.order(*orders) unless orders.empty?
|
1124
1564
|
end
|
1125
1565
|
|
@@ -1139,21 +1579,15 @@ module ActiveRecord
|
|
1139
1579
|
end
|
1140
1580
|
|
1141
1581
|
def preprocess_order_args(order_args)
|
1142
|
-
|
1143
|
-
klass.sanitize_sql_for_order(arg)
|
1144
|
-
end
|
1145
|
-
order_args.flatten!
|
1146
|
-
|
1147
|
-
@klass.enforce_raw_sql_whitelist(
|
1582
|
+
@klass.disallow_raw_sql!(
|
1148
1583
|
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1149
|
-
|
1584
|
+
permit: connection.column_name_with_order_matcher
|
1150
1585
|
)
|
1151
1586
|
|
1152
1587
|
validate_order_args(order_args)
|
1153
1588
|
|
1154
|
-
references = order_args
|
1155
|
-
|
1156
|
-
references!(references) if references.any?
|
1589
|
+
references = column_references(order_args)
|
1590
|
+
self.references_values |= references unless references.empty?
|
1157
1591
|
|
1158
1592
|
# if a symbol is given we prepend the quoted table name
|
1159
1593
|
order_args.map! do |arg|
|
@@ -1164,9 +1598,9 @@ module ActiveRecord
|
|
1164
1598
|
arg.map { |field, dir|
|
1165
1599
|
case field
|
1166
1600
|
when Arel::Nodes::SqlLiteral
|
1167
|
-
field.
|
1601
|
+
field.public_send(dir.downcase)
|
1168
1602
|
else
|
1169
|
-
order_column(field.to_s).
|
1603
|
+
order_column(field.to_s).public_send(dir.downcase)
|
1170
1604
|
end
|
1171
1605
|
}
|
1172
1606
|
else
|
@@ -1175,16 +1609,59 @@ module ActiveRecord
|
|
1175
1609
|
end.flatten!
|
1176
1610
|
end
|
1177
1611
|
|
1612
|
+
def sanitize_order_arguments(order_args)
|
1613
|
+
order_args.map! do |arg|
|
1614
|
+
klass.sanitize_sql_for_order(arg)
|
1615
|
+
end
|
1616
|
+
end
|
1617
|
+
|
1618
|
+
def column_references(order_args)
|
1619
|
+
references = order_args.flat_map do |arg|
|
1620
|
+
case arg
|
1621
|
+
when String, Symbol
|
1622
|
+
arg
|
1623
|
+
when Hash
|
1624
|
+
arg.keys
|
1625
|
+
end
|
1626
|
+
end
|
1627
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1628
|
+
references
|
1629
|
+
end
|
1630
|
+
|
1178
1631
|
def order_column(field)
|
1179
1632
|
arel_column(field) do |attr_name|
|
1180
1633
|
if attr_name == "count" && !group_values.empty?
|
1181
|
-
|
1634
|
+
table[attr_name]
|
1182
1635
|
else
|
1183
1636
|
Arel.sql(connection.quote_table_name(attr_name))
|
1184
1637
|
end
|
1185
1638
|
end
|
1186
1639
|
end
|
1187
1640
|
|
1641
|
+
def resolve_arel_attributes(attrs)
|
1642
|
+
attrs.flat_map do |attr|
|
1643
|
+
case attr
|
1644
|
+
when Arel::Predications
|
1645
|
+
attr
|
1646
|
+
when Hash
|
1647
|
+
attr.flat_map do |table, columns|
|
1648
|
+
table = table.to_s
|
1649
|
+
Array(columns).map do |column|
|
1650
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1651
|
+
end
|
1652
|
+
end
|
1653
|
+
else
|
1654
|
+
attr = attr.to_s
|
1655
|
+
if attr.include?(".")
|
1656
|
+
table, column = attr.split(".", 2)
|
1657
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1658
|
+
else
|
1659
|
+
attr
|
1660
|
+
end
|
1661
|
+
end
|
1662
|
+
end
|
1663
|
+
end
|
1664
|
+
|
1188
1665
|
# Checks to make sure that the arguments are not blank. Note that if some
|
1189
1666
|
# blank-like object were initially passed into the query method, then this
|
1190
1667
|
# method will not raise an error.
|
@@ -1194,40 +1671,40 @@ module ActiveRecord
|
|
1194
1671
|
# Post.references() # raises an error
|
1195
1672
|
# Post.references([]) # does not raise an error
|
1196
1673
|
#
|
1197
|
-
# This particular method should be called with a method_name and the args
|
1674
|
+
# This particular method should be called with a method_name (__callee__) and the args
|
1198
1675
|
# passed into that method as an input. For example:
|
1199
1676
|
#
|
1200
1677
|
# def references(*args)
|
1201
|
-
# check_if_method_has_arguments!(
|
1678
|
+
# check_if_method_has_arguments!(__callee__, args)
|
1202
1679
|
# ...
|
1203
1680
|
# end
|
1204
|
-
def check_if_method_has_arguments!(method_name, args)
|
1681
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1205
1682
|
if args.blank?
|
1206
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1207
|
-
|
1208
|
-
|
1683
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1684
|
+
else
|
1685
|
+
yield args if block_given?
|
1209
1686
|
|
1210
|
-
|
1211
|
-
|
1212
|
-
STRUCTURAL_OR_METHODS.reject do |method|
|
1213
|
-
get_value(method) == other.get_value(method)
|
1687
|
+
args.flatten!
|
1688
|
+
args.compact_blank!
|
1214
1689
|
end
|
1215
1690
|
end
|
1216
1691
|
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1692
|
+
STRUCTURAL_VALUE_METHODS = (
|
1693
|
+
Relation::VALUE_METHODS -
|
1694
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
1695
|
+
).freeze # :nodoc:
|
1696
|
+
|
1697
|
+
def structurally_incompatible_values_for(other)
|
1698
|
+
values = other.values
|
1699
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
1700
|
+
v1, v2 = @values[method], values[method]
|
1701
|
+
if v1.is_a?(Array)
|
1702
|
+
next true unless v2.is_a?(Array)
|
1703
|
+
v1 = v1.uniq
|
1704
|
+
v2 = v2.uniq
|
1705
|
+
end
|
1706
|
+
v1 == v2
|
1707
|
+
end
|
1231
1708
|
end
|
1232
1709
|
end
|
1233
1710
|
end
|