activerecord 6.1.7 → 7.1.5
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 +2030 -1020
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_record/aggregations.rb +17 -14
- data/lib/active_record/association_relation.rb +1 -11
- data/lib/active_record/associations/association.rb +51 -19
- data/lib/active_record/associations/association_scope.rb +17 -12
- data/lib/active_record/associations/belongs_to_association.rb +28 -9
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +11 -5
- data/lib/active_record/associations/builder/belongs_to.rb +40 -14
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/has_many.rb +3 -2
- data/lib/active_record/associations/builder/has_one.rb +2 -1
- data/lib/active_record/associations/builder/singular_association.rb +6 -2
- data/lib/active_record/associations/collection_association.rb +39 -35
- data/lib/active_record/associations/collection_proxy.rb +30 -15
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +28 -18
- 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 +3 -2
- data/lib/active_record/associations/join_dependency.rb +28 -20
- data/lib/active_record/associations/preloader/association.rb +210 -52
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +147 -0
- data/lib/active_record/associations/preloader/through_association.rb +50 -14
- data/lib/active_record/associations/preloader.rb +50 -121
- data/lib/active_record/associations/singular_association.rb +9 -3
- data/lib/active_record/associations/through_association.rb +25 -14
- data/lib/active_record/associations.rb +446 -306
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
- data/lib/active_record/attribute_methods/dirty.rb +73 -22
- data/lib/active_record/attribute_methods/primary_key.rb +78 -26
- data/lib/active_record/attribute_methods/query.rb +31 -19
- data/lib/active_record/attribute_methods/read.rb +27 -12
- data/lib/active_record/attribute_methods/serialization.rb +194 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
- data/lib/active_record/attribute_methods/write.rb +12 -15
- data/lib/active_record/attribute_methods.rb +161 -40
- data/lib/active_record/attributes.rb +27 -38
- data/lib/active_record/autosave_association.rb +65 -31
- data/lib/active_record/base.rb +25 -2
- 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 +367 -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 +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +113 -597
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
- 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 +367 -141
- data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
- data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -150
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +317 -164
- 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 +25 -134
- data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
- 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 +39 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +112 -55
- data/lib/active_record/connection_adapters/pool_config.rb +20 -11
- 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 +89 -52
- 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/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.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
- 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 +153 -3
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +397 -75
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +508 -246
- data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +296 -104
- 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 +258 -0
- data/lib/active_record/connection_adapters.rb +9 -6
- data/lib/active_record/connection_handling.rb +108 -137
- data/lib/active_record/core.rb +242 -233
- data/lib/active_record/counter_cache.rb +52 -27
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -2
- data/lib/active_record/database_configurations/database_config.rb +21 -12
- data/lib/active_record/database_configurations/hash_config.rb +88 -16
- data/lib/active_record/database_configurations/url_config.rb +18 -12
- data/lib/active_record/database_configurations.rb +95 -59
- data/lib/active_record/delegated_type.rb +66 -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 +1 -1
- 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 +155 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +155 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +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_serializer.rb +92 -0
- data/lib/active_record/encryption/null_encryptor.rb +21 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
- data/lib/active_record/encryption/scheme.rb +100 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +154 -63
- data/lib/active_record/errors.rb +172 -15
- data/lib/active_record/explain.rb +23 -3
- 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 +147 -86
- data/lib/active_record/future_result.rb +174 -0
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +81 -29
- data/lib/active_record/insert_all.rb +135 -22
- data/lib/active_record/integration.rb +11 -10
- data/lib/active_record/internal_metadata.rb +119 -33
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +37 -22
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +52 -19
- 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 +112 -14
- data/lib/active_record/migration/compatibility.rb +233 -46
- data/lib/active_record/migration/default_strategy.rb +23 -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 +361 -173
- data/lib/active_record/model_schema.rb +125 -101
- data/lib/active_record/nested_attributes.rb +50 -20
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +409 -88
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +4 -22
- data/lib/active_record/query_logs.rb +174 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +29 -6
- data/lib/active_record/railtie.rb +220 -44
- data/lib/active_record/railties/controller_runtime.rb +15 -10
- data/lib/active_record/railties/databases.rake +188 -252
- 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 +248 -81
- data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
- data/lib/active_record/relation/batches.rb +192 -63
- data/lib/active_record/relation/calculations.rb +246 -90
- data/lib/active_record/relation/delegation.rb +28 -14
- data/lib/active_record/relation/finder_methods.rb +108 -51
- data/lib/active_record/relation/merger.rb +22 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- 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 +27 -20
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +670 -129
- data/lib/active_record/relation/record_fetch_warning.rb +7 -9
- data/lib/active_record/relation/spawn_methods.rb +20 -3
- data/lib/active_record/relation/where_clause.rb +10 -19
- data/lib/active_record/relation.rb +287 -120
- data/lib/active_record/result.rb +37 -11
- data/lib/active_record/runtime_registry.rb +32 -13
- data/lib/active_record/sanitization.rb +65 -20
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +73 -24
- data/lib/active_record/schema_migration.rb +68 -33
- 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 +10 -8
- data/lib/active_record/store.rb +10 -10
- data/lib/active_record/suppressor.rb +13 -15
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +251 -140
- 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 +15 -7
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +117 -96
- data/lib/active_record/timestamp.rb +32 -19
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +48 -27
- data/lib/active_record/translation.rb +3 -3
- 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 -5
- 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/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +4 -4
- 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 +51 -6
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +335 -32
- data/lib/arel/attributes/attribute.rb +0 -8
- 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/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -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/node.rb +111 -2
- 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 +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +5 -0
- data/lib/arel/predications.rb +13 -3
- data/lib/arel/select_manager.rb +10 -4
- data/lib/arel/table.rb +9 -6
- data/lib/arel/tree_manager.rb +5 -13
- data/lib/arel/update_manager.rb +18 -4
- data/lib/arel/visitors/dot.rb +80 -90
- data/lib/arel/visitors/mysql.rb +16 -3
- data/lib/arel/visitors/postgresql.rb +0 -10
- data/lib/arel/visitors/to_sql.rb +141 -20
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +18 -3
- 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.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 +96 -16
- 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,38 @@ 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
|
+
def associated(*associations)
|
76
|
+
associations.each do |association|
|
77
|
+
reflection = scope_association_reflection(association)
|
78
|
+
@scope.joins!(association)
|
79
|
+
if reflection.options[:class_name]
|
80
|
+
self.not(association => { reflection.association_primary_key => nil })
|
81
|
+
else
|
82
|
+
self.not(reflection.table_name => { reflection.association_primary_key => nil })
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
@scope
|
87
|
+
end
|
88
|
+
|
53
89
|
# Returns a new relation with left outer joins and where clause to identify
|
54
90
|
# missing relations.
|
55
91
|
#
|
@@ -68,16 +104,37 @@ module ActiveRecord
|
|
68
104
|
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
69
105
|
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
70
106
|
# # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
|
71
|
-
def missing(*
|
72
|
-
|
73
|
-
reflection =
|
74
|
-
|
75
|
-
|
76
|
-
|
107
|
+
def missing(*associations)
|
108
|
+
associations.each do |association|
|
109
|
+
reflection = scope_association_reflection(association)
|
110
|
+
@scope.left_outer_joins!(association)
|
111
|
+
if reflection.options[:class_name]
|
112
|
+
@scope.where!(association => { reflection.association_primary_key => nil })
|
113
|
+
else
|
114
|
+
@scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
|
115
|
+
end
|
77
116
|
end
|
78
117
|
|
79
118
|
@scope
|
80
119
|
end
|
120
|
+
|
121
|
+
private
|
122
|
+
def scope_association_reflection(association)
|
123
|
+
reflection = @scope.klass._reflect_on_association(association)
|
124
|
+
unless reflection
|
125
|
+
raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
|
126
|
+
end
|
127
|
+
reflection
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# A wrapper to distinguish CTE joins from other nodes.
|
132
|
+
class CTEJoin # :nodoc:
|
133
|
+
attr_reader :name
|
134
|
+
|
135
|
+
def initialize(name)
|
136
|
+
@name = name
|
137
|
+
end
|
81
138
|
end
|
82
139
|
|
83
140
|
FROZEN_EMPTY_ARRAY = [].freeze
|
@@ -108,47 +165,71 @@ module ActiveRecord
|
|
108
165
|
|
109
166
|
alias extensions extending_values
|
110
167
|
|
111
|
-
# Specify
|
112
|
-
#
|
168
|
+
# Specify associations +args+ to be eager loaded to prevent N + 1 queries.
|
169
|
+
# A separate query is performed for each association, unless a join is
|
170
|
+
# required by conditions.
|
171
|
+
#
|
172
|
+
# For example:
|
113
173
|
#
|
114
|
-
# users = User.includes(:address)
|
174
|
+
# users = User.includes(:address).limit(5)
|
115
175
|
# users.each do |user|
|
116
176
|
# user.address.city
|
117
177
|
# end
|
118
178
|
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
# performance improvement over a simple join.
|
179
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
180
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
122
181
|
#
|
123
|
-
#
|
182
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
183
|
+
# are loaded with a single query.
|
124
184
|
#
|
125
|
-
#
|
185
|
+
# Loading the associations in a separate query will often result in a
|
186
|
+
# performance improvement over a simple join, as a join can result in many
|
187
|
+
# rows that contain redundant data and it performs poorly at scale.
|
126
188
|
#
|
127
|
-
#
|
189
|
+
# You can also specify multiple associations. Each association will result
|
190
|
+
# in an additional query:
|
128
191
|
#
|
129
|
-
#
|
192
|
+
# User.includes(:address, :friends).to_a
|
193
|
+
# # SELECT "users".* FROM "users"
|
194
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
195
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
130
196
|
#
|
131
|
-
#
|
197
|
+
# Loading nested associations is possible using a Hash:
|
198
|
+
#
|
199
|
+
# User.includes(:address, friends: [:address, :followers])
|
200
|
+
#
|
201
|
+
# === Conditions
|
132
202
|
#
|
133
203
|
# If you want to add string conditions to your included models, you'll have
|
134
204
|
# to explicitly reference them. For example:
|
135
205
|
#
|
136
|
-
# User.includes(:posts).where('posts.name = ?', 'example')
|
206
|
+
# User.includes(:posts).where('posts.name = ?', 'example').to_a
|
137
207
|
#
|
138
208
|
# Will throw an error, but this will work:
|
139
209
|
#
|
140
|
-
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
210
|
+
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
|
211
|
+
# # SELECT "users"."id" AS t0_r0, ... FROM "users"
|
212
|
+
# # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
213
|
+
# # WHERE "posts"."name" = ? [["name", "example"]]
|
214
|
+
#
|
215
|
+
# As the <tt>LEFT OUTER JOIN</tt> already contains the posts, the second query for
|
216
|
+
# the posts is no longer performed.
|
141
217
|
#
|
142
218
|
# Note that #includes works with association names while #references needs
|
143
219
|
# the actual table name.
|
144
220
|
#
|
145
|
-
# If you pass the conditions via
|
221
|
+
# If you pass the conditions via a Hash, you don't need to call #references
|
146
222
|
# explicitly, as #where references the tables for you. For example, this
|
147
223
|
# will work correctly:
|
148
224
|
#
|
149
225
|
# User.includes(:posts).where(posts: { name: 'example' })
|
226
|
+
#
|
227
|
+
# NOTE: Conditions affect both sides of an association. For example, the
|
228
|
+
# above code will return only users that have a post named "example",
|
229
|
+
# <em>and will only include posts named "example"</em>, even when a
|
230
|
+
# matching user has other additional posts.
|
150
231
|
def includes(*args)
|
151
|
-
check_if_method_has_arguments!(
|
232
|
+
check_if_method_has_arguments!(__callee__, args)
|
152
233
|
spawn.includes!(*args)
|
153
234
|
end
|
154
235
|
|
@@ -157,14 +238,34 @@ module ActiveRecord
|
|
157
238
|
self
|
158
239
|
end
|
159
240
|
|
160
|
-
#
|
241
|
+
# Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
|
242
|
+
# Performs a single query joining all specified associations. For example:
|
161
243
|
#
|
162
|
-
# User.eager_load(:
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
244
|
+
# users = User.eager_load(:address).limit(5)
|
245
|
+
# users.each do |user|
|
246
|
+
# user.address.city
|
247
|
+
# end
|
248
|
+
#
|
249
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
250
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
251
|
+
# # LIMIT 5
|
252
|
+
#
|
253
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
254
|
+
# are loaded with a single joined query.
|
255
|
+
#
|
256
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
257
|
+
# similar to #includes:
|
258
|
+
#
|
259
|
+
# User.eager_load(:address, friends: [:address, :followers])
|
260
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
261
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
262
|
+
# # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
|
263
|
+
# # ...
|
264
|
+
#
|
265
|
+
# NOTE: Loading the associations in a join can result in many rows that
|
266
|
+
# contain redundant data and it performs poorly at scale.
|
166
267
|
def eager_load(*args)
|
167
|
-
check_if_method_has_arguments!(
|
268
|
+
check_if_method_has_arguments!(__callee__, args)
|
168
269
|
spawn.eager_load!(*args)
|
169
270
|
end
|
170
271
|
|
@@ -173,12 +274,30 @@ module ActiveRecord
|
|
173
274
|
self
|
174
275
|
end
|
175
276
|
|
176
|
-
#
|
277
|
+
# Specify associations +args+ to be eager loaded using separate queries.
|
278
|
+
# A separate query is performed for each association.
|
177
279
|
#
|
178
|
-
# User.preload(:
|
179
|
-
#
|
280
|
+
# users = User.preload(:address).limit(5)
|
281
|
+
# users.each do |user|
|
282
|
+
# user.address.city
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
286
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
287
|
+
#
|
288
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
289
|
+
# are loaded with a separate query.
|
290
|
+
#
|
291
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
292
|
+
# similar to #includes:
|
293
|
+
#
|
294
|
+
# User.preload(:address, friends: [:address, :followers])
|
295
|
+
# # SELECT "users".* FROM "users"
|
296
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
297
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
298
|
+
# # SELECT ...
|
180
299
|
def preload(*args)
|
181
|
-
check_if_method_has_arguments!(
|
300
|
+
check_if_method_has_arguments!(__callee__, args)
|
182
301
|
spawn.preload!(*args)
|
183
302
|
end
|
184
303
|
|
@@ -201,7 +320,7 @@ module ActiveRecord
|
|
201
320
|
end
|
202
321
|
|
203
322
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
204
|
-
# and should therefore be
|
323
|
+
# and should therefore be +JOIN+ed in any query rather than loaded separately.
|
205
324
|
# This method only works in conjunction with #includes.
|
206
325
|
# See #includes for more details.
|
207
326
|
#
|
@@ -211,7 +330,7 @@ module ActiveRecord
|
|
211
330
|
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
|
212
331
|
# # Query now knows the string references posts, so adds a JOIN
|
213
332
|
def references(*table_names)
|
214
|
-
check_if_method_has_arguments!(
|
333
|
+
check_if_method_has_arguments!(__callee__, table_names)
|
215
334
|
spawn.references!(*table_names)
|
216
335
|
end
|
217
336
|
|
@@ -245,10 +364,18 @@ module ActiveRecord
|
|
245
364
|
# Model.select(:field, :other_field, :and_one_more)
|
246
365
|
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
|
247
366
|
#
|
367
|
+
# The argument also can be a hash of fields and aliases.
|
368
|
+
#
|
369
|
+
# Model.select(models: { field: :alias, other_field: :other_alias })
|
370
|
+
# # => [#<Model id: nil, alias: "value", other_alias: "value">]
|
371
|
+
#
|
372
|
+
# Model.select(models: [:field, :other_field])
|
373
|
+
# # => [#<Model id: nil, field: "value", other_field: "value">]
|
374
|
+
#
|
248
375
|
# You can also use one or more strings, which will be used unchanged as SELECT fields.
|
249
376
|
#
|
250
377
|
# Model.select('field AS field_one', 'other_field AS field_two')
|
251
|
-
# # => [#<Model id: nil,
|
378
|
+
# # => [#<Model id: nil, field_one: "value", field_two: "value">]
|
252
379
|
#
|
253
380
|
# If an alias was specified, it will be accessible from the resulting objects:
|
254
381
|
#
|
@@ -259,7 +386,7 @@ module ActiveRecord
|
|
259
386
|
# except +id+ will throw ActiveModel::MissingAttributeError:
|
260
387
|
#
|
261
388
|
# Model.select(:field).first.other_field
|
262
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
389
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
|
263
390
|
def select(*fields)
|
264
391
|
if block_given?
|
265
392
|
if fields.any?
|
@@ -269,7 +396,9 @@ module ActiveRecord
|
|
269
396
|
return super()
|
270
397
|
end
|
271
398
|
|
272
|
-
check_if_method_has_arguments!(
|
399
|
+
check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
|
400
|
+
|
401
|
+
fields = process_select_args(fields)
|
273
402
|
spawn._select!(*fields)
|
274
403
|
end
|
275
404
|
|
@@ -278,6 +407,66 @@ module ActiveRecord
|
|
278
407
|
self
|
279
408
|
end
|
280
409
|
|
410
|
+
# Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
411
|
+
#
|
412
|
+
# Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to
|
413
|
+
# use CTE's with MySQL 5.7.
|
414
|
+
#
|
415
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
|
416
|
+
# # => ActiveRecord::Relation
|
417
|
+
# # WITH posts_with_tags AS (
|
418
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
419
|
+
# # )
|
420
|
+
# # SELECT * FROM posts
|
421
|
+
#
|
422
|
+
# Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
|
423
|
+
#
|
424
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
|
425
|
+
# # => ActiveRecord::Relation
|
426
|
+
# # WITH posts_with_tags AS (
|
427
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
428
|
+
# # )
|
429
|
+
# # SELECT * FROM posts_with_tags AS posts
|
430
|
+
#
|
431
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
|
432
|
+
# # => ActiveRecord::Relation
|
433
|
+
# # WITH posts_with_tags AS (
|
434
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
435
|
+
# # )
|
436
|
+
# # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
|
437
|
+
#
|
438
|
+
# It is recommended to pass a query as ActiveRecord::Relation. If that is not possible
|
439
|
+
# and you have verified it is safe for the database, you can pass it as SQL literal
|
440
|
+
# using +Arel+.
|
441
|
+
#
|
442
|
+
# Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
|
443
|
+
#
|
444
|
+
# Great caution should be taken to avoid SQL injection vulnerabilities. This method should not
|
445
|
+
# be used with unsafe values that include unsanitized input.
|
446
|
+
#
|
447
|
+
# To add multiple CTEs just pass multiple key-value pairs
|
448
|
+
#
|
449
|
+
# Post.with(
|
450
|
+
# posts_with_comments: Post.where("comments_count > ?", 0),
|
451
|
+
# posts_with_tags: Post.where("tags_count > ?", 0)
|
452
|
+
# )
|
453
|
+
#
|
454
|
+
# or chain multiple +.with+ calls
|
455
|
+
#
|
456
|
+
# Post
|
457
|
+
# .with(posts_with_comments: Post.where("comments_count > ?", 0))
|
458
|
+
# .with(posts_with_tags: Post.where("tags_count > ?", 0))
|
459
|
+
def with(*args)
|
460
|
+
check_if_method_has_arguments!(__callee__, args)
|
461
|
+
spawn.with!(*args)
|
462
|
+
end
|
463
|
+
|
464
|
+
# Like #with, but modifies relation in place.
|
465
|
+
def with!(*args) # :nodoc:
|
466
|
+
self.with_values += args
|
467
|
+
self
|
468
|
+
end
|
469
|
+
|
281
470
|
# Allows you to change a previously set select statement.
|
282
471
|
#
|
283
472
|
# Post.select(:title, :body)
|
@@ -289,7 +478,8 @@ module ActiveRecord
|
|
289
478
|
# This is short-hand for <tt>unscope(:select).select(fields)</tt>.
|
290
479
|
# Note that we're unscoping the entire select statement.
|
291
480
|
def reselect(*args)
|
292
|
-
check_if_method_has_arguments!(
|
481
|
+
check_if_method_has_arguments!(__callee__, args)
|
482
|
+
args = process_select_args(args)
|
293
483
|
spawn.reselect!(*args)
|
294
484
|
end
|
295
485
|
|
@@ -320,7 +510,7 @@ module ActiveRecord
|
|
320
510
|
# User.select([:id, :first_name]).group(:id, :first_name).first(3)
|
321
511
|
# # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
|
322
512
|
def group(*args)
|
323
|
-
check_if_method_has_arguments!(
|
513
|
+
check_if_method_has_arguments!(__callee__, args)
|
324
514
|
spawn.group!(*args)
|
325
515
|
end
|
326
516
|
|
@@ -329,17 +519,58 @@ module ActiveRecord
|
|
329
519
|
self
|
330
520
|
end
|
331
521
|
|
332
|
-
# Allows to
|
522
|
+
# Allows you to change a previously set group statement.
|
523
|
+
#
|
524
|
+
# Post.group(:title, :body)
|
525
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
|
526
|
+
#
|
527
|
+
# Post.group(:title, :body).regroup(:title)
|
528
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
|
529
|
+
#
|
530
|
+
# This is short-hand for <tt>unscope(:group).group(fields)</tt>.
|
531
|
+
# Note that we're unscoping the entire group statement.
|
532
|
+
def regroup(*args)
|
533
|
+
check_if_method_has_arguments!(__callee__, args)
|
534
|
+
spawn.regroup!(*args)
|
535
|
+
end
|
536
|
+
|
537
|
+
# Same as #regroup but operates on relation in-place instead of copying.
|
538
|
+
def regroup!(*args) # :nodoc:
|
539
|
+
self.group_values = args
|
540
|
+
self
|
541
|
+
end
|
542
|
+
|
543
|
+
# Applies an <code>ORDER BY</code> clause to a query.
|
544
|
+
#
|
545
|
+
# #order accepts arguments in one of several formats.
|
546
|
+
#
|
547
|
+
# === symbols
|
548
|
+
#
|
549
|
+
# The symbol represents the name of the column you want to order the results by.
|
333
550
|
#
|
334
551
|
# User.order(:name)
|
335
552
|
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
|
336
553
|
#
|
554
|
+
# By default, the order is ascending. If you want descending order, you can
|
555
|
+
# map the column name symbol to +:desc+.
|
556
|
+
#
|
337
557
|
# User.order(email: :desc)
|
338
558
|
# # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
|
339
559
|
#
|
560
|
+
# Multiple columns can be passed this way, and they will be applied in the order specified.
|
561
|
+
#
|
340
562
|
# User.order(:name, email: :desc)
|
341
563
|
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
|
342
564
|
#
|
565
|
+
# === strings
|
566
|
+
#
|
567
|
+
# Strings are passed directly to the database, allowing you to specify
|
568
|
+
# simple SQL expressions.
|
569
|
+
#
|
570
|
+
# This could be a source of SQL injection, so only strings composed of plain
|
571
|
+
# column names and simple <code>function(column_name)</code> expressions
|
572
|
+
# with optional +ASC+/+DESC+ modifiers are allowed.
|
573
|
+
#
|
343
574
|
# User.order('name')
|
344
575
|
# # SELECT "users".* FROM "users" ORDER BY name
|
345
576
|
#
|
@@ -348,8 +579,21 @@ module ActiveRecord
|
|
348
579
|
#
|
349
580
|
# User.order('name DESC, email')
|
350
581
|
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
|
582
|
+
#
|
583
|
+
# === Arel
|
584
|
+
#
|
585
|
+
# If you need to pass in complicated expressions that you have verified
|
586
|
+
# are safe for the database, you can use Arel.
|
587
|
+
#
|
588
|
+
# User.order(Arel.sql('end_date - start_date'))
|
589
|
+
# # SELECT "users".* FROM "users" ORDER BY end_date - start_date
|
590
|
+
#
|
591
|
+
# Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
|
592
|
+
#
|
593
|
+
# User.order(Arel.sql("payload->>'kind'"))
|
594
|
+
# # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
|
351
595
|
def order(*args)
|
352
|
-
check_if_method_has_arguments!(
|
596
|
+
check_if_method_has_arguments!(__callee__, args) do
|
353
597
|
sanitize_order_arguments(args)
|
354
598
|
end
|
355
599
|
spawn.order!(*args)
|
@@ -362,6 +606,39 @@ module ActiveRecord
|
|
362
606
|
self
|
363
607
|
end
|
364
608
|
|
609
|
+
# Allows to specify an order by a specific set of values.
|
610
|
+
#
|
611
|
+
# User.in_order_of(:id, [1, 5, 3])
|
612
|
+
# # SELECT "users".* FROM "users"
|
613
|
+
# # WHERE "users"."id" IN (1, 5, 3)
|
614
|
+
# # ORDER BY CASE
|
615
|
+
# # WHEN "users"."id" = 1 THEN 1
|
616
|
+
# # WHEN "users"."id" = 5 THEN 2
|
617
|
+
# # WHEN "users"."id" = 3 THEN 3
|
618
|
+
# # END ASC
|
619
|
+
#
|
620
|
+
def in_order_of(column, values)
|
621
|
+
klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
|
622
|
+
return spawn.none! if values.empty?
|
623
|
+
|
624
|
+
references = column_references([column])
|
625
|
+
self.references_values |= references unless references.empty?
|
626
|
+
|
627
|
+
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
628
|
+
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
|
629
|
+
|
630
|
+
where_clause =
|
631
|
+
if values.include?(nil)
|
632
|
+
arel_column.in(values.compact).or(arel_column.eq(nil))
|
633
|
+
else
|
634
|
+
arel_column.in(values)
|
635
|
+
end
|
636
|
+
|
637
|
+
spawn
|
638
|
+
.order!(build_case_for_value_position(arel_column, values))
|
639
|
+
.where!(where_clause)
|
640
|
+
end
|
641
|
+
|
365
642
|
# Replaces any existing order defined on the relation with the specified order.
|
366
643
|
#
|
367
644
|
# User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
|
@@ -370,17 +647,17 @@ module ActiveRecord
|
|
370
647
|
#
|
371
648
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
372
649
|
#
|
373
|
-
# generates a query with
|
650
|
+
# generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
|
374
651
|
def reorder(*args)
|
375
|
-
check_if_method_has_arguments!(
|
376
|
-
sanitize_order_arguments(args)
|
652
|
+
check_if_method_has_arguments!(__callee__, args) do
|
653
|
+
sanitize_order_arguments(args)
|
377
654
|
end
|
378
655
|
spawn.reorder!(*args)
|
379
656
|
end
|
380
657
|
|
381
658
|
# Same as #reorder but operates on relation in-place instead of copying.
|
382
659
|
def reorder!(*args) # :nodoc:
|
383
|
-
preprocess_order_args(args)
|
660
|
+
preprocess_order_args(args)
|
384
661
|
args.uniq!
|
385
662
|
self.reordering_value = true
|
386
663
|
self.order_values = args
|
@@ -389,7 +666,8 @@ module ActiveRecord
|
|
389
666
|
|
390
667
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
391
668
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
392
|
-
:includes, :
|
669
|
+
:includes, :eager_load, :preload, :from, :readonly,
|
670
|
+
:having, :optimizer_hints])
|
393
671
|
|
394
672
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
395
673
|
# This is useful when passing around chains of relations and would like to
|
@@ -425,7 +703,7 @@ module ActiveRecord
|
|
425
703
|
# has_many :comments, -> { unscope(where: :trashed) }
|
426
704
|
#
|
427
705
|
def unscope(*args)
|
428
|
-
check_if_method_has_arguments!(
|
706
|
+
check_if_method_has_arguments!(__callee__, args)
|
429
707
|
spawn.unscope!(*args)
|
430
708
|
end
|
431
709
|
|
@@ -458,7 +736,7 @@ module ActiveRecord
|
|
458
736
|
self
|
459
737
|
end
|
460
738
|
|
461
|
-
# Performs
|
739
|
+
# Performs JOINs on +args+. The given symbol(s) should match the name of
|
462
740
|
# the association(s).
|
463
741
|
#
|
464
742
|
# User.joins(:posts)
|
@@ -487,7 +765,7 @@ module ActiveRecord
|
|
487
765
|
# User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
|
488
766
|
# # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
|
489
767
|
def joins(*args)
|
490
|
-
check_if_method_has_arguments!(
|
768
|
+
check_if_method_has_arguments!(__callee__, args)
|
491
769
|
spawn.joins!(*args)
|
492
770
|
end
|
493
771
|
|
@@ -496,10 +774,10 @@ module ActiveRecord
|
|
496
774
|
self
|
497
775
|
end
|
498
776
|
|
499
|
-
# Performs
|
777
|
+
# Performs LEFT OUTER JOINs on +args+:
|
500
778
|
#
|
501
779
|
# User.left_outer_joins(:posts)
|
502
|
-
#
|
780
|
+
# # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
503
781
|
#
|
504
782
|
def left_outer_joins(*args)
|
505
783
|
check_if_method_has_arguments!(__callee__, args)
|
@@ -519,7 +797,7 @@ module ActiveRecord
|
|
519
797
|
# SQL is given as an illustration; the actual query generated may be different depending
|
520
798
|
# on the database adapter.
|
521
799
|
#
|
522
|
-
# ===
|
800
|
+
# === \String
|
523
801
|
#
|
524
802
|
# A single string, without additional arguments, is passed to the query
|
525
803
|
# constructor as an SQL fragment, and used in the where clause of the query.
|
@@ -531,7 +809,7 @@ module ActiveRecord
|
|
531
809
|
# to injection attacks if not done properly. As an alternative, it is recommended
|
532
810
|
# to use one of the following methods.
|
533
811
|
#
|
534
|
-
# ===
|
812
|
+
# === \Array
|
535
813
|
#
|
536
814
|
# If an array is passed, then the first element of the array is treated as a template, and
|
537
815
|
# the remaining elements are inserted into the template to generate the condition.
|
@@ -571,20 +849,20 @@ module ActiveRecord
|
|
571
849
|
# dependencies on the underlying database. If your code is intended for general consumption,
|
572
850
|
# test with multiple database backends.
|
573
851
|
#
|
574
|
-
# ===
|
852
|
+
# === \Hash
|
575
853
|
#
|
576
854
|
# #where will also accept a hash condition, in which the keys are fields and the values
|
577
855
|
# are values to be searched for.
|
578
856
|
#
|
579
857
|
# Fields can be symbols or strings. Values can be single values, arrays, or ranges.
|
580
858
|
#
|
581
|
-
# User.where(
|
859
|
+
# User.where(name: "Joe", email: "joe@example.com")
|
582
860
|
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
|
583
861
|
#
|
584
|
-
# User.where(
|
862
|
+
# User.where(name: ["Alice", "Bob"])
|
585
863
|
# # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
|
586
864
|
#
|
587
|
-
# User.where(
|
865
|
+
# User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
|
588
866
|
# # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
|
589
867
|
#
|
590
868
|
# In the case of a belongs_to relationship, an association key can be used
|
@@ -605,6 +883,12 @@ module ActiveRecord
|
|
605
883
|
# PriceEstimate.where(estimate_of: treasure)
|
606
884
|
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
607
885
|
#
|
886
|
+
# Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
|
887
|
+
# an array of columns with an array of tuples as values.
|
888
|
+
#
|
889
|
+
# Article.where([:author_id, :id] => [[15, 1], [15, 2]])
|
890
|
+
# # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
|
891
|
+
#
|
608
892
|
# === Joins
|
609
893
|
#
|
610
894
|
# If the relation is the result of a join, you may create a condition which uses any of the
|
@@ -614,20 +898,34 @@ module ActiveRecord
|
|
614
898
|
#
|
615
899
|
# For hash conditions, you can either use the table name in the key, or use a sub-hash.
|
616
900
|
#
|
617
|
-
# User.joins(:posts).where(
|
618
|
-
# User.joins(:posts).where(
|
901
|
+
# User.joins(:posts).where("posts.published" => true)
|
902
|
+
# User.joins(:posts).where(posts: { published: true })
|
619
903
|
#
|
620
|
-
# ===
|
904
|
+
# === No Argument
|
621
905
|
#
|
622
906
|
# If no argument is passed, #where returns a new instance of WhereChain, that
|
623
|
-
# can be chained with #not
|
907
|
+
# can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
|
908
|
+
#
|
909
|
+
# Chaining with WhereChain#not:
|
624
910
|
#
|
625
911
|
# User.where.not(name: "Jon")
|
626
912
|
# # SELECT * FROM users WHERE name != 'Jon'
|
627
913
|
#
|
628
|
-
#
|
914
|
+
# Chaining with WhereChain#associated:
|
915
|
+
#
|
916
|
+
# Post.where.associated(:author)
|
917
|
+
# # SELECT "posts".* FROM "posts"
|
918
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
919
|
+
# # WHERE "authors"."id" IS NOT NULL
|
920
|
+
#
|
921
|
+
# Chaining with WhereChain#missing:
|
629
922
|
#
|
630
|
-
#
|
923
|
+
# Post.where.missing(:author)
|
924
|
+
# # SELECT "posts".* FROM "posts"
|
925
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
926
|
+
# # WHERE "authors"."id" IS NULL
|
927
|
+
#
|
928
|
+
# === Blank Condition
|
631
929
|
#
|
632
930
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
633
931
|
# the current relation.
|
@@ -660,6 +958,8 @@ module ActiveRecord
|
|
660
958
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
661
959
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
662
960
|
def rewhere(conditions)
|
961
|
+
return unscope(:where) if conditions.nil?
|
962
|
+
|
663
963
|
scope = spawn
|
664
964
|
where_clause = scope.build_where_clause(conditions)
|
665
965
|
|
@@ -668,6 +968,59 @@ module ActiveRecord
|
|
668
968
|
scope
|
669
969
|
end
|
670
970
|
|
971
|
+
# Allows you to invert an entire where clause instead of manually applying conditions.
|
972
|
+
#
|
973
|
+
# class User
|
974
|
+
# scope :active, -> { where(accepted: true, locked: false) }
|
975
|
+
# end
|
976
|
+
#
|
977
|
+
# User.where(accepted: true)
|
978
|
+
# # WHERE `accepted` = 1
|
979
|
+
#
|
980
|
+
# User.where(accepted: true).invert_where
|
981
|
+
# # WHERE `accepted` != 1
|
982
|
+
#
|
983
|
+
# User.active
|
984
|
+
# # WHERE `accepted` = 1 AND `locked` = 0
|
985
|
+
#
|
986
|
+
# User.active.invert_where
|
987
|
+
# # WHERE NOT (`accepted` = 1 AND `locked` = 0)
|
988
|
+
#
|
989
|
+
# Be careful because this inverts all conditions before +invert_where+ call.
|
990
|
+
#
|
991
|
+
# class User
|
992
|
+
# scope :active, -> { where(accepted: true, locked: false) }
|
993
|
+
# scope :inactive, -> { active.invert_where } # Do not attempt it
|
994
|
+
# end
|
995
|
+
#
|
996
|
+
# # It also inverts `where(role: 'admin')` unexpectedly.
|
997
|
+
# User.where(role: 'admin').inactive
|
998
|
+
# # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
|
999
|
+
#
|
1000
|
+
def invert_where
|
1001
|
+
spawn.invert_where!
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
def invert_where! # :nodoc:
|
1005
|
+
self.where_clause = where_clause.invert
|
1006
|
+
self
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
# Checks whether the given relation is structurally compatible with this relation, to determine
|
1010
|
+
# if it's possible to use the #and and #or methods without raising an error. Structurally
|
1011
|
+
# compatible is defined as: they must be scoping the same model, and they must differ only by
|
1012
|
+
# #where (if no #group has been defined) or #having (if a #group is present).
|
1013
|
+
#
|
1014
|
+
# Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
|
1015
|
+
# # => true
|
1016
|
+
#
|
1017
|
+
# Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
|
1018
|
+
# # => false
|
1019
|
+
#
|
1020
|
+
def structurally_compatible?(other)
|
1021
|
+
structurally_incompatible_values_for(other).empty?
|
1022
|
+
end
|
1023
|
+
|
671
1024
|
# Returns a new relation, which is the logical intersection of this relation and the one passed
|
672
1025
|
# as an argument.
|
673
1026
|
#
|
@@ -712,7 +1065,11 @@ module ActiveRecord
|
|
712
1065
|
#
|
713
1066
|
def or(other)
|
714
1067
|
if other.is_a?(Relation)
|
715
|
-
|
1068
|
+
if @none
|
1069
|
+
other.spawn
|
1070
|
+
else
|
1071
|
+
spawn.or!(other)
|
1072
|
+
end
|
716
1073
|
else
|
717
1074
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
718
1075
|
end
|
@@ -825,15 +1182,29 @@ module ActiveRecord
|
|
825
1182
|
end
|
826
1183
|
|
827
1184
|
def none! # :nodoc:
|
828
|
-
|
1185
|
+
unless @none
|
1186
|
+
where!("1=0")
|
1187
|
+
@none = true
|
1188
|
+
end
|
1189
|
+
self
|
829
1190
|
end
|
830
1191
|
|
831
|
-
|
832
|
-
|
1192
|
+
def null_relation? # :nodoc:
|
1193
|
+
@none
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
# Mark a relation as readonly. Attempting to update a record will result in
|
1197
|
+
# an error.
|
833
1198
|
#
|
834
1199
|
# users = User.readonly
|
835
1200
|
# users.first.save
|
836
1201
|
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
1202
|
+
#
|
1203
|
+
# To make a readonly relation writable, pass +false+.
|
1204
|
+
#
|
1205
|
+
# users.readonly(false)
|
1206
|
+
# users.first.save
|
1207
|
+
# => true
|
837
1208
|
def readonly(value = true)
|
838
1209
|
spawn.readonly!(value)
|
839
1210
|
end
|
@@ -886,7 +1257,7 @@ module ActiveRecord
|
|
886
1257
|
self
|
887
1258
|
end
|
888
1259
|
|
889
|
-
# Specifies table from which the records will be fetched. For example:
|
1260
|
+
# Specifies the table from which the records will be fetched. For example:
|
890
1261
|
#
|
891
1262
|
# Topic.select('title').from('posts')
|
892
1263
|
# # SELECT title FROM posts
|
@@ -896,9 +1267,26 @@ module ActiveRecord
|
|
896
1267
|
# Topic.select('title').from(Topic.approved)
|
897
1268
|
# # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
|
898
1269
|
#
|
1270
|
+
# Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
|
1271
|
+
#
|
899
1272
|
# Topic.select('a.title').from(Topic.approved, :a)
|
900
1273
|
# # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
|
901
1274
|
#
|
1275
|
+
# It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
|
1276
|
+
#
|
1277
|
+
# Topic.select('title').from(Topic.approved).from(Topic.inactive)
|
1278
|
+
# # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
|
1279
|
+
#
|
1280
|
+
# For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
|
1281
|
+
#
|
1282
|
+
# color = "red"
|
1283
|
+
# Color
|
1284
|
+
# .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
|
1285
|
+
# .where("colorvalue->>'color' = ?", color)
|
1286
|
+
# .select("c.*").to_a
|
1287
|
+
# # SELECT c.*
|
1288
|
+
# # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
|
1289
|
+
# # WHERE (colorvalue->>'color' = 'red')
|
902
1290
|
def from(value, subquery_name = nil)
|
903
1291
|
spawn.from!(value, subquery_name)
|
904
1292
|
end
|
@@ -933,7 +1321,7 @@ module ActiveRecord
|
|
933
1321
|
#
|
934
1322
|
# The object returned is a relation, which can be further extended.
|
935
1323
|
#
|
936
|
-
# === Using a
|
1324
|
+
# === Using a \Module
|
937
1325
|
#
|
938
1326
|
# module Pagination
|
939
1327
|
# def page(number)
|
@@ -948,7 +1336,7 @@ module ActiveRecord
|
|
948
1336
|
#
|
949
1337
|
# scope = Model.all.extending(Pagination, SomethingElse)
|
950
1338
|
#
|
951
|
-
# === Using a
|
1339
|
+
# === Using a Block
|
952
1340
|
#
|
953
1341
|
# scope = Model.all.extending do
|
954
1342
|
# def page(number)
|
@@ -994,7 +1382,7 @@ module ActiveRecord
|
|
994
1382
|
# Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
|
995
1383
|
# # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
|
996
1384
|
def optimizer_hints(*args)
|
997
|
-
check_if_method_has_arguments!(
|
1385
|
+
check_if_method_has_arguments!(__callee__, args)
|
998
1386
|
spawn.optimizer_hints!(*args)
|
999
1387
|
end
|
1000
1388
|
|
@@ -1035,8 +1423,10 @@ module ActiveRecord
|
|
1035
1423
|
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
1036
1424
|
#
|
1037
1425
|
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
1426
|
+
#
|
1427
|
+
# Some escaping is performed, however untrusted user input should not be used.
|
1038
1428
|
def annotate(*args)
|
1039
|
-
check_if_method_has_arguments!(
|
1429
|
+
check_if_method_has_arguments!(__callee__, args)
|
1040
1430
|
spawn.annotate!(*args)
|
1041
1431
|
end
|
1042
1432
|
|
@@ -1054,6 +1444,47 @@ module ActiveRecord
|
|
1054
1444
|
self
|
1055
1445
|
end
|
1056
1446
|
|
1447
|
+
# Excludes the specified record (or collection of records) from the resulting
|
1448
|
+
# relation. For example:
|
1449
|
+
#
|
1450
|
+
# Post.excluding(post)
|
1451
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
|
1452
|
+
#
|
1453
|
+
# Post.excluding(post_one, post_two)
|
1454
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
|
1455
|
+
#
|
1456
|
+
# This can also be called on associations. As with the above example, either
|
1457
|
+
# a single record of collection thereof may be specified:
|
1458
|
+
#
|
1459
|
+
# post = Post.find(1)
|
1460
|
+
# comment = Comment.find(2)
|
1461
|
+
# post.comments.excluding(comment)
|
1462
|
+
# # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
|
1463
|
+
#
|
1464
|
+
# This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
|
1465
|
+
#
|
1466
|
+
# An <tt>ArgumentError</tt> will be raised if either no records are
|
1467
|
+
# specified, or if any of the records in the collection (if a collection
|
1468
|
+
# is passed in) are not instances of the same model that the relation is
|
1469
|
+
# scoping.
|
1470
|
+
def excluding(*records)
|
1471
|
+
records.flatten!(1)
|
1472
|
+
records.compact!
|
1473
|
+
|
1474
|
+
unless records.all?(klass)
|
1475
|
+
raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
spawn.excluding!(records)
|
1479
|
+
end
|
1480
|
+
alias :without :excluding
|
1481
|
+
|
1482
|
+
def excluding!(records) # :nodoc:
|
1483
|
+
predicates = [ predicate_builder[primary_key, records].invert ]
|
1484
|
+
self.where_clause += Relation::WhereClause.new(predicates)
|
1485
|
+
self
|
1486
|
+
end
|
1487
|
+
|
1057
1488
|
# Returns the Arel object associated with the relation.
|
1058
1489
|
def arel(aliases = nil) # :nodoc:
|
1059
1490
|
@arel ||= build_arel(aliases)
|
@@ -1082,8 +1513,12 @@ module ActiveRecord
|
|
1082
1513
|
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1083
1514
|
when Hash
|
1084
1515
|
opts = opts.transform_keys do |key|
|
1085
|
-
|
1086
|
-
|
1516
|
+
if key.is_a?(Array)
|
1517
|
+
key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
|
1518
|
+
else
|
1519
|
+
key = key.to_s
|
1520
|
+
klass.attribute_aliases[key] || key
|
1521
|
+
end
|
1087
1522
|
end
|
1088
1523
|
references = PredicateBuilder.references(opts)
|
1089
1524
|
self.references_values |= references unless references.empty?
|
@@ -1101,7 +1536,16 @@ module ActiveRecord
|
|
1101
1536
|
end
|
1102
1537
|
alias :build_having_clause :build_where_clause
|
1103
1538
|
|
1539
|
+
def async!
|
1540
|
+
@async = true
|
1541
|
+
self
|
1542
|
+
end
|
1543
|
+
|
1104
1544
|
private
|
1545
|
+
def async
|
1546
|
+
spawn.async!
|
1547
|
+
end
|
1548
|
+
|
1105
1549
|
def lookup_table_klass_from_join_dependencies(table_name)
|
1106
1550
|
each_join_dependencies do |join|
|
1107
1551
|
return join.base_klass if table_name == join.table_name
|
@@ -1109,22 +1553,20 @@ module ActiveRecord
|
|
1109
1553
|
nil
|
1110
1554
|
end
|
1111
1555
|
|
1112
|
-
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
1556
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
|
1113
1557
|
join_dependencies.each do |join_dependency|
|
1114
|
-
join_dependency.each
|
1115
|
-
yield join
|
1116
|
-
end
|
1558
|
+
join_dependency.each(&block)
|
1117
1559
|
end
|
1118
1560
|
end
|
1119
1561
|
|
1120
1562
|
def build_join_dependencies
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1563
|
+
joins = joins_values | left_outer_joins_values
|
1564
|
+
joins |= eager_load_values unless eager_load_values.empty?
|
1565
|
+
joins |= includes_values unless includes_values.empty?
|
1124
1566
|
|
1125
1567
|
join_dependencies = []
|
1126
1568
|
join_dependencies.unshift construct_join_dependency(
|
1127
|
-
|
1569
|
+
select_named_joins(joins, join_dependencies), nil
|
1128
1570
|
)
|
1129
1571
|
end
|
1130
1572
|
|
@@ -1145,6 +1587,7 @@ module ActiveRecord
|
|
1145
1587
|
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1146
1588
|
|
1147
1589
|
build_order(arel)
|
1590
|
+
build_with(arel)
|
1148
1591
|
build_select(arel)
|
1149
1592
|
|
1150
1593
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
@@ -1155,14 +1598,6 @@ module ActiveRecord
|
|
1155
1598
|
unless annotate_values.empty?
|
1156
1599
|
annotates = annotate_values
|
1157
1600
|
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
1601
|
arel.comment(*annotates)
|
1167
1602
|
end
|
1168
1603
|
|
@@ -1170,8 +1605,7 @@ module ActiveRecord
|
|
1170
1605
|
end
|
1171
1606
|
|
1172
1607
|
def build_cast_value(name, value)
|
1173
|
-
|
1174
|
-
Arel::Nodes::BindParam.new(cast_value)
|
1608
|
+
ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1175
1609
|
end
|
1176
1610
|
|
1177
1611
|
def build_from
|
@@ -1189,6 +1623,18 @@ module ActiveRecord
|
|
1189
1623
|
end
|
1190
1624
|
end
|
1191
1625
|
|
1626
|
+
def select_named_joins(join_names, stashed_joins = nil, &block)
|
1627
|
+
cte_joins, associations = join_names.partition do |join_name|
|
1628
|
+
Symbol === join_name && with_values.any? { _1.key?(join_name) }
|
1629
|
+
end
|
1630
|
+
|
1631
|
+
cte_joins.each do |cte_name|
|
1632
|
+
block&.call(CTEJoin.new(cte_name))
|
1633
|
+
end
|
1634
|
+
|
1635
|
+
select_association_list(associations, stashed_joins, &block)
|
1636
|
+
end
|
1637
|
+
|
1192
1638
|
def select_association_list(associations, stashed_joins = nil)
|
1193
1639
|
result = []
|
1194
1640
|
associations.each do |association|
|
@@ -1204,20 +1650,21 @@ module ActiveRecord
|
|
1204
1650
|
result
|
1205
1651
|
end
|
1206
1652
|
|
1207
|
-
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1208
|
-
end
|
1209
|
-
|
1210
1653
|
def build_join_buckets
|
1211
1654
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1212
1655
|
|
1213
1656
|
unless left_outer_joins_values.empty?
|
1214
1657
|
stashed_left_joins = []
|
1215
|
-
left_joins =
|
1216
|
-
|
1658
|
+
left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
|
1659
|
+
if left_join.is_a?(CTEJoin)
|
1660
|
+
buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
|
1661
|
+
else
|
1662
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1663
|
+
end
|
1217
1664
|
end
|
1218
1665
|
|
1219
1666
|
if joins_values.empty?
|
1220
|
-
buckets[:
|
1667
|
+
buckets[:named_join] = left_joins
|
1221
1668
|
buckets[:stashed_join] = stashed_left_joins
|
1222
1669
|
return buckets, Arel::Nodes::OuterJoin
|
1223
1670
|
else
|
@@ -1243,9 +1690,11 @@ module ActiveRecord
|
|
1243
1690
|
end
|
1244
1691
|
end
|
1245
1692
|
|
1246
|
-
buckets[:
|
1693
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
1247
1694
|
if join.is_a?(Arel::Nodes::Join)
|
1248
1695
|
buckets[:join_node] << join
|
1696
|
+
elsif join.is_a?(CTEJoin)
|
1697
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
1249
1698
|
else
|
1250
1699
|
raise "unknown class: %s" % join.class.name
|
1251
1700
|
end
|
@@ -1262,16 +1711,16 @@ module ActiveRecord
|
|
1262
1711
|
|
1263
1712
|
buckets, join_type = build_join_buckets
|
1264
1713
|
|
1265
|
-
|
1266
|
-
stashed_joins
|
1267
|
-
leading_joins
|
1268
|
-
join_nodes
|
1714
|
+
named_joins = buckets[:named_join]
|
1715
|
+
stashed_joins = buckets[:stashed_join]
|
1716
|
+
leading_joins = buckets[:leading_join]
|
1717
|
+
join_nodes = buckets[:join_node]
|
1269
1718
|
|
1270
1719
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1271
1720
|
|
1272
|
-
unless
|
1721
|
+
unless named_joins.empty? && stashed_joins.empty?
|
1273
1722
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1274
|
-
join_dependency = construct_join_dependency(
|
1723
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
1275
1724
|
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1276
1725
|
end
|
1277
1726
|
|
@@ -1282,13 +1731,47 @@ module ActiveRecord
|
|
1282
1731
|
def build_select(arel)
|
1283
1732
|
if select_values.any?
|
1284
1733
|
arel.project(*arel_columns(select_values))
|
1285
|
-
elsif klass.ignored_columns.any?
|
1734
|
+
elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
|
1286
1735
|
arel.project(*klass.column_names.map { |field| table[field] })
|
1287
1736
|
else
|
1288
1737
|
arel.project(table[Arel.star])
|
1289
1738
|
end
|
1290
1739
|
end
|
1291
1740
|
|
1741
|
+
def build_with(arel)
|
1742
|
+
return if with_values.empty?
|
1743
|
+
|
1744
|
+
with_statements = with_values.map do |with_value|
|
1745
|
+
raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
|
1746
|
+
|
1747
|
+
build_with_value_from_hash(with_value)
|
1748
|
+
end
|
1749
|
+
|
1750
|
+
arel.with(with_statements)
|
1751
|
+
end
|
1752
|
+
|
1753
|
+
def build_with_value_from_hash(hash)
|
1754
|
+
hash.map do |name, value|
|
1755
|
+
expression =
|
1756
|
+
case value
|
1757
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
1758
|
+
when ActiveRecord::Relation then value.arel
|
1759
|
+
when Arel::SelectManager then value
|
1760
|
+
else
|
1761
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
1762
|
+
end
|
1763
|
+
Arel::Nodes::TableAlias.new(expression, name)
|
1764
|
+
end
|
1765
|
+
end
|
1766
|
+
|
1767
|
+
def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
|
1768
|
+
with_table = Arel::Table.new(name)
|
1769
|
+
|
1770
|
+
table.join(with_table, kind).on(
|
1771
|
+
with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
|
1772
|
+
).join_sources.first
|
1773
|
+
end
|
1774
|
+
|
1292
1775
|
def arel_columns(columns)
|
1293
1776
|
columns.flat_map do |field|
|
1294
1777
|
case field
|
@@ -1407,7 +1890,7 @@ module ActiveRecord
|
|
1407
1890
|
when Hash
|
1408
1891
|
arg.map { |field, dir|
|
1409
1892
|
case field
|
1410
|
-
when Arel::Nodes::SqlLiteral
|
1893
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
1411
1894
|
field.public_send(dir.downcase)
|
1412
1895
|
else
|
1413
1896
|
order_column(field.to_s).public_send(dir.downcase)
|
@@ -1423,14 +1906,35 @@ module ActiveRecord
|
|
1423
1906
|
order_args.map! do |arg|
|
1424
1907
|
klass.sanitize_sql_for_order(arg)
|
1425
1908
|
end
|
1426
|
-
order_args.flatten!
|
1427
|
-
order_args.compact_blank!
|
1428
1909
|
end
|
1429
1910
|
|
1430
1911
|
def column_references(order_args)
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1912
|
+
order_args.flat_map do |arg|
|
1913
|
+
case arg
|
1914
|
+
when String, Symbol
|
1915
|
+
extract_table_name_from(arg)
|
1916
|
+
when Hash
|
1917
|
+
arg
|
1918
|
+
.map do |key, value|
|
1919
|
+
case value
|
1920
|
+
when Hash
|
1921
|
+
key.to_s
|
1922
|
+
else
|
1923
|
+
extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
|
1924
|
+
end
|
1925
|
+
end
|
1926
|
+
when Arel::Attribute
|
1927
|
+
arg.relation.name
|
1928
|
+
when Arel::Nodes::Ordering
|
1929
|
+
if arg.expr.is_a?(Arel::Attribute)
|
1930
|
+
arg.expr.relation.name
|
1931
|
+
end
|
1932
|
+
end
|
1933
|
+
end.compact
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
def extract_table_name_from(string)
|
1937
|
+
string.match(/^\W?(\w+)\W?\./) && $1
|
1434
1938
|
end
|
1435
1939
|
|
1436
1940
|
def order_column(field)
|
@@ -1443,6 +1947,15 @@ module ActiveRecord
|
|
1443
1947
|
end
|
1444
1948
|
end
|
1445
1949
|
|
1950
|
+
def build_case_for_value_position(column, values)
|
1951
|
+
node = Arel::Nodes::Case.new
|
1952
|
+
values.each.with_index(1) do |value, order|
|
1953
|
+
node.when(column.eq(value)).then(order)
|
1954
|
+
end
|
1955
|
+
|
1956
|
+
Arel::Nodes::Ascending.new(node)
|
1957
|
+
end
|
1958
|
+
|
1446
1959
|
def resolve_arel_attributes(attrs)
|
1447
1960
|
attrs.flat_map do |attr|
|
1448
1961
|
case attr
|
@@ -1476,24 +1989,59 @@ module ActiveRecord
|
|
1476
1989
|
# Post.references() # raises an error
|
1477
1990
|
# Post.references([]) # does not raise an error
|
1478
1991
|
#
|
1479
|
-
# This particular method should be called with a method_name and the args
|
1992
|
+
# This particular method should be called with a method_name (__callee__) and the args
|
1480
1993
|
# passed into that method as an input. For example:
|
1481
1994
|
#
|
1482
1995
|
# def references(*args)
|
1483
|
-
# check_if_method_has_arguments!(
|
1996
|
+
# check_if_method_has_arguments!(__callee__, args)
|
1484
1997
|
# ...
|
1485
1998
|
# end
|
1486
1999
|
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1487
2000
|
if args.blank?
|
1488
2001
|
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1489
|
-
elsif block_given?
|
1490
|
-
yield args
|
1491
2002
|
else
|
2003
|
+
yield args if block_given?
|
2004
|
+
|
1492
2005
|
args.flatten!
|
1493
2006
|
args.compact_blank!
|
1494
2007
|
end
|
1495
2008
|
end
|
1496
2009
|
|
2010
|
+
def process_select_args(fields)
|
2011
|
+
fields.flat_map do |field|
|
2012
|
+
if field.is_a?(Hash)
|
2013
|
+
transform_select_hash_values(field)
|
2014
|
+
else
|
2015
|
+
field
|
2016
|
+
end
|
2017
|
+
end
|
2018
|
+
end
|
2019
|
+
|
2020
|
+
def transform_select_hash_values(fields)
|
2021
|
+
fields.flat_map do |key, columns_aliases|
|
2022
|
+
case columns_aliases
|
2023
|
+
when Hash
|
2024
|
+
columns_aliases.map do |column, column_alias|
|
2025
|
+
if values[:joins]&.include?(key)
|
2026
|
+
references = PredicateBuilder.references({ key.to_s => fields[key] })
|
2027
|
+
self.references_values |= references unless references.empty?
|
2028
|
+
end
|
2029
|
+
arel_column("#{key}.#{column}") do
|
2030
|
+
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
2031
|
+
end.as(column_alias.to_s)
|
2032
|
+
end
|
2033
|
+
when Array
|
2034
|
+
columns_aliases.map do |column|
|
2035
|
+
arel_column("#{key}.#{column}", &:itself)
|
2036
|
+
end
|
2037
|
+
when String, Symbol
|
2038
|
+
arel_column(key.to_s) do
|
2039
|
+
predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
|
2040
|
+
end.as(columns_aliases.to_s)
|
2041
|
+
end
|
2042
|
+
end
|
2043
|
+
end
|
2044
|
+
|
1497
2045
|
STRUCTURAL_VALUE_METHODS = (
|
1498
2046
|
Relation::VALUE_METHODS -
|
1499
2047
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
@@ -1512,11 +2060,4 @@ module ActiveRecord
|
|
1512
2060
|
end
|
1513
2061
|
end
|
1514
2062
|
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
2063
|
end
|