activerecord 6.1.6 → 7.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1627 -983
- 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 +50 -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 +35 -31
- 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.rb +26 -16
- data/lib/active_record/associations/preloader/association.rb +207 -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 +439 -305
- 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 +25 -10
- data/lib/active_record/attribute_methods/serialization.rb +194 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
- data/lib/active_record/attribute_methods/write.rb +10 -13
- data/lib/active_record/attribute_methods.rb +121 -40
- data/lib/active_record/attributes.rb +27 -38
- data/lib/active_record/autosave_association.rb +61 -30
- 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 -34
- 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 +96 -590
- 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 +77 -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 +360 -138
- data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
- data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -149
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
- 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 +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
- 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/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 +394 -74
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +509 -247
- 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 +294 -102
- 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 +254 -0
- data/lib/active_record/connection_adapters.rb +9 -6
- data/lib/active_record/connection_handling.rb +107 -136
- data/lib/active_record/core.rb +202 -223
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
- data/lib/active_record/database_configurations/database_config.rb +21 -12
- data/lib/active_record/database_configurations/hash_config.rb +84 -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 +61 -15
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- 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 +224 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -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 +96 -0
- data/lib/active_record/encryption.rb +56 -0
- data/lib/active_record/enum.rb +154 -63
- data/lib/active_record/errors.rb +171 -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 +131 -86
- data/lib/active_record/future_result.rb +164 -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 +36 -21
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +52 -19
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
- data/lib/active_record/middleware/database_selector.rb +23 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +112 -14
- data/lib/active_record/migration/compatibility.rb +221 -48
- 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 +358 -171
- data/lib/active_record/model_schema.rb +120 -101
- data/lib/active_record/nested_attributes.rb +37 -18
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +405 -85
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- 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 +219 -43
- data/lib/active_record/railties/controller_runtime.rb +13 -9
- 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 +241 -80
- 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 +219 -90
- data/lib/active_record/relation/delegation.rb +27 -13
- 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 +4 -6
- 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 +654 -127
- 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 +262 -120
- data/lib/active_record/result.rb +37 -11
- data/lib/active_record/runtime_registry.rb +18 -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 +16 -11
- 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 +225 -136
- 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 +123 -99
- data/lib/active_record/timestamp.rb +29 -18
- 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 +0 -12
- 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 +139 -19
- 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 +93 -13
- 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
|
-
#
|
179
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
180
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
181
|
+
#
|
182
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
183
|
+
# are loaded with a single query.
|
122
184
|
#
|
123
|
-
#
|
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.
|
124
188
|
#
|
125
|
-
#
|
189
|
+
# You can also specify multiple associations. Each association will result
|
190
|
+
# in an additional query:
|
126
191
|
#
|
127
|
-
#
|
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)
|
128
196
|
#
|
129
|
-
#
|
197
|
+
# Loading nested associations is possible using a Hash:
|
130
198
|
#
|
131
|
-
#
|
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:
|
629
915
|
#
|
630
|
-
#
|
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:
|
922
|
+
#
|
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,12 +1906,19 @@ 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
|
-
references = order_args.
|
1912
|
+
references = order_args.flat_map do |arg|
|
1913
|
+
case arg
|
1914
|
+
when String, Symbol
|
1915
|
+
arg
|
1916
|
+
when Hash
|
1917
|
+
arg.keys.map do |key|
|
1918
|
+
key if key.is_a?(String) || key.is_a?(Symbol)
|
1919
|
+
end
|
1920
|
+
end
|
1921
|
+
end
|
1432
1922
|
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1433
1923
|
references
|
1434
1924
|
end
|
@@ -1443,6 +1933,15 @@ module ActiveRecord
|
|
1443
1933
|
end
|
1444
1934
|
end
|
1445
1935
|
|
1936
|
+
def build_case_for_value_position(column, values)
|
1937
|
+
node = Arel::Nodes::Case.new
|
1938
|
+
values.each.with_index(1) do |value, order|
|
1939
|
+
node.when(column.eq(value)).then(order)
|
1940
|
+
end
|
1941
|
+
|
1942
|
+
Arel::Nodes::Ascending.new(node)
|
1943
|
+
end
|
1944
|
+
|
1446
1945
|
def resolve_arel_attributes(attrs)
|
1447
1946
|
attrs.flat_map do |attr|
|
1448
1947
|
case attr
|
@@ -1476,24 +1975,59 @@ module ActiveRecord
|
|
1476
1975
|
# Post.references() # raises an error
|
1477
1976
|
# Post.references([]) # does not raise an error
|
1478
1977
|
#
|
1479
|
-
# This particular method should be called with a method_name and the args
|
1978
|
+
# This particular method should be called with a method_name (__callee__) and the args
|
1480
1979
|
# passed into that method as an input. For example:
|
1481
1980
|
#
|
1482
1981
|
# def references(*args)
|
1483
|
-
# check_if_method_has_arguments!(
|
1982
|
+
# check_if_method_has_arguments!(__callee__, args)
|
1484
1983
|
# ...
|
1485
1984
|
# end
|
1486
1985
|
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1487
1986
|
if args.blank?
|
1488
1987
|
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1489
|
-
elsif block_given?
|
1490
|
-
yield args
|
1491
1988
|
else
|
1989
|
+
yield args if block_given?
|
1990
|
+
|
1492
1991
|
args.flatten!
|
1493
1992
|
args.compact_blank!
|
1494
1993
|
end
|
1495
1994
|
end
|
1496
1995
|
|
1996
|
+
def process_select_args(fields)
|
1997
|
+
fields.flat_map do |field|
|
1998
|
+
if field.is_a?(Hash)
|
1999
|
+
transform_select_hash_values(field)
|
2000
|
+
else
|
2001
|
+
field
|
2002
|
+
end
|
2003
|
+
end
|
2004
|
+
end
|
2005
|
+
|
2006
|
+
def transform_select_hash_values(fields)
|
2007
|
+
fields.flat_map do |key, columns_aliases|
|
2008
|
+
case columns_aliases
|
2009
|
+
when Hash
|
2010
|
+
columns_aliases.map do |column, column_alias|
|
2011
|
+
if values[:joins]&.include?(key)
|
2012
|
+
references = PredicateBuilder.references({ key.to_s => fields[key] })
|
2013
|
+
self.references_values |= references unless references.empty?
|
2014
|
+
end
|
2015
|
+
arel_column("#{key}.#{column}") do
|
2016
|
+
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
2017
|
+
end.as(column_alias.to_s)
|
2018
|
+
end
|
2019
|
+
when Array
|
2020
|
+
columns_aliases.map do |column|
|
2021
|
+
arel_column("#{key}.#{column}", &:itself)
|
2022
|
+
end
|
2023
|
+
when String, Symbol
|
2024
|
+
arel_column(key.to_s) do
|
2025
|
+
predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
|
2026
|
+
end.as(columns_aliases.to_s)
|
2027
|
+
end
|
2028
|
+
end
|
2029
|
+
end
|
2030
|
+
|
1497
2031
|
STRUCTURAL_VALUE_METHODS = (
|
1498
2032
|
Relation::VALUE_METHODS -
|
1499
2033
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
@@ -1512,11 +2046,4 @@ module ActiveRecord
|
|
1512
2046
|
end
|
1513
2047
|
end
|
1514
2048
|
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
2049
|
end
|