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