activerecord 6.1.7 → 7.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +616 -1290
- 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 +19 -8
- 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 +30 -27
- data/lib/active_record/associations/join_dependency.rb +28 -20
- 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 +429 -522
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -5
- 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 +15 -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 +57 -54
- data/lib/active_record/autosave_association.rb +74 -57
- data/lib/active_record/base.rb +27 -5
- data/lib/active_record/callbacks.rb +19 -35
- 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 +325 -604
- 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 +230 -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 +378 -143
- 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 +348 -165
- 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 +403 -77
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
- 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 +310 -253
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
- 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 +58 -0
- data/lib/active_record/enum.rb +170 -62
- 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 +59 -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 +145 -158
- data/lib/active_record/nested_attributes.rb +61 -23
- 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 +18 -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 +229 -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 +332 -103
- data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
- data/lib/active_record/relation/batches.rb +200 -65
- data/lib/active_record/relation/calculations.rb +301 -112
- 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 +870 -163
- 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 +6 -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 +288 -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 +65 -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/sqlite.rb +25 -0
- 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 +103 -17
- 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.
|
122
200
|
#
|
123
|
-
#
|
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.
|
124
204
|
#
|
125
|
-
#
|
205
|
+
# You can also specify multiple associations. Each association will result
|
206
|
+
# in an additional query:
|
126
207
|
#
|
127
|
-
#
|
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)
|
128
212
|
#
|
129
|
-
#
|
213
|
+
# Loading nested associations is possible using a Hash:
|
130
214
|
#
|
131
|
-
#
|
215
|
+
# User.includes(:address, friends: [:address, :followers])
|
216
|
+
#
|
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.
|
295
|
+
#
|
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.
|
177
306
|
#
|
178
|
-
#
|
179
|
-
#
|
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
|
629
999
|
#
|
630
|
-
#
|
1000
|
+
# Chaining with WhereChain#missing:
|
1001
|
+
#
|
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
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
def null_relation? # :nodoc:
|
1272
|
+
@none
|
829
1273
|
end
|
830
1274
|
|
831
|
-
#
|
832
|
-
#
|
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,76 @@ 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
|
+
|
1639
|
+
protected
|
1640
|
+
def arel_columns(columns)
|
1641
|
+
columns.flat_map do |field|
|
1642
|
+
case field
|
1643
|
+
when Symbol
|
1644
|
+
arel_column(field.to_s) do |attr_name|
|
1645
|
+
adapter_class.quote_table_name(attr_name)
|
1646
|
+
end
|
1647
|
+
when String
|
1648
|
+
arel_column(field, &:itself)
|
1649
|
+
when Proc
|
1650
|
+
field.call
|
1651
|
+
when Hash
|
1652
|
+
arel_columns_from_hash(field)
|
1653
|
+
else
|
1654
|
+
field
|
1655
|
+
end
|
1656
|
+
end
|
1657
|
+
end
|
1658
|
+
|
1104
1659
|
private
|
1660
|
+
def async
|
1661
|
+
spawn.async!
|
1662
|
+
end
|
1663
|
+
|
1664
|
+
def build_named_bound_sql_literal(statement, values)
|
1665
|
+
bound_values = values.transform_values 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})", nil, bound_values)
|
1679
|
+
rescue Arel::BindError => error
|
1680
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
1681
|
+
end
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
def build_bound_sql_literal(statement, values)
|
1685
|
+
bound_values = values.map do |value|
|
1686
|
+
if ActiveRecord::Relation === value
|
1687
|
+
Arel.sql(value.to_sql)
|
1688
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
1689
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
1690
|
+
values.empty? ? nil : values
|
1691
|
+
else
|
1692
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
1693
|
+
value
|
1694
|
+
end
|
1695
|
+
end
|
1696
|
+
|
1697
|
+
begin
|
1698
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
|
1699
|
+
rescue Arel::BindError => error
|
1700
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
1701
|
+
end
|
1702
|
+
end
|
1703
|
+
|
1105
1704
|
def lookup_table_klass_from_join_dependencies(table_name)
|
1106
1705
|
each_join_dependencies do |join|
|
1107
1706
|
return join.base_klass if table_name == join.table_name
|
@@ -1109,31 +1708,28 @@ module ActiveRecord
|
|
1109
1708
|
nil
|
1110
1709
|
end
|
1111
1710
|
|
1112
|
-
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
1711
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
|
1113
1712
|
join_dependencies.each do |join_dependency|
|
1114
|
-
join_dependency.each
|
1115
|
-
yield join
|
1116
|
-
end
|
1713
|
+
join_dependency.each(&block)
|
1117
1714
|
end
|
1118
1715
|
end
|
1119
1716
|
|
1120
1717
|
def build_join_dependencies
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1718
|
+
joins = joins_values | left_outer_joins_values
|
1719
|
+
joins |= eager_load_values unless eager_load_values.empty?
|
1720
|
+
joins |= includes_values unless includes_values.empty?
|
1124
1721
|
|
1125
1722
|
join_dependencies = []
|
1126
1723
|
join_dependencies.unshift construct_join_dependency(
|
1127
|
-
|
1724
|
+
select_named_joins(joins, join_dependencies), nil
|
1128
1725
|
)
|
1129
1726
|
end
|
1130
1727
|
|
1131
|
-
def
|
1132
|
-
raise
|
1133
|
-
raise ImmutableRelation if defined?(@arel) && @arel
|
1728
|
+
def assert_modifiable!
|
1729
|
+
raise UnmodifiableRelation if @loaded || @arel
|
1134
1730
|
end
|
1135
1731
|
|
1136
|
-
def build_arel(aliases = nil)
|
1732
|
+
def build_arel(connection, aliases = nil)
|
1137
1733
|
arel = Arel::SelectManager.new(table)
|
1138
1734
|
|
1139
1735
|
build_joins(arel.join_sources, aliases)
|
@@ -1145,6 +1741,7 @@ module ActiveRecord
|
|
1145
1741
|
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1146
1742
|
|
1147
1743
|
build_order(arel)
|
1744
|
+
build_with(arel)
|
1148
1745
|
build_select(arel)
|
1149
1746
|
|
1150
1747
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
@@ -1155,14 +1752,6 @@ module ActiveRecord
|
|
1155
1752
|
unless annotate_values.empty?
|
1156
1753
|
annotates = annotate_values
|
1157
1754
|
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
1755
|
arel.comment(*annotates)
|
1167
1756
|
end
|
1168
1757
|
|
@@ -1170,8 +1759,7 @@ module ActiveRecord
|
|
1170
1759
|
end
|
1171
1760
|
|
1172
1761
|
def build_cast_value(name, value)
|
1173
|
-
|
1174
|
-
Arel::Nodes::BindParam.new(cast_value)
|
1762
|
+
ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1175
1763
|
end
|
1176
1764
|
|
1177
1765
|
def build_from
|
@@ -1189,6 +1777,18 @@ module ActiveRecord
|
|
1189
1777
|
end
|
1190
1778
|
end
|
1191
1779
|
|
1780
|
+
def select_named_joins(join_names, stashed_joins = nil, &block)
|
1781
|
+
cte_joins, associations = join_names.partition do |join_name|
|
1782
|
+
Symbol === join_name && with_values.any? { _1.key?(join_name) }
|
1783
|
+
end
|
1784
|
+
|
1785
|
+
cte_joins.each do |cte_name|
|
1786
|
+
block&.call(CTEJoin.new(cte_name))
|
1787
|
+
end
|
1788
|
+
|
1789
|
+
select_association_list(associations, stashed_joins, &block)
|
1790
|
+
end
|
1791
|
+
|
1192
1792
|
def select_association_list(associations, stashed_joins = nil)
|
1193
1793
|
result = []
|
1194
1794
|
associations.each do |association|
|
@@ -1204,20 +1804,21 @@ module ActiveRecord
|
|
1204
1804
|
result
|
1205
1805
|
end
|
1206
1806
|
|
1207
|
-
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1208
|
-
end
|
1209
|
-
|
1210
1807
|
def build_join_buckets
|
1211
1808
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1212
1809
|
|
1213
1810
|
unless left_outer_joins_values.empty?
|
1214
1811
|
stashed_left_joins = []
|
1215
|
-
left_joins =
|
1216
|
-
|
1812
|
+
left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
|
1813
|
+
if left_join.is_a?(CTEJoin)
|
1814
|
+
buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
|
1815
|
+
else
|
1816
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1817
|
+
end
|
1217
1818
|
end
|
1218
1819
|
|
1219
1820
|
if joins_values.empty?
|
1220
|
-
buckets[:
|
1821
|
+
buckets[:named_join] = left_joins
|
1221
1822
|
buckets[:stashed_join] = stashed_left_joins
|
1222
1823
|
return buckets, Arel::Nodes::OuterJoin
|
1223
1824
|
else
|
@@ -1243,9 +1844,11 @@ module ActiveRecord
|
|
1243
1844
|
end
|
1244
1845
|
end
|
1245
1846
|
|
1246
|
-
buckets[:
|
1847
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
1247
1848
|
if join.is_a?(Arel::Nodes::Join)
|
1248
1849
|
buckets[:join_node] << join
|
1850
|
+
elsif join.is_a?(CTEJoin)
|
1851
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
1249
1852
|
else
|
1250
1853
|
raise "unknown class: %s" % join.class.name
|
1251
1854
|
end
|
@@ -1262,16 +1865,16 @@ module ActiveRecord
|
|
1262
1865
|
|
1263
1866
|
buckets, join_type = build_join_buckets
|
1264
1867
|
|
1265
|
-
|
1266
|
-
stashed_joins
|
1267
|
-
leading_joins
|
1268
|
-
join_nodes
|
1868
|
+
named_joins = buckets[:named_join]
|
1869
|
+
stashed_joins = buckets[:stashed_join]
|
1870
|
+
leading_joins = buckets[:leading_join]
|
1871
|
+
join_nodes = buckets[:join_node]
|
1269
1872
|
|
1270
1873
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1271
1874
|
|
1272
|
-
unless
|
1875
|
+
unless named_joins.empty? && stashed_joins.empty?
|
1273
1876
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1274
|
-
join_dependency = construct_join_dependency(
|
1877
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
1275
1878
|
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1276
1879
|
end
|
1277
1880
|
|
@@ -1282,38 +1885,72 @@ module ActiveRecord
|
|
1282
1885
|
def build_select(arel)
|
1283
1886
|
if select_values.any?
|
1284
1887
|
arel.project(*arel_columns(select_values))
|
1285
|
-
elsif klass.ignored_columns.any?
|
1888
|
+
elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
|
1286
1889
|
arel.project(*klass.column_names.map { |field| table[field] })
|
1287
1890
|
else
|
1288
1891
|
arel.project(table[Arel.star])
|
1289
1892
|
end
|
1290
1893
|
end
|
1291
1894
|
|
1292
|
-
def
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1895
|
+
def build_with(arel)
|
1896
|
+
return if with_values.empty?
|
1897
|
+
|
1898
|
+
with_statements = with_values.map do |with_value|
|
1899
|
+
raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
|
1900
|
+
|
1901
|
+
build_with_value_from_hash(with_value)
|
1902
|
+
end
|
1903
|
+
|
1904
|
+
@with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
|
1905
|
+
end
|
1906
|
+
|
1907
|
+
def build_with_value_from_hash(hash)
|
1908
|
+
hash.map do |name, value|
|
1909
|
+
Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
|
1910
|
+
end
|
1911
|
+
end
|
1912
|
+
|
1913
|
+
def build_with_expression_from_value(value, nested = false)
|
1914
|
+
case value
|
1915
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
1916
|
+
when ActiveRecord::Relation
|
1917
|
+
if nested
|
1918
|
+
value.arel.ast
|
1303
1919
|
else
|
1304
|
-
|
1920
|
+
value.arel
|
1305
1921
|
end
|
1922
|
+
when Arel::SelectManager then value
|
1923
|
+
when Array
|
1924
|
+
return build_with_expression_from_value(value.first, false) if value.size == 1
|
1925
|
+
|
1926
|
+
parts = value.map do |query|
|
1927
|
+
build_with_expression_from_value(query, true)
|
1928
|
+
end
|
1929
|
+
|
1930
|
+
parts.reduce do |result, value|
|
1931
|
+
Arel::Nodes::UnionAll.new(result, value)
|
1932
|
+
end
|
1933
|
+
else
|
1934
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
1306
1935
|
end
|
1307
1936
|
end
|
1308
1937
|
|
1938
|
+
def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
|
1939
|
+
with_table = Arel::Table.new(name)
|
1940
|
+
|
1941
|
+
table.join(with_table, kind).on(
|
1942
|
+
with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
|
1943
|
+
).join_sources.first
|
1944
|
+
end
|
1945
|
+
|
1309
1946
|
def arel_column(field)
|
1310
1947
|
field = klass.attribute_aliases[field] || field
|
1311
1948
|
from = from_clause.name || from_clause.value
|
1312
1949
|
|
1313
1950
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1314
1951
|
table[field]
|
1315
|
-
elsif
|
1316
|
-
table,
|
1952
|
+
elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
|
1953
|
+
self.references_values |= [Arel.sql(table, retryable: true)]
|
1317
1954
|
predicate_builder.resolve_arel_attribute(table, column) do
|
1318
1955
|
lookup_table_klass_from_join_dependencies(table)
|
1319
1956
|
end
|
@@ -1324,7 +1961,7 @@ module ActiveRecord
|
|
1324
1961
|
|
1325
1962
|
def table_name_matches?(from)
|
1326
1963
|
table_name = Regexp.escape(table.name)
|
1327
|
-
quoted_table_name = Regexp.escape(
|
1964
|
+
quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
|
1328
1965
|
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1329
1966
|
end
|
1330
1967
|
|
@@ -1380,7 +2017,9 @@ module ActiveRecord
|
|
1380
2017
|
args.each do |arg|
|
1381
2018
|
next unless arg.is_a?(Hash)
|
1382
2019
|
arg.each do |_key, value|
|
1383
|
-
|
2020
|
+
if value.is_a?(Hash)
|
2021
|
+
validate_order_args([value])
|
2022
|
+
elsif VALID_DIRECTIONS.exclude?(value)
|
1384
2023
|
raise ArgumentError,
|
1385
2024
|
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
1386
2025
|
end
|
@@ -1388,10 +2027,14 @@ module ActiveRecord
|
|
1388
2027
|
end
|
1389
2028
|
end
|
1390
2029
|
|
2030
|
+
def flattened_args(args)
|
2031
|
+
args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
|
2032
|
+
end
|
2033
|
+
|
1391
2034
|
def preprocess_order_args(order_args)
|
1392
2035
|
@klass.disallow_raw_sql!(
|
1393
|
-
order_args
|
1394
|
-
permit:
|
2036
|
+
flattened_args(order_args),
|
2037
|
+
permit: model.adapter_class.column_name_with_order_matcher
|
1395
2038
|
)
|
1396
2039
|
|
1397
2040
|
validate_order_args(order_args)
|
@@ -1405,14 +2048,20 @@ module ActiveRecord
|
|
1405
2048
|
when Symbol
|
1406
2049
|
order_column(arg.to_s).asc
|
1407
2050
|
when Hash
|
1408
|
-
arg.map
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
2051
|
+
arg.map do |key, value|
|
2052
|
+
if value.is_a?(Hash)
|
2053
|
+
value.map do |field, dir|
|
2054
|
+
order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
|
2055
|
+
end
|
1412
2056
|
else
|
1413
|
-
|
2057
|
+
case key
|
2058
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
2059
|
+
key.public_send(value.downcase)
|
2060
|
+
else
|
2061
|
+
order_column(key.to_s).public_send(value.downcase)
|
2062
|
+
end
|
1414
2063
|
end
|
1415
|
-
|
2064
|
+
end
|
1416
2065
|
else
|
1417
2066
|
arg
|
1418
2067
|
end
|
@@ -1423,14 +2072,35 @@ module ActiveRecord
|
|
1423
2072
|
order_args.map! do |arg|
|
1424
2073
|
klass.sanitize_sql_for_order(arg)
|
1425
2074
|
end
|
1426
|
-
order_args.flatten!
|
1427
|
-
order_args.compact_blank!
|
1428
2075
|
end
|
1429
2076
|
|
1430
2077
|
def column_references(order_args)
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
2078
|
+
order_args.flat_map do |arg|
|
2079
|
+
case arg
|
2080
|
+
when String, Symbol
|
2081
|
+
extract_table_name_from(arg)
|
2082
|
+
when Hash
|
2083
|
+
arg
|
2084
|
+
.map do |key, value|
|
2085
|
+
case value
|
2086
|
+
when Hash
|
2087
|
+
key.to_s
|
2088
|
+
else
|
2089
|
+
extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
|
2090
|
+
end
|
2091
|
+
end
|
2092
|
+
when Arel::Attribute
|
2093
|
+
arg.relation.name
|
2094
|
+
when Arel::Nodes::Ordering
|
2095
|
+
if arg.expr.is_a?(Arel::Attribute)
|
2096
|
+
arg.expr.relation.name
|
2097
|
+
end
|
2098
|
+
end
|
2099
|
+
end.compact
|
2100
|
+
end
|
2101
|
+
|
2102
|
+
def extract_table_name_from(string)
|
2103
|
+
string.match(/^\W?(\w+)\W?\./) && $1
|
1434
2104
|
end
|
1435
2105
|
|
1436
2106
|
def order_column(field)
|
@@ -1438,11 +2108,20 @@ module ActiveRecord
|
|
1438
2108
|
if attr_name == "count" && !group_values.empty?
|
1439
2109
|
table[attr_name]
|
1440
2110
|
else
|
1441
|
-
Arel.sql(
|
2111
|
+
Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
|
1442
2112
|
end
|
1443
2113
|
end
|
1444
2114
|
end
|
1445
2115
|
|
2116
|
+
def build_case_for_value_position(column, values)
|
2117
|
+
node = Arel::Nodes::Case.new
|
2118
|
+
values.each.with_index(1) do |value, order|
|
2119
|
+
node.when(column.eq(value)).then(order)
|
2120
|
+
end
|
2121
|
+
|
2122
|
+
Arel::Nodes::Ascending.new(node)
|
2123
|
+
end
|
2124
|
+
|
1446
2125
|
def resolve_arel_attributes(attrs)
|
1447
2126
|
attrs.flat_map do |attr|
|
1448
2127
|
case attr
|
@@ -1476,24 +2155,59 @@ module ActiveRecord
|
|
1476
2155
|
# Post.references() # raises an error
|
1477
2156
|
# Post.references([]) # does not raise an error
|
1478
2157
|
#
|
1479
|
-
# This particular method should be called with a method_name and the args
|
2158
|
+
# This particular method should be called with a method_name (__callee__) and the args
|
1480
2159
|
# passed into that method as an input. For example:
|
1481
2160
|
#
|
1482
2161
|
# def references(*args)
|
1483
|
-
# check_if_method_has_arguments!(
|
2162
|
+
# check_if_method_has_arguments!(__callee__, args)
|
1484
2163
|
# ...
|
1485
2164
|
# end
|
1486
2165
|
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1487
2166
|
if args.blank?
|
1488
2167
|
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1489
|
-
elsif block_given?
|
1490
|
-
yield args
|
1491
2168
|
else
|
2169
|
+
yield args if block_given?
|
2170
|
+
|
1492
2171
|
args.flatten!
|
1493
2172
|
args.compact_blank!
|
1494
2173
|
end
|
1495
2174
|
end
|
1496
2175
|
|
2176
|
+
def process_select_args(fields)
|
2177
|
+
fields.flat_map do |field|
|
2178
|
+
if field.is_a?(Hash)
|
2179
|
+
arel_columns_from_hash(field)
|
2180
|
+
else
|
2181
|
+
field
|
2182
|
+
end
|
2183
|
+
end
|
2184
|
+
end
|
2185
|
+
|
2186
|
+
def arel_columns_from_hash(fields)
|
2187
|
+
fields.flat_map do |key, columns_aliases|
|
2188
|
+
case columns_aliases
|
2189
|
+
when Hash
|
2190
|
+
columns_aliases.map do |column, column_alias|
|
2191
|
+
if values[:joins]&.include?(key)
|
2192
|
+
references = PredicateBuilder.references({ key.to_s => fields[key] })
|
2193
|
+
self.references_values |= references unless references.empty?
|
2194
|
+
end
|
2195
|
+
arel_column("#{key}.#{column}") do
|
2196
|
+
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
2197
|
+
end.as(column_alias.to_s)
|
2198
|
+
end
|
2199
|
+
when Array
|
2200
|
+
columns_aliases.map do |column|
|
2201
|
+
arel_column("#{key}.#{column}", &:itself)
|
2202
|
+
end
|
2203
|
+
when String, Symbol
|
2204
|
+
arel_column(key.to_s) do
|
2205
|
+
predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
|
2206
|
+
end.as(columns_aliases.to_s)
|
2207
|
+
end
|
2208
|
+
end
|
2209
|
+
end
|
2210
|
+
|
1497
2211
|
STRUCTURAL_VALUE_METHODS = (
|
1498
2212
|
Relation::VALUE_METHODS -
|
1499
2213
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
@@ -1512,11 +2226,4 @@ module ActiveRecord
|
|
1512
2226
|
end
|
1513
2227
|
end
|
1514
2228
|
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
2229
|
end
|