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