activerecord 6.0.0 → 7.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +996 -594
- data/MIT-LICENSE +1 -1
- data/README.rdoc +34 -34
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +22 -20
- data/lib/active_record/association_relation.rb +22 -12
- data/lib/active_record/associations/alias_tracker.rb +41 -30
- data/lib/active_record/associations/association.rb +106 -41
- data/lib/active_record/associations/association_scope.rb +30 -21
- data/lib/active_record/associations/belongs_to_association.rb +69 -14
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
- data/lib/active_record/associations/builder/association.rb +39 -6
- data/lib/active_record/associations/builder/belongs_to.rb +47 -17
- data/lib/active_record/associations/builder/collection_association.rb +14 -6
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
- data/lib/active_record/associations/builder/has_many.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +13 -16
- data/lib/active_record/associations/builder/singular_association.rb +7 -3
- data/lib/active_record/associations/collection_association.rb +90 -53
- data/lib/active_record/associations/collection_proxy.rb +54 -19
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +21 -1
- data/lib/active_record/associations/has_many_association.rb +41 -10
- data/lib/active_record/associations/has_many_through_association.rb +29 -12
- data/lib/active_record/associations/has_one_association.rb +33 -9
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/join_dependency.rb +97 -54
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +237 -54
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +51 -17
- data/lib/active_record/associations/preloader.rb +55 -121
- data/lib/active_record/associations/singular_association.rb +16 -4
- data/lib/active_record/associations/through_association.rb +26 -15
- data/lib/active_record/associations.rb +454 -440
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +11 -14
- data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +75 -34
- data/lib/active_record/attribute_methods/primary_key.rb +53 -31
- data/lib/active_record/attribute_methods/query.rb +31 -22
- data/lib/active_record/attribute_methods/read.rb +16 -17
- data/lib/active_record/attribute_methods/serialization.rb +177 -35
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
- data/lib/active_record/attribute_methods/write.rb +16 -28
- data/lib/active_record/attribute_methods.rb +227 -100
- data/lib/active_record/attributes.rb +94 -56
- data/lib/active_record/autosave_association.rb +119 -73
- data/lib/active_record/base.rb +31 -21
- data/lib/active_record/callbacks.rb +168 -55
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -25
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
- data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
- data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
- data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
- data/lib/active_record/connection_adapters/column.rb +28 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
- data/lib/active_record/connection_adapters/pool_config.rb +83 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -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 +53 -15
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
- 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 +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
- data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +176 -0
- data/lib/active_record/connection_handling.rb +243 -115
- data/lib/active_record/core.rb +481 -199
- data/lib/active_record/counter_cache.rb +69 -32
- data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
- data/lib/active_record/database_configurations/database_config.rb +77 -10
- data/lib/active_record/database_configurations/hash_config.rb +148 -26
- data/lib/active_record/database_configurations/url_config.rb +44 -45
- data/lib/active_record/database_configurations.rb +190 -114
- data/lib/active_record/delegated_type.rb +279 -0
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +38 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +5 -6
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -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 +171 -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 +157 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +100 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +224 -73
- data/lib/active_record/errors.rb +254 -36
- data/lib/active_record/explain.rb +30 -17
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +2 -2
- data/lib/active_record/fixture_set/file.rb +22 -15
- data/lib/active_record/fixture_set/model_metadata.rb +15 -6
- data/lib/active_record/fixture_set/render_context.rb +3 -1
- data/lib/active_record/fixture_set/table_row.rb +88 -16
- data/lib/active_record/fixture_set/table_rows.rb +4 -5
- data/lib/active_record/fixtures.rb +229 -116
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +121 -48
- data/lib/active_record/insert_all.rb +178 -29
- data/lib/active_record/integration.rb +16 -14
- data/lib/active_record/internal_metadata.rb +132 -21
- data/lib/active_record/legacy_yaml_adapter.rb +3 -36
- data/lib/active_record/locking/optimistic.rb +64 -33
- data/lib/active_record/locking/pessimistic.rb +21 -8
- data/lib/active_record/log_subscriber.rb +61 -30
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
- data/lib/active_record/middleware/database_selector.rb +25 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +160 -55
- data/lib/active_record/migration/compatibility.rb +286 -43
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/join_table.rb +1 -2
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +421 -193
- data/lib/active_record/model_schema.rb +217 -125
- data/lib/active_record/nested_attributes.rb +62 -27
- data/lib/active_record/no_touching.rb +4 -4
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +322 -319
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -15
- data/lib/active_record/query_logs.rb +193 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +54 -14
- data/lib/active_record/railtie.rb +250 -72
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +312 -197
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +45 -3
- data/lib/active_record/reflection.rb +389 -146
- data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
- data/lib/active_record/relation/batches.rb +214 -73
- data/lib/active_record/relation/calculations.rb +379 -124
- data/lib/active_record/relation/delegation.rb +36 -23
- data/lib/active_record/relation/finder_methods.rb +159 -49
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +41 -33
- data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +79 -53
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +1156 -279
- data/lib/active_record/relation/record_fetch_warning.rb +12 -11
- data/lib/active_record/relation/spawn_methods.rb +10 -9
- data/lib/active_record/relation/where_clause.rb +100 -66
- data/lib/active_record/relation.rb +829 -194
- data/lib/active_record/result.rb +76 -56
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +86 -47
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +140 -33
- data/lib/active_record/schema_migration.rb +74 -29
- data/lib/active_record/scoping/default.rb +73 -19
- data/lib/active_record/scoping/named.rb +10 -28
- data/lib/active_record/scoping.rb +65 -35
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +34 -8
- data/lib/active_record/serialization.rb +11 -4
- data/lib/active_record/signed_id.rb +138 -0
- data/lib/active_record/statement_cache.rb +26 -10
- data/lib/active_record/store.rb +19 -14
- data/lib/active_record/suppressor.rb +15 -17
- data/lib/active_record/table_metadata.rb +46 -36
- data/lib/active_record/tasks/database_tasks.rb +371 -205
- data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
- data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +189 -104
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +35 -25
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +31 -27
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +131 -99
- data/lib/active_record/translation.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +33 -18
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +11 -6
- data/lib/active_record/type/time.rb +14 -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 +7 -2
- data/lib/active_record/type_caster/connection.rb +4 -5
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +13 -8
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +88 -18
- data/lib/active_record/validations.rb +15 -8
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +446 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +4 -8
- data/lib/arel/collectors/bind.rb +8 -1
- data/lib/arel/collectors/composite.rb +15 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/crud.rb +30 -22
- data/lib/arel/delete_manager.rb +23 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/binary.rb +82 -9
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/casted.rb +22 -10
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +14 -13
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +122 -11
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/table_alias.rb +11 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes/update_statement.rb +11 -4
- data/lib/arel/nodes.rb +10 -3
- data/lib/arel/predications.rb +31 -28
- data/lib/arel/select_manager.rb +18 -9
- data/lib/arel/table.rb +21 -10
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +25 -5
- data/lib/arel/visitors/dot.rb +94 -90
- data/lib/arel/visitors/mysql.rb +34 -6
- data/lib/arel/visitors/postgresql.rb +5 -16
- data/lib/arel/visitors/sqlite.rb +25 -1
- data/lib/arel/visitors/to_sql.rb +227 -81
- data/lib/arel/visitors/visitor.rb +2 -3
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel.rb +37 -15
- data/lib/rails/generators/active_record/application_record/USAGE +8 -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 +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
- data/lib/rails/generators/active_record/migration.rb +9 -3
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
- 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 +1 -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 +117 -30
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/null_relation.rb +0 -68
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -204
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -157
- data/lib/arel/visitors/oracle.rb +0 -159
- data/lib/arel/visitors/oracle12.rb +0 -66
- data/lib/arel/visitors/where_sql.rb +0 -23
|
@@ -3,21 +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 "
|
|
7
|
-
require "active_model/forbidden_attributes_protection"
|
|
6
|
+
require "active_support/core_ext/array/wrap"
|
|
8
7
|
|
|
9
8
|
module ActiveRecord
|
|
10
9
|
module QueryMethods
|
|
11
|
-
extend ActiveSupport::Concern
|
|
12
|
-
|
|
13
10
|
include ActiveModel::ForbiddenAttributesProtection
|
|
14
11
|
|
|
15
|
-
# WhereChain objects act as placeholder for queries in which
|
|
16
|
-
# In this case,
|
|
12
|
+
# +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter.
|
|
13
|
+
# In this case, +where+ can be chained to return a new relation.
|
|
17
14
|
class WhereChain
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def initialize(scope)
|
|
15
|
+
def initialize(scope) # :nodoc:
|
|
21
16
|
@scope = scope
|
|
22
17
|
end
|
|
23
18
|
|
|
@@ -41,137 +36,289 @@ module ActiveRecord
|
|
|
41
36
|
#
|
|
42
37
|
# User.where.not(name: %w(Ko1 Nobu))
|
|
43
38
|
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
|
39
|
+
#
|
|
40
|
+
# User.where.not(name: "Jon", role: "admin")
|
|
41
|
+
# # SELECT * FROM users WHERE NOT (name = 'Jon' AND role = 'admin')
|
|
42
|
+
#
|
|
43
|
+
# If there is a non-nil condition on a nullable column in the hash condition, the records that have
|
|
44
|
+
# nil values on the nullable column won't be returned.
|
|
45
|
+
# User.create!(nullable_country: nil)
|
|
46
|
+
# User.where.not(nullable_country: "UK")
|
|
47
|
+
# # SELECT * FROM users WHERE NOT (nullable_country = 'UK')
|
|
48
|
+
# # => []
|
|
44
49
|
def not(opts, *rest)
|
|
45
|
-
|
|
50
|
+
where_clause = @scope.send(:build_where_clause, opts, rest)
|
|
46
51
|
|
|
47
|
-
where_clause
|
|
52
|
+
@scope.where_clause += where_clause.invert
|
|
48
53
|
|
|
49
|
-
@scope
|
|
54
|
+
@scope
|
|
55
|
+
end
|
|
50
56
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
# Returns a new relation with joins and where clause to identify
|
|
58
|
+
# associated relations.
|
|
59
|
+
#
|
|
60
|
+
# For example, posts that are associated to a related author:
|
|
61
|
+
#
|
|
62
|
+
# Post.where.associated(:author)
|
|
63
|
+
# # SELECT "posts".* FROM "posts"
|
|
64
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
65
|
+
# # WHERE "authors"."id" IS NOT NULL
|
|
66
|
+
#
|
|
67
|
+
# Additionally, multiple relations can be combined. This will return posts
|
|
68
|
+
# associated to both an author and any comments:
|
|
69
|
+
#
|
|
70
|
+
# Post.where.associated(:author, :comments)
|
|
71
|
+
# # SELECT "posts".* FROM "posts"
|
|
72
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
73
|
+
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
|
74
|
+
# # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
|
|
75
|
+
#
|
|
76
|
+
# You can define join type in the scope and +associated+ will not use `JOIN` by default.
|
|
77
|
+
#
|
|
78
|
+
# Post.left_joins(:author).where.associated(:author)
|
|
79
|
+
# # SELECT "posts".* FROM "posts"
|
|
80
|
+
# # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
|
|
81
|
+
# # WHERE "authors"."id" IS NOT NULL
|
|
82
|
+
#
|
|
83
|
+
# Post.left_joins(:comments).where.associated(:author)
|
|
84
|
+
# # SELECT "posts".* FROM "posts"
|
|
85
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
86
|
+
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
|
87
|
+
# # WHERE "author"."id" IS NOT NULL
|
|
88
|
+
def associated(*associations)
|
|
89
|
+
associations.each do |association|
|
|
90
|
+
reflection = scope_association_reflection(association)
|
|
91
|
+
unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
|
|
92
|
+
@scope.joins!(association)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if reflection.options[:class_name]
|
|
96
|
+
self.not(association => { reflection.association_primary_key => nil })
|
|
97
|
+
else
|
|
98
|
+
self.not(reflection.table_name => { reflection.association_primary_key => nil })
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
@scope
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Returns a new relation with left outer joins and where clause to identify
|
|
106
|
+
# missing relations.
|
|
107
|
+
#
|
|
108
|
+
# For example, posts that are missing a related author:
|
|
109
|
+
#
|
|
110
|
+
# Post.where.missing(:author)
|
|
111
|
+
# # SELECT "posts".* FROM "posts"
|
|
112
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
113
|
+
# # WHERE "authors"."id" IS NULL
|
|
114
|
+
#
|
|
115
|
+
# Additionally, multiple relations can be combined. This will return posts
|
|
116
|
+
# that are missing both an author and any comments:
|
|
117
|
+
#
|
|
118
|
+
# Post.where.missing(:author, :comments)
|
|
119
|
+
# # SELECT "posts".* FROM "posts"
|
|
120
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
121
|
+
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
|
122
|
+
# # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
|
|
123
|
+
def missing(*associations)
|
|
124
|
+
associations.each do |association|
|
|
125
|
+
reflection = scope_association_reflection(association)
|
|
126
|
+
@scope.left_outer_joins!(association)
|
|
127
|
+
if reflection.options[:class_name]
|
|
128
|
+
@scope.where!(association => { reflection.association_primary_key => nil })
|
|
129
|
+
else
|
|
130
|
+
@scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
|
|
131
|
+
end
|
|
60
132
|
end
|
|
61
133
|
|
|
62
134
|
@scope
|
|
63
135
|
end
|
|
64
136
|
|
|
65
137
|
private
|
|
66
|
-
def
|
|
67
|
-
|
|
138
|
+
def scope_association_reflection(association)
|
|
139
|
+
reflection = @scope.klass._reflect_on_association(association)
|
|
140
|
+
unless reflection
|
|
141
|
+
raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
|
|
142
|
+
end
|
|
143
|
+
reflection
|
|
68
144
|
end
|
|
69
145
|
end
|
|
70
146
|
|
|
147
|
+
# A wrapper to distinguish CTE joins from other nodes.
|
|
148
|
+
class CTEJoin # :nodoc:
|
|
149
|
+
attr_reader :name
|
|
150
|
+
|
|
151
|
+
def initialize(name)
|
|
152
|
+
@name = name
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
71
156
|
FROZEN_EMPTY_ARRAY = [].freeze
|
|
72
157
|
FROZEN_EMPTY_HASH = {}.freeze
|
|
73
158
|
|
|
74
159
|
Relation::VALUE_METHODS.each do |name|
|
|
75
|
-
method_name =
|
|
160
|
+
method_name, default =
|
|
76
161
|
case name
|
|
77
|
-
when *Relation::MULTI_VALUE_METHODS
|
|
78
|
-
|
|
79
|
-
when *Relation::
|
|
162
|
+
when *Relation::MULTI_VALUE_METHODS
|
|
163
|
+
["#{name}_values", "FROZEN_EMPTY_ARRAY"]
|
|
164
|
+
when *Relation::SINGLE_VALUE_METHODS
|
|
165
|
+
["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
|
|
166
|
+
when *Relation::CLAUSE_METHODS
|
|
167
|
+
["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
|
|
80
168
|
end
|
|
169
|
+
|
|
81
170
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
82
|
-
def #{method_name}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
end # end
|
|
171
|
+
def #{method_name} # def includes_values
|
|
172
|
+
@values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
|
|
173
|
+
end # end
|
|
174
|
+
|
|
175
|
+
def #{method_name}=(value) # def includes_values=(value)
|
|
176
|
+
assert_modifiable! # assert_modifiable!
|
|
177
|
+
@values[:#{name}] = value # @values[:includes] = value
|
|
178
|
+
end # end
|
|
91
179
|
CODE
|
|
92
180
|
end
|
|
93
181
|
|
|
94
182
|
alias extensions extending_values
|
|
95
183
|
|
|
96
|
-
# Specify
|
|
97
|
-
#
|
|
184
|
+
# Specify associations +args+ to be eager loaded to prevent N + 1 queries.
|
|
185
|
+
# A separate query is performed for each association, unless a join is
|
|
186
|
+
# required by conditions.
|
|
187
|
+
#
|
|
188
|
+
# For example:
|
|
98
189
|
#
|
|
99
|
-
# users = User.includes(:address)
|
|
190
|
+
# users = User.includes(:address).limit(5)
|
|
100
191
|
# users.each do |user|
|
|
101
192
|
# user.address.city
|
|
102
193
|
# end
|
|
103
194
|
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
195
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
|
196
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
|
197
|
+
#
|
|
198
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
|
199
|
+
# are loaded with a single query.
|
|
107
200
|
#
|
|
108
|
-
#
|
|
201
|
+
# Loading the associations in a separate query will often result in a
|
|
202
|
+
# performance improvement over a simple join, as a join can result in many
|
|
203
|
+
# rows that contain redundant data and it performs poorly at scale.
|
|
109
204
|
#
|
|
110
|
-
#
|
|
205
|
+
# You can also specify multiple associations. Each association will result
|
|
206
|
+
# in an additional query:
|
|
111
207
|
#
|
|
112
|
-
#
|
|
208
|
+
# User.includes(:address, :friends).to_a
|
|
209
|
+
# # SELECT "users".* FROM "users"
|
|
210
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
|
211
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
|
113
212
|
#
|
|
114
|
-
#
|
|
213
|
+
# Loading nested associations is possible using a Hash:
|
|
115
214
|
#
|
|
116
|
-
#
|
|
215
|
+
# User.includes(:address, friends: [:address, :followers])
|
|
216
|
+
#
|
|
217
|
+
# === Conditions
|
|
117
218
|
#
|
|
118
219
|
# If you want to add string conditions to your included models, you'll have
|
|
119
220
|
# to explicitly reference them. For example:
|
|
120
221
|
#
|
|
121
|
-
# User.includes(:posts).where('posts.name = ?', 'example')
|
|
222
|
+
# User.includes(:posts).where('posts.name = ?', 'example').to_a
|
|
122
223
|
#
|
|
123
224
|
# Will throw an error, but this will work:
|
|
124
225
|
#
|
|
125
|
-
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
|
226
|
+
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
|
|
227
|
+
# # SELECT "users"."id" AS t0_r0, ... FROM "users"
|
|
228
|
+
# # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
|
229
|
+
# # WHERE "posts"."name" = ? [["name", "example"]]
|
|
230
|
+
#
|
|
231
|
+
# As the <tt>LEFT OUTER JOIN</tt> already contains the posts, the second query for
|
|
232
|
+
# the posts is no longer performed.
|
|
126
233
|
#
|
|
127
234
|
# Note that #includes works with association names while #references needs
|
|
128
235
|
# the actual table name.
|
|
129
236
|
#
|
|
130
|
-
# If you pass the conditions via
|
|
237
|
+
# If you pass the conditions via a Hash, you don't need to call #references
|
|
131
238
|
# explicitly, as #where references the tables for you. For example, this
|
|
132
239
|
# will work correctly:
|
|
133
240
|
#
|
|
134
241
|
# User.includes(:posts).where(posts: { name: 'example' })
|
|
242
|
+
#
|
|
243
|
+
# NOTE: Conditions affect both sides of an association. For example, the
|
|
244
|
+
# above code will return only users that have a post named "example",
|
|
245
|
+
# <em>and will only include posts named "example"</em>, even when a
|
|
246
|
+
# matching user has other additional posts.
|
|
135
247
|
def includes(*args)
|
|
136
|
-
check_if_method_has_arguments!(
|
|
248
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
137
249
|
spawn.includes!(*args)
|
|
138
250
|
end
|
|
139
251
|
|
|
140
252
|
def includes!(*args) # :nodoc:
|
|
141
|
-
args.reject!(&:blank?)
|
|
142
|
-
args.flatten!
|
|
143
|
-
|
|
144
253
|
self.includes_values |= args
|
|
145
254
|
self
|
|
146
255
|
end
|
|
147
256
|
|
|
148
|
-
#
|
|
257
|
+
# Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
|
|
258
|
+
# Performs a single query joining all specified associations. For example:
|
|
259
|
+
#
|
|
260
|
+
# users = User.eager_load(:address).limit(5)
|
|
261
|
+
# users.each do |user|
|
|
262
|
+
# user.address.city
|
|
263
|
+
# end
|
|
264
|
+
#
|
|
265
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
|
266
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
|
267
|
+
# # LIMIT 5
|
|
268
|
+
#
|
|
269
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
|
270
|
+
# are loaded with a single joined query.
|
|
149
271
|
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
272
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
|
273
|
+
# similar to #includes:
|
|
274
|
+
#
|
|
275
|
+
# User.eager_load(:address, friends: [:address, :followers])
|
|
276
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
|
277
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
|
278
|
+
# # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
|
|
279
|
+
# # ...
|
|
280
|
+
#
|
|
281
|
+
# NOTE: Loading the associations in a join can result in many rows that
|
|
282
|
+
# contain redundant data and it performs poorly at scale.
|
|
154
283
|
def eager_load(*args)
|
|
155
|
-
check_if_method_has_arguments!(
|
|
284
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
156
285
|
spawn.eager_load!(*args)
|
|
157
286
|
end
|
|
158
287
|
|
|
159
288
|
def eager_load!(*args) # :nodoc:
|
|
160
|
-
self.eager_load_values
|
|
289
|
+
self.eager_load_values |= args
|
|
161
290
|
self
|
|
162
291
|
end
|
|
163
292
|
|
|
164
|
-
#
|
|
293
|
+
# Specify associations +args+ to be eager loaded using separate queries.
|
|
294
|
+
# A separate query is performed for each association.
|
|
295
|
+
#
|
|
296
|
+
# users = User.preload(:address).limit(5)
|
|
297
|
+
# users.each do |user|
|
|
298
|
+
# user.address.city
|
|
299
|
+
# end
|
|
165
300
|
#
|
|
166
|
-
#
|
|
167
|
-
# # SELECT "
|
|
301
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
|
302
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
|
303
|
+
#
|
|
304
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
|
305
|
+
# are loaded with a separate query.
|
|
306
|
+
#
|
|
307
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
|
308
|
+
# similar to #includes:
|
|
309
|
+
#
|
|
310
|
+
# User.preload(:address, friends: [:address, :followers])
|
|
311
|
+
# # SELECT "users".* FROM "users"
|
|
312
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
|
313
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
|
314
|
+
# # SELECT ...
|
|
168
315
|
def preload(*args)
|
|
169
|
-
check_if_method_has_arguments!(
|
|
316
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
170
317
|
spawn.preload!(*args)
|
|
171
318
|
end
|
|
172
319
|
|
|
173
320
|
def preload!(*args) # :nodoc:
|
|
174
|
-
self.preload_values
|
|
321
|
+
self.preload_values |= args
|
|
175
322
|
self
|
|
176
323
|
end
|
|
177
324
|
|
|
@@ -189,7 +336,7 @@ module ActiveRecord
|
|
|
189
336
|
end
|
|
190
337
|
|
|
191
338
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
|
192
|
-
# and should therefore be
|
|
339
|
+
# and should therefore be +JOIN+ed in any query rather than loaded separately.
|
|
193
340
|
# This method only works in conjunction with #includes.
|
|
194
341
|
# See #includes for more details.
|
|
195
342
|
#
|
|
@@ -199,14 +346,11 @@ module ActiveRecord
|
|
|
199
346
|
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
|
|
200
347
|
# # Query now knows the string references posts, so adds a JOIN
|
|
201
348
|
def references(*table_names)
|
|
202
|
-
check_if_method_has_arguments!(
|
|
349
|
+
check_if_method_has_arguments!(__callee__, table_names)
|
|
203
350
|
spawn.references!(*table_names)
|
|
204
351
|
end
|
|
205
352
|
|
|
206
353
|
def references!(*table_names) # :nodoc:
|
|
207
|
-
table_names.flatten!
|
|
208
|
-
table_names.map!(&:to_s)
|
|
209
|
-
|
|
210
354
|
self.references_values |= table_names
|
|
211
355
|
self
|
|
212
356
|
end
|
|
@@ -236,10 +380,18 @@ module ActiveRecord
|
|
|
236
380
|
# Model.select(:field, :other_field, :and_one_more)
|
|
237
381
|
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
|
|
238
382
|
#
|
|
383
|
+
# The argument also can be a hash of fields and aliases.
|
|
384
|
+
#
|
|
385
|
+
# Model.select(models: { field: :alias, other_field: :other_alias })
|
|
386
|
+
# # => [#<Model id: nil, alias: "value", other_alias: "value">]
|
|
387
|
+
#
|
|
388
|
+
# Model.select(models: [:field, :other_field])
|
|
389
|
+
# # => [#<Model id: nil, field: "value", other_field: "value">]
|
|
390
|
+
#
|
|
239
391
|
# You can also use one or more strings, which will be used unchanged as SELECT fields.
|
|
240
392
|
#
|
|
241
393
|
# Model.select('field AS field_one', 'other_field AS field_two')
|
|
242
|
-
# # => [#<Model id: nil,
|
|
394
|
+
# # => [#<Model id: nil, field_one: "value", field_two: "value">]
|
|
243
395
|
#
|
|
244
396
|
# If an alias was specified, it will be accessible from the resulting objects:
|
|
245
397
|
#
|
|
@@ -250,7 +402,7 @@ module ActiveRecord
|
|
|
250
402
|
# except +id+ will throw ActiveModel::MissingAttributeError:
|
|
251
403
|
#
|
|
252
404
|
# Model.select(:field).first.other_field
|
|
253
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
|
405
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
|
|
254
406
|
def select(*fields)
|
|
255
407
|
if block_given?
|
|
256
408
|
if fields.any?
|
|
@@ -260,14 +412,112 @@ module ActiveRecord
|
|
|
260
412
|
return super()
|
|
261
413
|
end
|
|
262
414
|
|
|
263
|
-
|
|
415
|
+
check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
|
|
416
|
+
|
|
417
|
+
fields = process_select_args(fields)
|
|
264
418
|
spawn._select!(*fields)
|
|
265
419
|
end
|
|
266
420
|
|
|
267
421
|
def _select!(*fields) # :nodoc:
|
|
268
|
-
fields
|
|
269
|
-
|
|
270
|
-
|
|
422
|
+
self.select_values |= fields
|
|
423
|
+
self
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
|
427
|
+
#
|
|
428
|
+
# Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to
|
|
429
|
+
# use CTE's with MySQL 5.7.
|
|
430
|
+
#
|
|
431
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
|
|
432
|
+
# # => ActiveRecord::Relation
|
|
433
|
+
# # WITH posts_with_tags AS (
|
|
434
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
|
435
|
+
# # )
|
|
436
|
+
# # SELECT * FROM posts
|
|
437
|
+
#
|
|
438
|
+
# You can also pass an array of sub-queries to be joined in a +UNION ALL+.
|
|
439
|
+
#
|
|
440
|
+
# Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
|
|
441
|
+
# # => ActiveRecord::Relation
|
|
442
|
+
# # WITH posts_with_tags_or_comments AS (
|
|
443
|
+
# # (SELECT * FROM posts WHERE (tags_count > 0))
|
|
444
|
+
# # UNION ALL
|
|
445
|
+
# # (SELECT * FROM posts WHERE (comments_count > 0))
|
|
446
|
+
# # )
|
|
447
|
+
# # SELECT * FROM posts
|
|
448
|
+
#
|
|
449
|
+
# Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
|
|
450
|
+
#
|
|
451
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
|
|
452
|
+
# # => ActiveRecord::Relation
|
|
453
|
+
# # WITH posts_with_tags AS (
|
|
454
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
|
455
|
+
# # )
|
|
456
|
+
# # SELECT * FROM posts_with_tags AS posts
|
|
457
|
+
#
|
|
458
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
|
|
459
|
+
# # => ActiveRecord::Relation
|
|
460
|
+
# # WITH posts_with_tags AS (
|
|
461
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
|
462
|
+
# # )
|
|
463
|
+
# # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
|
|
464
|
+
#
|
|
465
|
+
# It is recommended to pass a query as ActiveRecord::Relation. If that is not possible
|
|
466
|
+
# and you have verified it is safe for the database, you can pass it as SQL literal
|
|
467
|
+
# using +Arel+.
|
|
468
|
+
#
|
|
469
|
+
# Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
|
|
470
|
+
#
|
|
471
|
+
# Great caution should be taken to avoid SQL injection vulnerabilities. This method should not
|
|
472
|
+
# be used with unsafe values that include unsanitized input.
|
|
473
|
+
#
|
|
474
|
+
# To add multiple CTEs just pass multiple key-value pairs
|
|
475
|
+
#
|
|
476
|
+
# Post.with(
|
|
477
|
+
# posts_with_comments: Post.where("comments_count > ?", 0),
|
|
478
|
+
# posts_with_tags: Post.where("tags_count > ?", 0)
|
|
479
|
+
# )
|
|
480
|
+
#
|
|
481
|
+
# or chain multiple +.with+ calls
|
|
482
|
+
#
|
|
483
|
+
# Post
|
|
484
|
+
# .with(posts_with_comments: Post.where("comments_count > ?", 0))
|
|
485
|
+
# .with(posts_with_tags: Post.where("tags_count > ?", 0))
|
|
486
|
+
def with(*args)
|
|
487
|
+
raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
|
|
488
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
489
|
+
spawn.with!(*args)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Like #with, but modifies relation in place.
|
|
493
|
+
def with!(*args) # :nodoc:
|
|
494
|
+
args = process_with_args(args)
|
|
495
|
+
self.with_values |= args
|
|
496
|
+
self
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
|
500
|
+
#
|
|
501
|
+
# Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
|
|
502
|
+
# # => ActiveRecord::Relation
|
|
503
|
+
# # WITH post_and_replies AS (
|
|
504
|
+
# # (SELECT * FROM posts WHERE id = 42)
|
|
505
|
+
# # UNION ALL
|
|
506
|
+
# # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
|
|
507
|
+
# # )
|
|
508
|
+
# # SELECT * FROM posts
|
|
509
|
+
#
|
|
510
|
+
# See `#with` for more information.
|
|
511
|
+
def with_recursive(*args)
|
|
512
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
513
|
+
spawn.with_recursive!(*args)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Like #with_recursive but modifies the relation in place.
|
|
517
|
+
def with_recursive!(*args) # :nodoc:
|
|
518
|
+
args = process_with_args(args)
|
|
519
|
+
self.with_values |= args
|
|
520
|
+
@with_is_recursive = true
|
|
271
521
|
self
|
|
272
522
|
end
|
|
273
523
|
|
|
@@ -282,7 +532,8 @@ module ActiveRecord
|
|
|
282
532
|
# This is short-hand for <tt>unscope(:select).select(fields)</tt>.
|
|
283
533
|
# Note that we're unscoping the entire select statement.
|
|
284
534
|
def reselect(*args)
|
|
285
|
-
check_if_method_has_arguments!(
|
|
535
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
536
|
+
args = process_select_args(args)
|
|
286
537
|
spawn.reselect!(*args)
|
|
287
538
|
end
|
|
288
539
|
|
|
@@ -313,28 +564,67 @@ module ActiveRecord
|
|
|
313
564
|
# User.select([:id, :first_name]).group(:id, :first_name).first(3)
|
|
314
565
|
# # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
|
|
315
566
|
def group(*args)
|
|
316
|
-
check_if_method_has_arguments!(
|
|
567
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
317
568
|
spawn.group!(*args)
|
|
318
569
|
end
|
|
319
570
|
|
|
320
571
|
def group!(*args) # :nodoc:
|
|
321
|
-
args.flatten!
|
|
322
|
-
|
|
323
572
|
self.group_values += args
|
|
324
573
|
self
|
|
325
574
|
end
|
|
326
575
|
|
|
327
|
-
# Allows to
|
|
576
|
+
# Allows you to change a previously set group statement.
|
|
577
|
+
#
|
|
578
|
+
# Post.group(:title, :body)
|
|
579
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
|
|
580
|
+
#
|
|
581
|
+
# Post.group(:title, :body).regroup(:title)
|
|
582
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
|
|
583
|
+
#
|
|
584
|
+
# This is short-hand for <tt>unscope(:group).group(fields)</tt>.
|
|
585
|
+
# Note that we're unscoping the entire group statement.
|
|
586
|
+
def regroup(*args)
|
|
587
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
588
|
+
spawn.regroup!(*args)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# Same as #regroup but operates on relation in-place instead of copying.
|
|
592
|
+
def regroup!(*args) # :nodoc:
|
|
593
|
+
self.group_values = args
|
|
594
|
+
self
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# Applies an <code>ORDER BY</code> clause to a query.
|
|
598
|
+
#
|
|
599
|
+
# #order accepts arguments in one of several formats.
|
|
600
|
+
#
|
|
601
|
+
# === symbols
|
|
602
|
+
#
|
|
603
|
+
# The symbol represents the name of the column you want to order the results by.
|
|
328
604
|
#
|
|
329
605
|
# User.order(:name)
|
|
330
606
|
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
|
|
331
607
|
#
|
|
608
|
+
# By default, the order is ascending. If you want descending order, you can
|
|
609
|
+
# map the column name symbol to +:desc+.
|
|
610
|
+
#
|
|
332
611
|
# User.order(email: :desc)
|
|
333
612
|
# # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
|
|
334
613
|
#
|
|
614
|
+
# Multiple columns can be passed this way, and they will be applied in the order specified.
|
|
615
|
+
#
|
|
335
616
|
# User.order(:name, email: :desc)
|
|
336
617
|
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
|
|
337
618
|
#
|
|
619
|
+
# === strings
|
|
620
|
+
#
|
|
621
|
+
# Strings are passed directly to the database, allowing you to specify
|
|
622
|
+
# simple SQL expressions.
|
|
623
|
+
#
|
|
624
|
+
# This could be a source of SQL injection, so only strings composed of plain
|
|
625
|
+
# column names and simple <code>function(column_name)</code> expressions
|
|
626
|
+
# with optional +ASC+/+DESC+ modifiers are allowed.
|
|
627
|
+
#
|
|
338
628
|
# User.order('name')
|
|
339
629
|
# # SELECT "users".* FROM "users" ORDER BY name
|
|
340
630
|
#
|
|
@@ -343,19 +633,93 @@ module ActiveRecord
|
|
|
343
633
|
#
|
|
344
634
|
# User.order('name DESC, email')
|
|
345
635
|
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
|
|
636
|
+
#
|
|
637
|
+
# === Arel
|
|
638
|
+
#
|
|
639
|
+
# If you need to pass in complicated expressions that you have verified
|
|
640
|
+
# are safe for the database, you can use Arel.
|
|
641
|
+
#
|
|
642
|
+
# User.order(Arel.sql('end_date - start_date'))
|
|
643
|
+
# # SELECT "users".* FROM "users" ORDER BY end_date - start_date
|
|
644
|
+
#
|
|
645
|
+
# Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
|
|
646
|
+
#
|
|
647
|
+
# User.order(Arel.sql("payload->>'kind'"))
|
|
648
|
+
# # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
|
|
346
649
|
def order(*args)
|
|
347
|
-
check_if_method_has_arguments!(
|
|
650
|
+
check_if_method_has_arguments!(__callee__, args) do
|
|
651
|
+
sanitize_order_arguments(args)
|
|
652
|
+
end
|
|
348
653
|
spawn.order!(*args)
|
|
349
654
|
end
|
|
350
655
|
|
|
351
656
|
# Same as #order but operates on relation in-place instead of copying.
|
|
352
657
|
def order!(*args) # :nodoc:
|
|
353
|
-
preprocess_order_args(args)
|
|
354
|
-
|
|
355
|
-
self.order_values += args
|
|
658
|
+
preprocess_order_args(args) unless args.empty?
|
|
659
|
+
self.order_values |= args
|
|
356
660
|
self
|
|
357
661
|
end
|
|
358
662
|
|
|
663
|
+
# Applies an <tt>ORDER BY</tt> clause based on a given +column+,
|
|
664
|
+
# ordered and filtered by a specific set of +values+.
|
|
665
|
+
#
|
|
666
|
+
# User.in_order_of(:id, [1, 5, 3])
|
|
667
|
+
# # SELECT "users".* FROM "users"
|
|
668
|
+
# # WHERE "users"."id" IN (1, 5, 3)
|
|
669
|
+
# # ORDER BY CASE
|
|
670
|
+
# # WHEN "users"."id" = 1 THEN 1
|
|
671
|
+
# # WHEN "users"."id" = 5 THEN 2
|
|
672
|
+
# # WHEN "users"."id" = 3 THEN 3
|
|
673
|
+
# # END ASC
|
|
674
|
+
#
|
|
675
|
+
# +column+ can point to an enum column; the actual query generated may be different depending
|
|
676
|
+
# on the database adapter and the column definition.
|
|
677
|
+
#
|
|
678
|
+
# class Conversation < ActiveRecord::Base
|
|
679
|
+
# enum :status, [ :active, :archived ]
|
|
680
|
+
# end
|
|
681
|
+
#
|
|
682
|
+
# Conversation.in_order_of(:status, [:archived, :active])
|
|
683
|
+
# # SELECT "conversations".* FROM "conversations"
|
|
684
|
+
# # WHERE "conversations"."status" IN (1, 0)
|
|
685
|
+
# # ORDER BY CASE
|
|
686
|
+
# # WHEN "conversations"."status" = 1 THEN 1
|
|
687
|
+
# # WHEN "conversations"."status" = 0 THEN 2
|
|
688
|
+
# # END ASC
|
|
689
|
+
#
|
|
690
|
+
# +values+ can also include +nil+.
|
|
691
|
+
#
|
|
692
|
+
# Conversation.in_order_of(:status, [nil, :archived, :active])
|
|
693
|
+
# # SELECT "conversations".* FROM "conversations"
|
|
694
|
+
# # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
|
|
695
|
+
# # ORDER BY CASE
|
|
696
|
+
# # WHEN "conversations"."status" IS NULL THEN 1
|
|
697
|
+
# # WHEN "conversations"."status" = 1 THEN 2
|
|
698
|
+
# # WHEN "conversations"."status" = 0 THEN 3
|
|
699
|
+
# # END ASC
|
|
700
|
+
#
|
|
701
|
+
def in_order_of(column, values)
|
|
702
|
+
klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
|
|
703
|
+
return spawn.none! if values.empty?
|
|
704
|
+
|
|
705
|
+
references = column_references([column])
|
|
706
|
+
self.references_values |= references unless references.empty?
|
|
707
|
+
|
|
708
|
+
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
|
709
|
+
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
|
|
710
|
+
|
|
711
|
+
where_clause =
|
|
712
|
+
if values.include?(nil)
|
|
713
|
+
arel_column.in(values.compact).or(arel_column.eq(nil))
|
|
714
|
+
else
|
|
715
|
+
arel_column.in(values)
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
spawn
|
|
719
|
+
.order!(build_case_for_value_position(arel_column, values))
|
|
720
|
+
.where!(where_clause)
|
|
721
|
+
end
|
|
722
|
+
|
|
359
723
|
# Replaces any existing order defined on the relation with the specified order.
|
|
360
724
|
#
|
|
361
725
|
# User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
|
|
@@ -364,16 +728,18 @@ module ActiveRecord
|
|
|
364
728
|
#
|
|
365
729
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
|
366
730
|
#
|
|
367
|
-
# generates a query with
|
|
731
|
+
# generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
|
|
368
732
|
def reorder(*args)
|
|
369
|
-
check_if_method_has_arguments!(
|
|
733
|
+
check_if_method_has_arguments!(__callee__, args) do
|
|
734
|
+
sanitize_order_arguments(args)
|
|
735
|
+
end
|
|
370
736
|
spawn.reorder!(*args)
|
|
371
737
|
end
|
|
372
738
|
|
|
373
739
|
# Same as #reorder but operates on relation in-place instead of copying.
|
|
374
740
|
def reorder!(*args) # :nodoc:
|
|
375
|
-
preprocess_order_args(args)
|
|
376
|
-
|
|
741
|
+
preprocess_order_args(args)
|
|
742
|
+
args.uniq!
|
|
377
743
|
self.reordering_value = true
|
|
378
744
|
self.order_values = args
|
|
379
745
|
self
|
|
@@ -381,7 +747,8 @@ module ActiveRecord
|
|
|
381
747
|
|
|
382
748
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
|
383
749
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
|
384
|
-
:includes, :
|
|
750
|
+
:includes, :eager_load, :preload, :from, :readonly,
|
|
751
|
+
:having, :optimizer_hints, :with])
|
|
385
752
|
|
|
386
753
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
|
387
754
|
# This is useful when passing around chains of relations and would like to
|
|
@@ -417,12 +784,11 @@ module ActiveRecord
|
|
|
417
784
|
# has_many :comments, -> { unscope(where: :trashed) }
|
|
418
785
|
#
|
|
419
786
|
def unscope(*args)
|
|
420
|
-
check_if_method_has_arguments!(
|
|
787
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
421
788
|
spawn.unscope!(*args)
|
|
422
789
|
end
|
|
423
790
|
|
|
424
791
|
def unscope!(*args) # :nodoc:
|
|
425
|
-
args.flatten!
|
|
426
792
|
self.unscope_values += args
|
|
427
793
|
|
|
428
794
|
args.each do |scope|
|
|
@@ -432,15 +798,15 @@ module ActiveRecord
|
|
|
432
798
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
|
433
799
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
|
434
800
|
end
|
|
435
|
-
|
|
436
|
-
@values
|
|
801
|
+
assert_modifiable!
|
|
802
|
+
@values.delete(scope)
|
|
437
803
|
when Hash
|
|
438
804
|
scope.each do |key, target_value|
|
|
439
805
|
if key != :where
|
|
440
806
|
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
|
|
441
807
|
end
|
|
442
808
|
|
|
443
|
-
target_values = Array(target_value)
|
|
809
|
+
target_values = resolve_arel_attributes(Array.wrap(target_value))
|
|
444
810
|
self.where_clause = where_clause.except(*target_values)
|
|
445
811
|
end
|
|
446
812
|
else
|
|
@@ -451,7 +817,7 @@ module ActiveRecord
|
|
|
451
817
|
self
|
|
452
818
|
end
|
|
453
819
|
|
|
454
|
-
# Performs
|
|
820
|
+
# Performs JOINs on +args+. The given symbol(s) should match the name of
|
|
455
821
|
# the association(s).
|
|
456
822
|
#
|
|
457
823
|
# User.joins(:posts)
|
|
@@ -473,29 +839,26 @@ module ActiveRecord
|
|
|
473
839
|
# # SELECT "users".*
|
|
474
840
|
# # FROM "users"
|
|
475
841
|
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
|
476
|
-
# # INNER JOIN "comments" "
|
|
477
|
-
# # ON "comments_posts"."post_id" = "posts"."id"
|
|
842
|
+
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
|
478
843
|
#
|
|
479
844
|
# You can use strings in order to customize your joins:
|
|
480
845
|
#
|
|
481
846
|
# User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
|
|
482
847
|
# # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
|
|
483
848
|
def joins(*args)
|
|
484
|
-
check_if_method_has_arguments!(
|
|
849
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
485
850
|
spawn.joins!(*args)
|
|
486
851
|
end
|
|
487
852
|
|
|
488
853
|
def joins!(*args) # :nodoc:
|
|
489
|
-
args
|
|
490
|
-
args.flatten!
|
|
491
|
-
self.joins_values += args
|
|
854
|
+
self.joins_values |= args
|
|
492
855
|
self
|
|
493
856
|
end
|
|
494
857
|
|
|
495
|
-
# Performs
|
|
858
|
+
# Performs LEFT OUTER JOINs on +args+:
|
|
496
859
|
#
|
|
497
860
|
# User.left_outer_joins(:posts)
|
|
498
|
-
#
|
|
861
|
+
# # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
|
499
862
|
#
|
|
500
863
|
def left_outer_joins(*args)
|
|
501
864
|
check_if_method_has_arguments!(__callee__, args)
|
|
@@ -504,9 +867,7 @@ module ActiveRecord
|
|
|
504
867
|
alias :left_joins :left_outer_joins
|
|
505
868
|
|
|
506
869
|
def left_outer_joins!(*args) # :nodoc:
|
|
507
|
-
args
|
|
508
|
-
args.flatten!
|
|
509
|
-
self.left_outer_joins_values += args
|
|
870
|
+
self.left_outer_joins_values |= args
|
|
510
871
|
self
|
|
511
872
|
end
|
|
512
873
|
|
|
@@ -517,7 +878,7 @@ module ActiveRecord
|
|
|
517
878
|
# SQL is given as an illustration; the actual query generated may be different depending
|
|
518
879
|
# on the database adapter.
|
|
519
880
|
#
|
|
520
|
-
# ===
|
|
881
|
+
# === \String
|
|
521
882
|
#
|
|
522
883
|
# A single string, without additional arguments, is passed to the query
|
|
523
884
|
# constructor as an SQL fragment, and used in the where clause of the query.
|
|
@@ -529,7 +890,7 @@ module ActiveRecord
|
|
|
529
890
|
# to injection attacks if not done properly. As an alternative, it is recommended
|
|
530
891
|
# to use one of the following methods.
|
|
531
892
|
#
|
|
532
|
-
# ===
|
|
893
|
+
# === \Array
|
|
533
894
|
#
|
|
534
895
|
# If an array is passed, then the first element of the array is treated as a template, and
|
|
535
896
|
# the remaining elements are inserted into the template to generate the condition.
|
|
@@ -569,20 +930,20 @@ module ActiveRecord
|
|
|
569
930
|
# dependencies on the underlying database. If your code is intended for general consumption,
|
|
570
931
|
# test with multiple database backends.
|
|
571
932
|
#
|
|
572
|
-
# ===
|
|
933
|
+
# === \Hash
|
|
573
934
|
#
|
|
574
935
|
# #where will also accept a hash condition, in which the keys are fields and the values
|
|
575
936
|
# are values to be searched for.
|
|
576
937
|
#
|
|
577
938
|
# Fields can be symbols or strings. Values can be single values, arrays, or ranges.
|
|
578
939
|
#
|
|
579
|
-
# User.where(
|
|
940
|
+
# User.where(name: "Joe", email: "joe@example.com")
|
|
580
941
|
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
|
|
581
942
|
#
|
|
582
|
-
# User.where(
|
|
943
|
+
# User.where(name: ["Alice", "Bob"])
|
|
583
944
|
# # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
|
|
584
945
|
#
|
|
585
|
-
# User.where(
|
|
946
|
+
# User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
|
|
586
947
|
# # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
|
|
587
948
|
#
|
|
588
949
|
# In the case of a belongs_to relationship, an association key can be used
|
|
@@ -603,6 +964,12 @@ module ActiveRecord
|
|
|
603
964
|
# PriceEstimate.where(estimate_of: treasure)
|
|
604
965
|
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
|
605
966
|
#
|
|
967
|
+
# Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
|
|
968
|
+
# an array of columns with an array of tuples as values.
|
|
969
|
+
#
|
|
970
|
+
# Article.where([:author_id, :id] => [[15, 1], [15, 2]])
|
|
971
|
+
# # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
|
|
972
|
+
#
|
|
606
973
|
# === Joins
|
|
607
974
|
#
|
|
608
975
|
# If the relation is the result of a join, you may create a condition which uses any of the
|
|
@@ -612,37 +979,49 @@ module ActiveRecord
|
|
|
612
979
|
#
|
|
613
980
|
# For hash conditions, you can either use the table name in the key, or use a sub-hash.
|
|
614
981
|
#
|
|
615
|
-
# User.joins(:posts).where(
|
|
616
|
-
# User.joins(:posts).where(
|
|
982
|
+
# User.joins(:posts).where("posts.published" => true)
|
|
983
|
+
# User.joins(:posts).where(posts: { published: true })
|
|
617
984
|
#
|
|
618
|
-
# ===
|
|
985
|
+
# === No Argument
|
|
619
986
|
#
|
|
620
987
|
# If no argument is passed, #where returns a new instance of WhereChain, that
|
|
621
|
-
# can be chained with #not
|
|
988
|
+
# can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
|
|
989
|
+
#
|
|
990
|
+
# Chaining with WhereChain#not:
|
|
622
991
|
#
|
|
623
992
|
# User.where.not(name: "Jon")
|
|
624
993
|
# # SELECT * FROM users WHERE name != 'Jon'
|
|
625
994
|
#
|
|
626
|
-
#
|
|
995
|
+
# Chaining with WhereChain#associated:
|
|
996
|
+
#
|
|
997
|
+
# Post.where.associated(:author)
|
|
998
|
+
# # SELECT "posts".* FROM "posts"
|
|
999
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
1000
|
+
# # WHERE "authors"."id" IS NOT NULL
|
|
1001
|
+
#
|
|
1002
|
+
# Chaining with WhereChain#missing:
|
|
627
1003
|
#
|
|
628
|
-
#
|
|
1004
|
+
# Post.where.missing(:author)
|
|
1005
|
+
# # SELECT "posts".* FROM "posts"
|
|
1006
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
1007
|
+
# # WHERE "authors"."id" IS NULL
|
|
1008
|
+
#
|
|
1009
|
+
# === Blank Condition
|
|
629
1010
|
#
|
|
630
1011
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
|
631
1012
|
# the current relation.
|
|
632
|
-
def where(
|
|
633
|
-
if
|
|
1013
|
+
def where(*args)
|
|
1014
|
+
if args.empty?
|
|
634
1015
|
WhereChain.new(spawn)
|
|
635
|
-
elsif
|
|
1016
|
+
elsif args.length == 1 && args.first.blank?
|
|
636
1017
|
self
|
|
637
1018
|
else
|
|
638
|
-
spawn.where!(
|
|
1019
|
+
spawn.where!(*args)
|
|
639
1020
|
end
|
|
640
1021
|
end
|
|
641
1022
|
|
|
642
1023
|
def where!(opts, *rest) # :nodoc:
|
|
643
|
-
|
|
644
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
|
645
|
-
self.where_clause += where_clause_factory.build(opts, rest)
|
|
1024
|
+
self.where_clause += build_where_clause(opts, rest)
|
|
646
1025
|
self
|
|
647
1026
|
end
|
|
648
1027
|
|
|
@@ -660,7 +1039,99 @@ module ActiveRecord
|
|
|
660
1039
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
|
661
1040
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
|
662
1041
|
def rewhere(conditions)
|
|
663
|
-
unscope(where
|
|
1042
|
+
return unscope(:where) if conditions.nil?
|
|
1043
|
+
|
|
1044
|
+
scope = spawn
|
|
1045
|
+
where_clause = scope.build_where_clause(conditions)
|
|
1046
|
+
|
|
1047
|
+
scope.unscope!(where: where_clause.extract_attributes)
|
|
1048
|
+
scope.where_clause += where_clause
|
|
1049
|
+
scope
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
# Allows you to invert an entire where clause instead of manually applying conditions.
|
|
1053
|
+
#
|
|
1054
|
+
# class User
|
|
1055
|
+
# scope :active, -> { where(accepted: true, locked: false) }
|
|
1056
|
+
# end
|
|
1057
|
+
#
|
|
1058
|
+
# User.where(accepted: true)
|
|
1059
|
+
# # WHERE `accepted` = 1
|
|
1060
|
+
#
|
|
1061
|
+
# User.where(accepted: true).invert_where
|
|
1062
|
+
# # WHERE `accepted` != 1
|
|
1063
|
+
#
|
|
1064
|
+
# User.active
|
|
1065
|
+
# # WHERE `accepted` = 1 AND `locked` = 0
|
|
1066
|
+
#
|
|
1067
|
+
# User.active.invert_where
|
|
1068
|
+
# # WHERE NOT (`accepted` = 1 AND `locked` = 0)
|
|
1069
|
+
#
|
|
1070
|
+
# Be careful because this inverts all conditions before +invert_where+ call.
|
|
1071
|
+
#
|
|
1072
|
+
# class User
|
|
1073
|
+
# scope :active, -> { where(accepted: true, locked: false) }
|
|
1074
|
+
# scope :inactive, -> { active.invert_where } # Do not attempt it
|
|
1075
|
+
# end
|
|
1076
|
+
#
|
|
1077
|
+
# # It also inverts `where(role: 'admin')` unexpectedly.
|
|
1078
|
+
# User.where(role: 'admin').inactive
|
|
1079
|
+
# # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
|
|
1080
|
+
#
|
|
1081
|
+
def invert_where
|
|
1082
|
+
spawn.invert_where!
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
def invert_where! # :nodoc:
|
|
1086
|
+
self.where_clause = where_clause.invert
|
|
1087
|
+
self
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
# Checks whether the given relation is structurally compatible with this relation, to determine
|
|
1091
|
+
# if it's possible to use the #and and #or methods without raising an error. Structurally
|
|
1092
|
+
# compatible is defined as: they must be scoping the same model, and they must differ only by
|
|
1093
|
+
# #where (if no #group has been defined) or #having (if a #group is present).
|
|
1094
|
+
#
|
|
1095
|
+
# Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
|
|
1096
|
+
# # => true
|
|
1097
|
+
#
|
|
1098
|
+
# Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
|
|
1099
|
+
# # => false
|
|
1100
|
+
#
|
|
1101
|
+
def structurally_compatible?(other)
|
|
1102
|
+
structurally_incompatible_values_for(other).empty?
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
# Returns a new relation, which is the logical intersection of this relation and the one passed
|
|
1106
|
+
# as an argument.
|
|
1107
|
+
#
|
|
1108
|
+
# The two relations must be structurally compatible: they must be scoping the same model, and
|
|
1109
|
+
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
|
1110
|
+
# present).
|
|
1111
|
+
#
|
|
1112
|
+
# Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
|
|
1113
|
+
# # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
|
|
1114
|
+
#
|
|
1115
|
+
def and(other)
|
|
1116
|
+
if other.is_a?(Relation)
|
|
1117
|
+
spawn.and!(other)
|
|
1118
|
+
else
|
|
1119
|
+
raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
|
|
1120
|
+
end
|
|
1121
|
+
end
|
|
1122
|
+
|
|
1123
|
+
def and!(other) # :nodoc:
|
|
1124
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
|
1125
|
+
|
|
1126
|
+
unless incompatible_values.empty?
|
|
1127
|
+
raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1130
|
+
self.where_clause |= other.where_clause
|
|
1131
|
+
self.having_clause |= other.having_clause
|
|
1132
|
+
self.references_values |= other.references_values
|
|
1133
|
+
|
|
1134
|
+
self
|
|
664
1135
|
end
|
|
665
1136
|
|
|
666
1137
|
# Returns a new relation, which is the logical union of this relation and the one passed as an
|
|
@@ -668,29 +1139,33 @@ module ActiveRecord
|
|
|
668
1139
|
#
|
|
669
1140
|
# The two relations must be structurally compatible: they must be scoping the same model, and
|
|
670
1141
|
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
|
671
|
-
# present).
|
|
1142
|
+
# present).
|
|
672
1143
|
#
|
|
673
1144
|
# Post.where("id = 1").or(Post.where("author_id = 3"))
|
|
674
1145
|
# # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
|
|
675
1146
|
#
|
|
676
1147
|
def or(other)
|
|
677
|
-
|
|
1148
|
+
if other.is_a?(Relation)
|
|
1149
|
+
if @none
|
|
1150
|
+
other.spawn
|
|
1151
|
+
else
|
|
1152
|
+
spawn.or!(other)
|
|
1153
|
+
end
|
|
1154
|
+
else
|
|
678
1155
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
|
679
1156
|
end
|
|
680
|
-
|
|
681
|
-
spawn.or!(other)
|
|
682
1157
|
end
|
|
683
1158
|
|
|
684
1159
|
def or!(other) # :nodoc:
|
|
685
|
-
incompatible_values =
|
|
1160
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
|
686
1161
|
|
|
687
1162
|
unless incompatible_values.empty?
|
|
688
1163
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
|
689
1164
|
end
|
|
690
1165
|
|
|
691
|
-
self.where_clause =
|
|
1166
|
+
self.where_clause = where_clause.or(other.where_clause)
|
|
692
1167
|
self.having_clause = having_clause.or(other.having_clause)
|
|
693
|
-
self.references_values
|
|
1168
|
+
self.references_values |= other.references_values
|
|
694
1169
|
|
|
695
1170
|
self
|
|
696
1171
|
end
|
|
@@ -704,10 +1179,7 @@ module ActiveRecord
|
|
|
704
1179
|
end
|
|
705
1180
|
|
|
706
1181
|
def having!(opts, *rest) # :nodoc:
|
|
707
|
-
|
|
708
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
|
709
|
-
|
|
710
|
-
self.having_clause += having_clause_factory.build(opts, rest)
|
|
1182
|
+
self.having_clause += build_having_clause(opts, rest)
|
|
711
1183
|
self
|
|
712
1184
|
end
|
|
713
1185
|
|
|
@@ -791,15 +1263,29 @@ module ActiveRecord
|
|
|
791
1263
|
end
|
|
792
1264
|
|
|
793
1265
|
def none! # :nodoc:
|
|
794
|
-
|
|
1266
|
+
unless @none
|
|
1267
|
+
where!("1=0")
|
|
1268
|
+
@none = true
|
|
1269
|
+
end
|
|
1270
|
+
self
|
|
795
1271
|
end
|
|
796
1272
|
|
|
797
|
-
|
|
798
|
-
|
|
1273
|
+
def null_relation? # :nodoc:
|
|
1274
|
+
@none
|
|
1275
|
+
end
|
|
1276
|
+
|
|
1277
|
+
# Mark a relation as readonly. Attempting to update a record will result in
|
|
1278
|
+
# an error.
|
|
799
1279
|
#
|
|
800
1280
|
# users = User.readonly
|
|
801
1281
|
# users.first.save
|
|
802
|
-
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
|
1282
|
+
# # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
|
1283
|
+
#
|
|
1284
|
+
# To make a readonly relation writable, pass +false+.
|
|
1285
|
+
#
|
|
1286
|
+
# users.readonly(false)
|
|
1287
|
+
# users.first.save
|
|
1288
|
+
# # => true
|
|
803
1289
|
def readonly(value = true)
|
|
804
1290
|
spawn.readonly!(value)
|
|
805
1291
|
end
|
|
@@ -809,6 +1295,21 @@ module ActiveRecord
|
|
|
809
1295
|
self
|
|
810
1296
|
end
|
|
811
1297
|
|
|
1298
|
+
# Sets the returned relation to strict_loading mode. This will raise an error
|
|
1299
|
+
# if the record tries to lazily load an association.
|
|
1300
|
+
#
|
|
1301
|
+
# user = User.strict_loading.first
|
|
1302
|
+
# user.comments.to_a
|
|
1303
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
|
1304
|
+
def strict_loading(value = true)
|
|
1305
|
+
spawn.strict_loading!(value)
|
|
1306
|
+
end
|
|
1307
|
+
|
|
1308
|
+
def strict_loading!(value = true) # :nodoc:
|
|
1309
|
+
self.strict_loading_value = value
|
|
1310
|
+
self
|
|
1311
|
+
end
|
|
1312
|
+
|
|
812
1313
|
# Sets attributes to be used when creating new records from a
|
|
813
1314
|
# relation object.
|
|
814
1315
|
#
|
|
@@ -837,7 +1338,7 @@ module ActiveRecord
|
|
|
837
1338
|
self
|
|
838
1339
|
end
|
|
839
1340
|
|
|
840
|
-
# Specifies table from which the records will be fetched. For example:
|
|
1341
|
+
# Specifies the table from which the records will be fetched. For example:
|
|
841
1342
|
#
|
|
842
1343
|
# Topic.select('title').from('posts')
|
|
843
1344
|
# # SELECT title FROM posts
|
|
@@ -847,9 +1348,26 @@ module ActiveRecord
|
|
|
847
1348
|
# Topic.select('title').from(Topic.approved)
|
|
848
1349
|
# # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
|
|
849
1350
|
#
|
|
1351
|
+
# Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
|
|
1352
|
+
#
|
|
850
1353
|
# Topic.select('a.title').from(Topic.approved, :a)
|
|
851
1354
|
# # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
|
|
852
1355
|
#
|
|
1356
|
+
# It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
|
|
1357
|
+
#
|
|
1358
|
+
# Topic.select('title').from(Topic.approved).from(Topic.inactive)
|
|
1359
|
+
# # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
|
|
1360
|
+
#
|
|
1361
|
+
# For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
|
|
1362
|
+
#
|
|
1363
|
+
# color = "red"
|
|
1364
|
+
# Color
|
|
1365
|
+
# .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
|
|
1366
|
+
# .where("colorvalue->>'color' = ?", color)
|
|
1367
|
+
# .select("c.*").to_a
|
|
1368
|
+
# # SELECT c.*
|
|
1369
|
+
# # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
|
|
1370
|
+
# # WHERE (colorvalue->>'color' = 'red')
|
|
853
1371
|
def from(value, subquery_name = nil)
|
|
854
1372
|
spawn.from!(value, subquery_name)
|
|
855
1373
|
end
|
|
@@ -884,7 +1402,7 @@ module ActiveRecord
|
|
|
884
1402
|
#
|
|
885
1403
|
# The object returned is a relation, which can be further extended.
|
|
886
1404
|
#
|
|
887
|
-
# === Using a
|
|
1405
|
+
# === Using a \Module
|
|
888
1406
|
#
|
|
889
1407
|
# module Pagination
|
|
890
1408
|
# def page(number)
|
|
@@ -899,7 +1417,7 @@ module ActiveRecord
|
|
|
899
1417
|
#
|
|
900
1418
|
# scope = Model.all.extending(Pagination, SomethingElse)
|
|
901
1419
|
#
|
|
902
|
-
# === Using a
|
|
1420
|
+
# === Using a Block
|
|
903
1421
|
#
|
|
904
1422
|
# scope = Model.all.extending do
|
|
905
1423
|
# def page(number)
|
|
@@ -945,13 +1463,11 @@ module ActiveRecord
|
|
|
945
1463
|
# Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
|
|
946
1464
|
# # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
|
|
947
1465
|
def optimizer_hints(*args)
|
|
948
|
-
check_if_method_has_arguments!(
|
|
1466
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
949
1467
|
spawn.optimizer_hints!(*args)
|
|
950
1468
|
end
|
|
951
1469
|
|
|
952
1470
|
def optimizer_hints!(*args) # :nodoc:
|
|
953
|
-
args.flatten!
|
|
954
|
-
|
|
955
1471
|
self.optimizer_hints_values |= args
|
|
956
1472
|
self
|
|
957
1473
|
end
|
|
@@ -964,8 +1480,7 @@ module ActiveRecord
|
|
|
964
1480
|
end
|
|
965
1481
|
|
|
966
1482
|
def reverse_order! # :nodoc:
|
|
967
|
-
orders = order_values.
|
|
968
|
-
orders.reject!(&:blank?)
|
|
1483
|
+
orders = order_values.compact_blank
|
|
969
1484
|
self.order_values = reverse_sql_order(orders)
|
|
970
1485
|
self
|
|
971
1486
|
end
|
|
@@ -989,8 +1504,10 @@ module ActiveRecord
|
|
|
989
1504
|
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
|
990
1505
|
#
|
|
991
1506
|
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
|
1507
|
+
#
|
|
1508
|
+
# Some escaping is performed, however untrusted user input should not be used.
|
|
992
1509
|
def annotate(*args)
|
|
993
|
-
check_if_method_has_arguments!(
|
|
1510
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
994
1511
|
spawn.annotate!(*args)
|
|
995
1512
|
end
|
|
996
1513
|
|
|
@@ -1000,9 +1517,62 @@ module ActiveRecord
|
|
|
1000
1517
|
self
|
|
1001
1518
|
end
|
|
1002
1519
|
|
|
1520
|
+
# Deduplicate multiple values.
|
|
1521
|
+
def uniq!(name)
|
|
1522
|
+
if values = @values[name]
|
|
1523
|
+
values.uniq! if values.is_a?(Array) && !values.empty?
|
|
1524
|
+
end
|
|
1525
|
+
self
|
|
1526
|
+
end
|
|
1527
|
+
|
|
1528
|
+
# Excludes the specified record (or collection of records) from the resulting
|
|
1529
|
+
# relation. For example:
|
|
1530
|
+
#
|
|
1531
|
+
# Post.excluding(post)
|
|
1532
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
|
|
1533
|
+
#
|
|
1534
|
+
# Post.excluding(post_one, post_two)
|
|
1535
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
|
|
1536
|
+
#
|
|
1537
|
+
# Post.excluding(Post.drafts)
|
|
1538
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
|
|
1539
|
+
#
|
|
1540
|
+
# This can also be called on associations. As with the above example, either
|
|
1541
|
+
# a single record of collection thereof may be specified:
|
|
1542
|
+
#
|
|
1543
|
+
# post = Post.find(1)
|
|
1544
|
+
# comment = Comment.find(2)
|
|
1545
|
+
# post.comments.excluding(comment)
|
|
1546
|
+
# # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
|
|
1547
|
+
#
|
|
1548
|
+
# This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
|
|
1549
|
+
#
|
|
1550
|
+
# An <tt>ArgumentError</tt> will be raised if either no records are
|
|
1551
|
+
# specified, or if any of the records in the collection (if a collection
|
|
1552
|
+
# is passed in) are not instances of the same model that the relation is
|
|
1553
|
+
# scoping.
|
|
1554
|
+
def excluding(*records)
|
|
1555
|
+
relations = records.extract! { |element| element.is_a?(Relation) }
|
|
1556
|
+
records.flatten!(1)
|
|
1557
|
+
records.compact!
|
|
1558
|
+
|
|
1559
|
+
unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
|
|
1560
|
+
raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
|
|
1561
|
+
end
|
|
1562
|
+
|
|
1563
|
+
spawn.excluding!(records + relations.flat_map(&:ids))
|
|
1564
|
+
end
|
|
1565
|
+
alias :without :excluding
|
|
1566
|
+
|
|
1567
|
+
def excluding!(records) # :nodoc:
|
|
1568
|
+
predicates = [ predicate_builder[primary_key, records].invert ]
|
|
1569
|
+
self.where_clause += Relation::WhereClause.new(predicates)
|
|
1570
|
+
self
|
|
1571
|
+
end
|
|
1572
|
+
|
|
1003
1573
|
# Returns the Arel object associated with the relation.
|
|
1004
1574
|
def arel(aliases = nil) # :nodoc:
|
|
1005
|
-
@arel ||= build_arel(aliases)
|
|
1575
|
+
@arel ||= with_connection { |c| build_arel(c, aliases) }
|
|
1006
1576
|
end
|
|
1007
1577
|
|
|
1008
1578
|
def construct_join_dependency(associations, join_type) # :nodoc:
|
|
@@ -1020,54 +1590,180 @@ module ActiveRecord
|
|
|
1020
1590
|
end
|
|
1021
1591
|
end
|
|
1022
1592
|
|
|
1593
|
+
def build_where_clause(opts, rest = []) # :nodoc:
|
|
1594
|
+
opts = sanitize_forbidden_attributes(opts)
|
|
1595
|
+
|
|
1596
|
+
if opts.is_a?(Array)
|
|
1597
|
+
opts, *rest = opts
|
|
1598
|
+
end
|
|
1599
|
+
|
|
1600
|
+
case opts
|
|
1601
|
+
when String
|
|
1602
|
+
if rest.empty?
|
|
1603
|
+
parts = [Arel.sql(opts)]
|
|
1604
|
+
elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
|
|
1605
|
+
parts = [build_named_bound_sql_literal(opts, rest.first)]
|
|
1606
|
+
elsif opts.include?("?")
|
|
1607
|
+
parts = [build_bound_sql_literal(opts, rest)]
|
|
1608
|
+
else
|
|
1609
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
|
1610
|
+
end
|
|
1611
|
+
when Hash
|
|
1612
|
+
opts = opts.transform_keys do |key|
|
|
1613
|
+
if key.is_a?(Array)
|
|
1614
|
+
key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
|
|
1615
|
+
else
|
|
1616
|
+
key = key.to_s
|
|
1617
|
+
klass.attribute_aliases[key] || key
|
|
1618
|
+
end
|
|
1619
|
+
end
|
|
1620
|
+
references = PredicateBuilder.references(opts)
|
|
1621
|
+
self.references_values |= references unless references.empty?
|
|
1622
|
+
|
|
1623
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
|
1624
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
|
1625
|
+
end
|
|
1626
|
+
when Arel::Nodes::Node
|
|
1627
|
+
parts = [opts]
|
|
1628
|
+
else
|
|
1629
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
|
1630
|
+
end
|
|
1631
|
+
|
|
1632
|
+
Relation::WhereClause.new(parts)
|
|
1633
|
+
end
|
|
1634
|
+
alias :build_having_clause :build_where_clause
|
|
1635
|
+
|
|
1636
|
+
def async!
|
|
1637
|
+
@async = true
|
|
1638
|
+
self
|
|
1639
|
+
end
|
|
1640
|
+
|
|
1641
|
+
protected
|
|
1642
|
+
def arel_columns(columns)
|
|
1643
|
+
columns.flat_map do |field|
|
|
1644
|
+
case field
|
|
1645
|
+
when Symbol
|
|
1646
|
+
arel_column(field.to_s) do |attr_name|
|
|
1647
|
+
adapter_class.quote_table_name(attr_name)
|
|
1648
|
+
end
|
|
1649
|
+
when String
|
|
1650
|
+
arel_column(field, &:itself)
|
|
1651
|
+
when Proc
|
|
1652
|
+
field.call
|
|
1653
|
+
when Hash
|
|
1654
|
+
arel_columns_from_hash(field)
|
|
1655
|
+
else
|
|
1656
|
+
field
|
|
1657
|
+
end
|
|
1658
|
+
end
|
|
1659
|
+
end
|
|
1660
|
+
|
|
1023
1661
|
private
|
|
1024
|
-
def
|
|
1025
|
-
|
|
1026
|
-
raise ImmutableRelation if defined?(@arel) && @arel
|
|
1662
|
+
def async
|
|
1663
|
+
spawn.async!
|
|
1027
1664
|
end
|
|
1028
1665
|
|
|
1029
|
-
def
|
|
1030
|
-
|
|
1666
|
+
def build_named_bound_sql_literal(statement, values)
|
|
1667
|
+
bound_values = values.transform_values do |value|
|
|
1668
|
+
if ActiveRecord::Relation === value
|
|
1669
|
+
Arel.sql(value.to_sql)
|
|
1670
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
|
1671
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
|
1672
|
+
values.empty? ? nil : values
|
|
1673
|
+
else
|
|
1674
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
|
1675
|
+
value
|
|
1676
|
+
end
|
|
1677
|
+
end
|
|
1031
1678
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1679
|
+
begin
|
|
1680
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
|
|
1681
|
+
rescue Arel::BindError => error
|
|
1682
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
|
1036
1683
|
end
|
|
1684
|
+
end
|
|
1037
1685
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1686
|
+
def build_bound_sql_literal(statement, values)
|
|
1687
|
+
bound_values = values.map do |value|
|
|
1688
|
+
if ActiveRecord::Relation === value
|
|
1689
|
+
Arel.sql(value.to_sql)
|
|
1690
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
|
1691
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
|
1692
|
+
values.empty? ? nil : values
|
|
1693
|
+
else
|
|
1694
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
|
1695
|
+
value
|
|
1696
|
+
end
|
|
1047
1697
|
end
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
)
|
|
1054
|
-
arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
|
|
1698
|
+
|
|
1699
|
+
begin
|
|
1700
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
|
|
1701
|
+
rescue Arel::BindError => error
|
|
1702
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
|
1055
1703
|
end
|
|
1056
|
-
|
|
1704
|
+
end
|
|
1057
1705
|
|
|
1058
|
-
|
|
1706
|
+
def lookup_table_klass_from_join_dependencies(table_name)
|
|
1707
|
+
each_join_dependencies do |join|
|
|
1708
|
+
return join.base_klass if table_name == join.table_name
|
|
1709
|
+
end
|
|
1710
|
+
nil
|
|
1711
|
+
end
|
|
1059
1712
|
|
|
1713
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
|
|
1714
|
+
join_dependencies.each do |join_dependency|
|
|
1715
|
+
join_dependency.each(&block)
|
|
1716
|
+
end
|
|
1717
|
+
end
|
|
1718
|
+
|
|
1719
|
+
def build_join_dependencies
|
|
1720
|
+
joins = joins_values | left_outer_joins_values
|
|
1721
|
+
joins |= eager_load_values unless eager_load_values.empty?
|
|
1722
|
+
joins |= includes_values unless includes_values.empty?
|
|
1723
|
+
|
|
1724
|
+
join_dependencies = []
|
|
1725
|
+
join_dependencies.unshift construct_join_dependency(
|
|
1726
|
+
select_named_joins(joins, join_dependencies), nil
|
|
1727
|
+
)
|
|
1728
|
+
end
|
|
1729
|
+
|
|
1730
|
+
def assert_modifiable!
|
|
1731
|
+
raise UnmodifiableRelation if @loaded || @arel
|
|
1732
|
+
end
|
|
1733
|
+
|
|
1734
|
+
def build_arel(connection, aliases = nil)
|
|
1735
|
+
arel = Arel::SelectManager.new(table)
|
|
1736
|
+
|
|
1737
|
+
build_joins(arel.join_sources, aliases)
|
|
1738
|
+
|
|
1739
|
+
arel.where(where_clause.ast) unless where_clause.empty?
|
|
1740
|
+
arel.having(having_clause.ast) unless having_clause.empty?
|
|
1741
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
|
1742
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
|
1743
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
|
1744
|
+
|
|
1745
|
+
build_order(arel)
|
|
1746
|
+
build_with(arel)
|
|
1060
1747
|
build_select(arel)
|
|
1061
1748
|
|
|
1062
1749
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
|
1063
1750
|
arel.distinct(distinct_value)
|
|
1064
1751
|
arel.from(build_from) unless from_clause.empty?
|
|
1065
1752
|
arel.lock(lock_value) if lock_value
|
|
1066
|
-
|
|
1753
|
+
|
|
1754
|
+
unless annotate_values.empty?
|
|
1755
|
+
annotates = annotate_values
|
|
1756
|
+
annotates = annotates.uniq if annotates.size > 1
|
|
1757
|
+
arel.comment(*annotates)
|
|
1758
|
+
end
|
|
1067
1759
|
|
|
1068
1760
|
arel
|
|
1069
1761
|
end
|
|
1070
1762
|
|
|
1763
|
+
def build_cast_value(name, value)
|
|
1764
|
+
ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
|
1765
|
+
end
|
|
1766
|
+
|
|
1071
1767
|
def build_from
|
|
1072
1768
|
opts = from_clause.value
|
|
1073
1769
|
name = from_clause.name
|
|
@@ -1083,127 +1779,195 @@ module ActiveRecord
|
|
|
1083
1779
|
end
|
|
1084
1780
|
end
|
|
1085
1781
|
|
|
1086
|
-
def
|
|
1782
|
+
def select_named_joins(join_names, stashed_joins = nil, &block)
|
|
1783
|
+
cte_joins, associations = join_names.partition do |join_name|
|
|
1784
|
+
Symbol === join_name && with_values.any? { _1.key?(join_name) }
|
|
1785
|
+
end
|
|
1786
|
+
|
|
1787
|
+
cte_joins.each do |cte_name|
|
|
1788
|
+
block&.call(CTEJoin.new(cte_name))
|
|
1789
|
+
end
|
|
1790
|
+
|
|
1791
|
+
select_association_list(associations, stashed_joins, &block)
|
|
1792
|
+
end
|
|
1793
|
+
|
|
1794
|
+
def select_association_list(associations, stashed_joins = nil)
|
|
1795
|
+
result = []
|
|
1087
1796
|
associations.each do |association|
|
|
1088
1797
|
case association
|
|
1089
1798
|
when Hash, Symbol, Array
|
|
1090
|
-
|
|
1799
|
+
result << association
|
|
1800
|
+
when ActiveRecord::Associations::JoinDependency
|
|
1801
|
+
stashed_joins&.<< association
|
|
1091
1802
|
else
|
|
1092
|
-
|
|
1803
|
+
yield association if block_given?
|
|
1093
1804
|
end
|
|
1094
1805
|
end
|
|
1806
|
+
result
|
|
1095
1807
|
end
|
|
1096
1808
|
|
|
1097
|
-
def
|
|
1098
|
-
buckets = Hash.new { |h, k| h[k] = [] }
|
|
1099
|
-
buckets[:association_join] = valid_association_list(outer_joins)
|
|
1100
|
-
build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
|
|
1101
|
-
end
|
|
1102
|
-
|
|
1103
|
-
def build_joins(manager, joins, aliases)
|
|
1809
|
+
def build_join_buckets
|
|
1104
1810
|
buckets = Hash.new { |h, k| h[k] = [] }
|
|
1105
1811
|
|
|
1106
1812
|
unless left_outer_joins_values.empty?
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1813
|
+
stashed_left_joins = []
|
|
1814
|
+
left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
|
|
1815
|
+
if left_join.is_a?(CTEJoin)
|
|
1816
|
+
buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
|
|
1817
|
+
else
|
|
1818
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
|
1819
|
+
end
|
|
1820
|
+
end
|
|
1110
1821
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1822
|
+
if joins_values.empty?
|
|
1823
|
+
buckets[:named_join] = left_joins
|
|
1824
|
+
buckets[:stashed_join] = stashed_left_joins
|
|
1825
|
+
return buckets, Arel::Nodes::OuterJoin
|
|
1114
1826
|
else
|
|
1115
|
-
|
|
1827
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
|
1116
1828
|
end
|
|
1117
|
-
end
|
|
1829
|
+
end
|
|
1830
|
+
|
|
1831
|
+
joins = joins_values.dup
|
|
1832
|
+
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
|
1833
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
|
1834
|
+
end
|
|
1835
|
+
|
|
1836
|
+
joins.each_with_index do |join, i|
|
|
1837
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
|
1838
|
+
end
|
|
1118
1839
|
|
|
1119
1840
|
while joins.first.is_a?(Arel::Nodes::Join)
|
|
1120
1841
|
join_node = joins.shift
|
|
1121
|
-
if join_node.is_a?(Arel::Nodes::
|
|
1842
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
|
1122
1843
|
buckets[:join_node] << join_node
|
|
1123
1844
|
else
|
|
1124
1845
|
buckets[:leading_join] << join_node
|
|
1125
1846
|
end
|
|
1126
1847
|
end
|
|
1127
1848
|
|
|
1128
|
-
joins
|
|
1129
|
-
|
|
1130
|
-
when Hash, Symbol, Array
|
|
1131
|
-
buckets[:association_join] << join
|
|
1132
|
-
when ActiveRecord::Associations::JoinDependency
|
|
1133
|
-
buckets[:stashed_join] << join
|
|
1134
|
-
when Arel::Nodes::Join
|
|
1849
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
|
1850
|
+
if join.is_a?(Arel::Nodes::Join)
|
|
1135
1851
|
buckets[:join_node] << join
|
|
1852
|
+
elsif join.is_a?(CTEJoin)
|
|
1853
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
|
1136
1854
|
else
|
|
1137
1855
|
raise "unknown class: %s" % join.class.name
|
|
1138
1856
|
end
|
|
1139
1857
|
end
|
|
1140
1858
|
|
|
1141
|
-
|
|
1859
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
|
1860
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
|
1861
|
+
|
|
1862
|
+
return buckets, Arel::Nodes::InnerJoin
|
|
1142
1863
|
end
|
|
1143
1864
|
|
|
1144
|
-
def
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1865
|
+
def build_joins(join_sources, aliases = nil)
|
|
1866
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
|
1867
|
+
|
|
1868
|
+
buckets, join_type = build_join_buckets
|
|
1869
|
+
|
|
1870
|
+
named_joins = buckets[:named_join]
|
|
1871
|
+
stashed_joins = buckets[:stashed_join]
|
|
1872
|
+
leading_joins = buckets[:leading_join]
|
|
1873
|
+
join_nodes = buckets[:join_node]
|
|
1149
1874
|
|
|
1150
|
-
join_sources = manager.join_sources
|
|
1151
1875
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
|
1152
1876
|
|
|
1153
|
-
unless
|
|
1877
|
+
unless named_joins.empty? && stashed_joins.empty?
|
|
1154
1878
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
|
1155
|
-
join_dependency = construct_join_dependency(
|
|
1156
|
-
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
|
|
1879
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
|
1880
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
|
1157
1881
|
end
|
|
1158
1882
|
|
|
1159
1883
|
join_sources.concat(join_nodes) unless join_nodes.empty?
|
|
1884
|
+
join_sources
|
|
1160
1885
|
end
|
|
1161
1886
|
|
|
1162
1887
|
def build_select(arel)
|
|
1163
1888
|
if select_values.any?
|
|
1164
|
-
arel.project(*arel_columns(select_values
|
|
1165
|
-
elsif klass.ignored_columns.any?
|
|
1166
|
-
arel.project(*klass.column_names.map { |field|
|
|
1889
|
+
arel.project(*arel_columns(select_values))
|
|
1890
|
+
elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
|
|
1891
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
|
1167
1892
|
else
|
|
1168
1893
|
arel.project(table[Arel.star])
|
|
1169
1894
|
end
|
|
1170
1895
|
end
|
|
1171
1896
|
|
|
1172
|
-
def
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1897
|
+
def build_with(arel)
|
|
1898
|
+
return if with_values.empty?
|
|
1899
|
+
|
|
1900
|
+
with_statements = with_values.map do |with_value|
|
|
1901
|
+
build_with_value_from_hash(with_value)
|
|
1902
|
+
end
|
|
1903
|
+
|
|
1904
|
+
@with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
|
|
1905
|
+
end
|
|
1906
|
+
|
|
1907
|
+
def build_with_value_from_hash(hash)
|
|
1908
|
+
hash.map do |name, value|
|
|
1909
|
+
Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
|
|
1910
|
+
end
|
|
1911
|
+
end
|
|
1912
|
+
|
|
1913
|
+
def build_with_expression_from_value(value, nested = false)
|
|
1914
|
+
case value
|
|
1915
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
|
1916
|
+
when ActiveRecord::Relation
|
|
1917
|
+
if nested
|
|
1918
|
+
value.arel.ast
|
|
1183
1919
|
else
|
|
1184
|
-
|
|
1920
|
+
value.arel
|
|
1921
|
+
end
|
|
1922
|
+
when Arel::SelectManager then value
|
|
1923
|
+
when Array
|
|
1924
|
+
return build_with_expression_from_value(value.first, false) if value.size == 1
|
|
1925
|
+
|
|
1926
|
+
parts = value.map do |query|
|
|
1927
|
+
build_with_expression_from_value(query, true)
|
|
1185
1928
|
end
|
|
1929
|
+
|
|
1930
|
+
parts.reduce do |result, value|
|
|
1931
|
+
Arel::Nodes::UnionAll.new(result, value)
|
|
1932
|
+
end
|
|
1933
|
+
else
|
|
1934
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
|
1186
1935
|
end
|
|
1187
1936
|
end
|
|
1188
1937
|
|
|
1938
|
+
def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
|
|
1939
|
+
with_table = Arel::Table.new(name)
|
|
1940
|
+
|
|
1941
|
+
table.join(with_table, kind).on(
|
|
1942
|
+
with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
|
|
1943
|
+
).join_sources.first
|
|
1944
|
+
end
|
|
1945
|
+
|
|
1189
1946
|
def arel_column(field)
|
|
1190
1947
|
field = klass.attribute_aliases[field] || field
|
|
1191
1948
|
from = from_clause.name || from_clause.value
|
|
1192
1949
|
|
|
1193
1950
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
|
1194
|
-
|
|
1951
|
+
table[field]
|
|
1952
|
+
elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
|
|
1953
|
+
self.references_values |= [Arel.sql(table, retryable: true)]
|
|
1954
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
|
1955
|
+
lookup_table_klass_from_join_dependencies(table)
|
|
1956
|
+
end
|
|
1195
1957
|
else
|
|
1196
1958
|
yield field
|
|
1197
1959
|
end
|
|
1198
1960
|
end
|
|
1199
1961
|
|
|
1200
1962
|
def table_name_matches?(from)
|
|
1201
|
-
|
|
1963
|
+
table_name = Regexp.escape(table.name)
|
|
1964
|
+
quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
|
|
1965
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
|
1202
1966
|
end
|
|
1203
1967
|
|
|
1204
1968
|
def reverse_sql_order(order_query)
|
|
1205
1969
|
if order_query.empty?
|
|
1206
|
-
return [
|
|
1970
|
+
return [table[primary_key].desc] if primary_key
|
|
1207
1971
|
raise IrreversibleOrderError,
|
|
1208
1972
|
"Relation has no current order and table has no primary key to be used as default order"
|
|
1209
1973
|
end
|
|
@@ -1214,6 +1978,8 @@ module ActiveRecord
|
|
|
1214
1978
|
o.desc
|
|
1215
1979
|
when Arel::Nodes::Ordering
|
|
1216
1980
|
o.reverse
|
|
1981
|
+
when Arel::Nodes::NodeExpression
|
|
1982
|
+
o.desc
|
|
1217
1983
|
when String
|
|
1218
1984
|
if does_not_support_reverse?(o)
|
|
1219
1985
|
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
|
@@ -1240,9 +2006,7 @@ module ActiveRecord
|
|
|
1240
2006
|
end
|
|
1241
2007
|
|
|
1242
2008
|
def build_order(arel)
|
|
1243
|
-
orders = order_values.
|
|
1244
|
-
orders.reject!(&:blank?)
|
|
1245
|
-
|
|
2009
|
+
orders = order_values.compact_blank
|
|
1246
2010
|
arel.order(*orders) unless orders.empty?
|
|
1247
2011
|
end
|
|
1248
2012
|
|
|
@@ -1253,7 +2017,9 @@ module ActiveRecord
|
|
|
1253
2017
|
args.each do |arg|
|
|
1254
2018
|
next unless arg.is_a?(Hash)
|
|
1255
2019
|
arg.each do |_key, value|
|
|
1256
|
-
|
|
2020
|
+
if value.is_a?(Hash)
|
|
2021
|
+
validate_order_args([value])
|
|
2022
|
+
elsif VALID_DIRECTIONS.exclude?(value)
|
|
1257
2023
|
raise ArgumentError,
|
|
1258
2024
|
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
|
1259
2025
|
end
|
|
@@ -1261,23 +2027,20 @@ module ActiveRecord
|
|
|
1261
2027
|
end
|
|
1262
2028
|
end
|
|
1263
2029
|
|
|
1264
|
-
def
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
klass.sanitize_sql_for_order(arg)
|
|
1268
|
-
end
|
|
1269
|
-
order_args.flatten!
|
|
2030
|
+
def flattened_args(args)
|
|
2031
|
+
args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
|
|
2032
|
+
end
|
|
1270
2033
|
|
|
2034
|
+
def preprocess_order_args(order_args)
|
|
1271
2035
|
@klass.disallow_raw_sql!(
|
|
1272
|
-
order_args
|
|
1273
|
-
permit:
|
|
2036
|
+
flattened_args(order_args),
|
|
2037
|
+
permit: model.adapter_class.column_name_with_order_matcher
|
|
1274
2038
|
)
|
|
1275
2039
|
|
|
1276
2040
|
validate_order_args(order_args)
|
|
1277
2041
|
|
|
1278
|
-
references = order_args
|
|
1279
|
-
|
|
1280
|
-
references!(references) if references.any?
|
|
2042
|
+
references = column_references(order_args)
|
|
2043
|
+
self.references_values |= references unless references.empty?
|
|
1281
2044
|
|
|
1282
2045
|
# if a symbol is given we prepend the quoted table name
|
|
1283
2046
|
order_args.map! do |arg|
|
|
@@ -1285,26 +2048,100 @@ module ActiveRecord
|
|
|
1285
2048
|
when Symbol
|
|
1286
2049
|
order_column(arg.to_s).asc
|
|
1287
2050
|
when Hash
|
|
1288
|
-
arg.map
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
2051
|
+
arg.map do |key, value|
|
|
2052
|
+
if value.is_a?(Hash)
|
|
2053
|
+
value.map do |field, dir|
|
|
2054
|
+
order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
|
|
2055
|
+
end
|
|
1292
2056
|
else
|
|
1293
|
-
|
|
2057
|
+
case key
|
|
2058
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
|
2059
|
+
key.public_send(value.downcase)
|
|
2060
|
+
else
|
|
2061
|
+
order_column(key.to_s).public_send(value.downcase)
|
|
2062
|
+
end
|
|
1294
2063
|
end
|
|
1295
|
-
|
|
2064
|
+
end
|
|
1296
2065
|
else
|
|
1297
2066
|
arg
|
|
1298
2067
|
end
|
|
1299
2068
|
end.flatten!
|
|
1300
2069
|
end
|
|
1301
2070
|
|
|
2071
|
+
def sanitize_order_arguments(order_args)
|
|
2072
|
+
order_args.map! do |arg|
|
|
2073
|
+
klass.sanitize_sql_for_order(arg)
|
|
2074
|
+
end
|
|
2075
|
+
end
|
|
2076
|
+
|
|
2077
|
+
def column_references(order_args)
|
|
2078
|
+
order_args.flat_map do |arg|
|
|
2079
|
+
case arg
|
|
2080
|
+
when String, Symbol
|
|
2081
|
+
extract_table_name_from(arg)
|
|
2082
|
+
when Hash
|
|
2083
|
+
arg
|
|
2084
|
+
.map do |key, value|
|
|
2085
|
+
case value
|
|
2086
|
+
when Hash
|
|
2087
|
+
key.to_s
|
|
2088
|
+
else
|
|
2089
|
+
extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
|
|
2090
|
+
end
|
|
2091
|
+
end
|
|
2092
|
+
when Arel::Attribute
|
|
2093
|
+
arg.relation.name
|
|
2094
|
+
when Arel::Nodes::Ordering
|
|
2095
|
+
if arg.expr.is_a?(Arel::Attribute)
|
|
2096
|
+
arg.expr.relation.name
|
|
2097
|
+
end
|
|
2098
|
+
end
|
|
2099
|
+
end.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
|
|
2100
|
+
end
|
|
2101
|
+
|
|
2102
|
+
def extract_table_name_from(string)
|
|
2103
|
+
string.match(/^\W?(\w+)\W?\./) && $1
|
|
2104
|
+
end
|
|
2105
|
+
|
|
1302
2106
|
def order_column(field)
|
|
1303
2107
|
arel_column(field) do |attr_name|
|
|
1304
2108
|
if attr_name == "count" && !group_values.empty?
|
|
1305
|
-
|
|
2109
|
+
table[attr_name]
|
|
1306
2110
|
else
|
|
1307
|
-
Arel.sql(
|
|
2111
|
+
Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
|
|
2112
|
+
end
|
|
2113
|
+
end
|
|
2114
|
+
end
|
|
2115
|
+
|
|
2116
|
+
def build_case_for_value_position(column, values)
|
|
2117
|
+
node = Arel::Nodes::Case.new
|
|
2118
|
+
values.each.with_index(1) do |value, order|
|
|
2119
|
+
node.when(column.eq(value)).then(order)
|
|
2120
|
+
end
|
|
2121
|
+
|
|
2122
|
+
Arel::Nodes::Ascending.new(node)
|
|
2123
|
+
end
|
|
2124
|
+
|
|
2125
|
+
def resolve_arel_attributes(attrs)
|
|
2126
|
+
attrs.flat_map do |attr|
|
|
2127
|
+
case attr
|
|
2128
|
+
when Arel::Predications
|
|
2129
|
+
attr
|
|
2130
|
+
when Hash
|
|
2131
|
+
attr.flat_map do |table, columns|
|
|
2132
|
+
table = table.to_s
|
|
2133
|
+
Array(columns).map do |column|
|
|
2134
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
|
2135
|
+
end
|
|
2136
|
+
end
|
|
2137
|
+
else
|
|
2138
|
+
attr = attr.to_s
|
|
2139
|
+
if attr.include?(".")
|
|
2140
|
+
table, column = attr.split(".", 2)
|
|
2141
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
|
2142
|
+
else
|
|
2143
|
+
attr
|
|
2144
|
+
end
|
|
1308
2145
|
end
|
|
1309
2146
|
end
|
|
1310
2147
|
end
|
|
@@ -1318,42 +2155,82 @@ module ActiveRecord
|
|
|
1318
2155
|
# Post.references() # raises an error
|
|
1319
2156
|
# Post.references([]) # does not raise an error
|
|
1320
2157
|
#
|
|
1321
|
-
# This particular method should be called with a method_name and the args
|
|
2158
|
+
# This particular method should be called with a method_name (__callee__) and the args
|
|
1322
2159
|
# passed into that method as an input. For example:
|
|
1323
2160
|
#
|
|
1324
2161
|
# def references(*args)
|
|
1325
|
-
# check_if_method_has_arguments!(
|
|
2162
|
+
# check_if_method_has_arguments!(__callee__, args)
|
|
1326
2163
|
# ...
|
|
1327
2164
|
# end
|
|
1328
|
-
def check_if_method_has_arguments!(method_name, args)
|
|
2165
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
|
1329
2166
|
if args.blank?
|
|
1330
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
|
2167
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
|
2168
|
+
else
|
|
2169
|
+
yield args if block_given?
|
|
2170
|
+
|
|
2171
|
+
args.flatten!
|
|
2172
|
+
args.compact_blank!
|
|
1331
2173
|
end
|
|
1332
2174
|
end
|
|
1333
2175
|
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
2176
|
+
def process_select_args(fields)
|
|
2177
|
+
fields.flat_map do |field|
|
|
2178
|
+
if field.is_a?(Hash)
|
|
2179
|
+
arel_columns_from_hash(field)
|
|
2180
|
+
else
|
|
2181
|
+
field
|
|
2182
|
+
end
|
|
1340
2183
|
end
|
|
1341
2184
|
end
|
|
1342
2185
|
|
|
1343
|
-
def
|
|
1344
|
-
|
|
2186
|
+
def arel_columns_from_hash(fields)
|
|
2187
|
+
fields.flat_map do |key, columns_aliases|
|
|
2188
|
+
case columns_aliases
|
|
2189
|
+
when Hash
|
|
2190
|
+
columns_aliases.map do |column, column_alias|
|
|
2191
|
+
if values[:joins]&.include?(key)
|
|
2192
|
+
references = PredicateBuilder.references({ key.to_s => fields[key] })
|
|
2193
|
+
self.references_values |= references unless references.empty?
|
|
2194
|
+
end
|
|
2195
|
+
arel_column("#{key}.#{column}") do
|
|
2196
|
+
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
|
2197
|
+
end.as(column_alias.to_s)
|
|
2198
|
+
end
|
|
2199
|
+
when Array
|
|
2200
|
+
columns_aliases.map do |column|
|
|
2201
|
+
arel_column("#{key}.#{column}", &:itself)
|
|
2202
|
+
end
|
|
2203
|
+
when String, Symbol
|
|
2204
|
+
arel_column(key.to_s) do
|
|
2205
|
+
predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
|
|
2206
|
+
end.as(columns_aliases.to_s)
|
|
2207
|
+
end
|
|
2208
|
+
end
|
|
1345
2209
|
end
|
|
1346
|
-
alias having_clause_factory where_clause_factory
|
|
1347
2210
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
2211
|
+
def process_with_args(args)
|
|
2212
|
+
args.flat_map do |arg|
|
|
2213
|
+
raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
|
|
2214
|
+
arg.map { |k, v| { k => v } }
|
|
2215
|
+
end
|
|
2216
|
+
end
|
|
2217
|
+
|
|
2218
|
+
STRUCTURAL_VALUE_METHODS = (
|
|
2219
|
+
Relation::VALUE_METHODS -
|
|
2220
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
|
2221
|
+
).freeze # :nodoc:
|
|
1354
2222
|
|
|
1355
|
-
|
|
1356
|
-
|
|
2223
|
+
def structurally_incompatible_values_for(other)
|
|
2224
|
+
values = other.values
|
|
2225
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
|
2226
|
+
v1, v2 = @values[method], values[method]
|
|
2227
|
+
if v1.is_a?(Array)
|
|
2228
|
+
next true unless v2.is_a?(Array)
|
|
2229
|
+
v1 = v1.uniq
|
|
2230
|
+
v2 = v2.uniq
|
|
2231
|
+
end
|
|
2232
|
+
v1 == v2
|
|
2233
|
+
end
|
|
1357
2234
|
end
|
|
1358
2235
|
end
|
|
1359
2236
|
end
|