activerecord 7.0.0 → 7.2.1
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 +515 -1268
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +28 -17
- data/lib/active_record/associations/collection_proxy.rb +36 -13
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +28 -18
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +18 -14
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +2 -4
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +378 -491
- data/lib/active_record/attribute_assignment.rb +1 -13
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +153 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +153 -40
- data/lib/active_record/attributes.rb +63 -48
- data/lib/active_record/autosave_association.rb +70 -38
- data/lib/active_record/base.rb +12 -8
- data/lib/active_record/callbacks.rb +16 -32
- 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 +124 -132
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +297 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +215 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
- data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
- 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/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +98 -106
- data/lib/active_record/core.rb +220 -177
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +88 -35
- data/lib/active_record/delegated_type.rb +40 -11
- 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 +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +47 -25
- data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
- data/lib/active_record/encryption/encryptor.rb +25 -10
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +23 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +131 -27
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/explain_subscriber.rb +1 -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 +29 -8
- data/lib/active_record/fixtures.rb +169 -99
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +13 -10
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +39 -24
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +28 -27
- 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 +4 -0
- data/lib/active_record/middleware/database_selector.rb +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +110 -13
- data/lib/active_record/migration/compatibility.rb +174 -64
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +292 -125
- data/lib/active_record/model_schema.rb +113 -112
- data/lib/active_record/nested_attributes.rb +35 -9
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +177 -345
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +102 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +34 -9
- data/lib/active_record/railtie.rb +153 -100
- data/lib/active_record/railties/controller_runtime.rb +24 -10
- data/lib/active_record/railties/databases.rake +148 -152
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +278 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +293 -108
- data/lib/active_record/relation/delegation.rb +31 -20
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +625 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +602 -96
- data/lib/active_record/result.rb +55 -52
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +76 -30
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +82 -30
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +3 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +29 -8
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +191 -121
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +174 -152
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +109 -27
- data/lib/active_record/translation.rb +1 -3
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +9 -7
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +12 -6
- 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 +63 -14
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +266 -30
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +59 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
|
@@ -3,17 +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
10
|
include ActiveModel::ForbiddenAttributesProtection
|
|
12
11
|
|
|
13
|
-
# WhereChain objects act as placeholder for queries in which
|
|
14
|
-
# 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.
|
|
15
14
|
class WhereChain
|
|
16
|
-
def initialize(scope)
|
|
15
|
+
def initialize(scope) # :nodoc:
|
|
17
16
|
@scope = scope
|
|
18
17
|
end
|
|
19
18
|
|
|
@@ -39,7 +38,14 @@ module ActiveRecord
|
|
|
39
38
|
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
|
40
39
|
#
|
|
41
40
|
# User.where.not(name: "Jon", role: "admin")
|
|
42
|
-
# # 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
|
+
# # => []
|
|
43
49
|
def not(opts, *rest)
|
|
44
50
|
where_clause = @scope.send(:build_where_clause, opts, rest)
|
|
45
51
|
|
|
@@ -66,11 +72,31 @@ module ActiveRecord
|
|
|
66
72
|
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
67
73
|
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
|
68
74
|
# # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
|
|
75
|
+
#
|
|
76
|
+
# You can define join type in the scope and +associated+ will not use `JOIN` by default.
|
|
77
|
+
#
|
|
78
|
+
# Post.left_joins(:author).where.associated(:author)
|
|
79
|
+
# # SELECT "posts".* FROM "posts"
|
|
80
|
+
# # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
|
|
81
|
+
# # WHERE "authors"."id" IS NOT NULL
|
|
82
|
+
#
|
|
83
|
+
# Post.left_joins(:comments).where.associated(:author)
|
|
84
|
+
# # SELECT "posts".* FROM "posts"
|
|
85
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
86
|
+
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
|
87
|
+
# # WHERE "author"."id" IS NOT NULL
|
|
69
88
|
def associated(*associations)
|
|
70
89
|
associations.each do |association|
|
|
71
|
-
reflection =
|
|
72
|
-
@scope.
|
|
73
|
-
|
|
90
|
+
reflection = scope_association_reflection(association)
|
|
91
|
+
unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
|
|
92
|
+
@scope.joins!(association)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if reflection.options[:class_name]
|
|
96
|
+
self.not(association => { reflection.association_primary_key => nil })
|
|
97
|
+
else
|
|
98
|
+
self.not(reflection.table_name => { reflection.association_primary_key => nil })
|
|
99
|
+
end
|
|
74
100
|
end
|
|
75
101
|
|
|
76
102
|
@scope
|
|
@@ -96,13 +122,35 @@ module ActiveRecord
|
|
|
96
122
|
# # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
|
|
97
123
|
def missing(*associations)
|
|
98
124
|
associations.each do |association|
|
|
99
|
-
reflection =
|
|
125
|
+
reflection = scope_association_reflection(association)
|
|
100
126
|
@scope.left_outer_joins!(association)
|
|
101
|
-
|
|
127
|
+
if reflection.options[:class_name]
|
|
128
|
+
@scope.where!(association => { reflection.association_primary_key => nil })
|
|
129
|
+
else
|
|
130
|
+
@scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
|
|
131
|
+
end
|
|
102
132
|
end
|
|
103
133
|
|
|
104
134
|
@scope
|
|
105
135
|
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
def scope_association_reflection(association)
|
|
139
|
+
reflection = @scope.klass._reflect_on_association(association)
|
|
140
|
+
unless reflection
|
|
141
|
+
raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
|
|
142
|
+
end
|
|
143
|
+
reflection
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# A wrapper to distinguish CTE joins from other nodes.
|
|
148
|
+
class CTEJoin # :nodoc:
|
|
149
|
+
attr_reader :name
|
|
150
|
+
|
|
151
|
+
def initialize(name)
|
|
152
|
+
@name = name
|
|
153
|
+
end
|
|
106
154
|
end
|
|
107
155
|
|
|
108
156
|
FROZEN_EMPTY_ARRAY = [].freeze
|
|
@@ -125,7 +173,7 @@ module ActiveRecord
|
|
|
125
173
|
end # end
|
|
126
174
|
|
|
127
175
|
def #{method_name}=(value) # def includes_values=(value)
|
|
128
|
-
|
|
176
|
+
assert_modifiable! # assert_modifiable!
|
|
129
177
|
@values[:#{name}] = value # @values[:includes] = value
|
|
130
178
|
end # end
|
|
131
179
|
CODE
|
|
@@ -133,45 +181,69 @@ module ActiveRecord
|
|
|
133
181
|
|
|
134
182
|
alias extensions extending_values
|
|
135
183
|
|
|
136
|
-
# Specify
|
|
137
|
-
#
|
|
184
|
+
# Specify associations +args+ to be eager loaded to prevent N + 1 queries.
|
|
185
|
+
# A separate query is performed for each association, unless a join is
|
|
186
|
+
# required by conditions.
|
|
138
187
|
#
|
|
139
|
-
#
|
|
188
|
+
# For example:
|
|
189
|
+
#
|
|
190
|
+
# users = User.includes(:address).limit(5)
|
|
140
191
|
# users.each do |user|
|
|
141
192
|
# user.address.city
|
|
142
193
|
# end
|
|
143
194
|
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
195
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
|
196
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
|
197
|
+
#
|
|
198
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
|
199
|
+
# are loaded with a single query.
|
|
147
200
|
#
|
|
148
|
-
#
|
|
201
|
+
# Loading the associations in a separate query will often result in a
|
|
202
|
+
# performance improvement over a simple join, as a join can result in many
|
|
203
|
+
# rows that contain redundant data and it performs poorly at scale.
|
|
149
204
|
#
|
|
150
|
-
#
|
|
205
|
+
# You can also specify multiple associations. Each association will result
|
|
206
|
+
# in an additional query:
|
|
151
207
|
#
|
|
152
|
-
#
|
|
208
|
+
# User.includes(:address, :friends).to_a
|
|
209
|
+
# # SELECT "users".* FROM "users"
|
|
210
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
|
211
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
|
153
212
|
#
|
|
154
|
-
#
|
|
213
|
+
# Loading nested associations is possible using a Hash:
|
|
155
214
|
#
|
|
156
|
-
#
|
|
215
|
+
# User.includes(:address, friends: [:address, :followers])
|
|
216
|
+
#
|
|
217
|
+
# === Conditions
|
|
157
218
|
#
|
|
158
219
|
# If you want to add string conditions to your included models, you'll have
|
|
159
220
|
# to explicitly reference them. For example:
|
|
160
221
|
#
|
|
161
|
-
# User.includes(:posts).where('posts.name = ?', 'example')
|
|
222
|
+
# User.includes(:posts).where('posts.name = ?', 'example').to_a
|
|
162
223
|
#
|
|
163
224
|
# Will throw an error, but this will work:
|
|
164
225
|
#
|
|
165
|
-
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
|
226
|
+
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
|
|
227
|
+
# # SELECT "users"."id" AS t0_r0, ... FROM "users"
|
|
228
|
+
# # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
|
229
|
+
# # WHERE "posts"."name" = ? [["name", "example"]]
|
|
230
|
+
#
|
|
231
|
+
# As the <tt>LEFT OUTER JOIN</tt> already contains the posts, the second query for
|
|
232
|
+
# the posts is no longer performed.
|
|
166
233
|
#
|
|
167
234
|
# Note that #includes works with association names while #references needs
|
|
168
235
|
# the actual table name.
|
|
169
236
|
#
|
|
170
|
-
# If you pass the conditions via
|
|
237
|
+
# If you pass the conditions via a Hash, you don't need to call #references
|
|
171
238
|
# explicitly, as #where references the tables for you. For example, this
|
|
172
239
|
# will work correctly:
|
|
173
240
|
#
|
|
174
241
|
# User.includes(:posts).where(posts: { name: 'example' })
|
|
242
|
+
#
|
|
243
|
+
# NOTE: Conditions affect both sides of an association. For example, the
|
|
244
|
+
# above code will return only users that have a post named "example",
|
|
245
|
+
# <em>and will only include posts named "example"</em>, even when a
|
|
246
|
+
# matching user has other additional posts.
|
|
175
247
|
def includes(*args)
|
|
176
248
|
check_if_method_has_arguments!(__callee__, args)
|
|
177
249
|
spawn.includes!(*args)
|
|
@@ -182,12 +254,32 @@ module ActiveRecord
|
|
|
182
254
|
self
|
|
183
255
|
end
|
|
184
256
|
|
|
185
|
-
#
|
|
257
|
+
# Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
|
|
258
|
+
# Performs a single query joining all specified associations. For example:
|
|
259
|
+
#
|
|
260
|
+
# users = User.eager_load(:address).limit(5)
|
|
261
|
+
# users.each do |user|
|
|
262
|
+
# user.address.city
|
|
263
|
+
# end
|
|
264
|
+
#
|
|
265
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
|
266
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
|
267
|
+
# # LIMIT 5
|
|
268
|
+
#
|
|
269
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
|
270
|
+
# are loaded with a single joined query.
|
|
186
271
|
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
272
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
|
273
|
+
# similar to #includes:
|
|
274
|
+
#
|
|
275
|
+
# User.eager_load(:address, friends: [:address, :followers])
|
|
276
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
|
277
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
|
278
|
+
# # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
|
|
279
|
+
# # ...
|
|
280
|
+
#
|
|
281
|
+
# NOTE: Loading the associations in a join can result in many rows that
|
|
282
|
+
# contain redundant data and it performs poorly at scale.
|
|
191
283
|
def eager_load(*args)
|
|
192
284
|
check_if_method_has_arguments!(__callee__, args)
|
|
193
285
|
spawn.eager_load!(*args)
|
|
@@ -198,10 +290,28 @@ module ActiveRecord
|
|
|
198
290
|
self
|
|
199
291
|
end
|
|
200
292
|
|
|
201
|
-
#
|
|
293
|
+
# Specify associations +args+ to be eager loaded using separate queries.
|
|
294
|
+
# A separate query is performed for each association.
|
|
295
|
+
#
|
|
296
|
+
# users = User.preload(:address).limit(5)
|
|
297
|
+
# users.each do |user|
|
|
298
|
+
# user.address.city
|
|
299
|
+
# end
|
|
202
300
|
#
|
|
203
|
-
#
|
|
204
|
-
# # SELECT "
|
|
301
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
|
302
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
|
303
|
+
#
|
|
304
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
|
305
|
+
# are loaded with a separate query.
|
|
306
|
+
#
|
|
307
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
|
308
|
+
# similar to #includes:
|
|
309
|
+
#
|
|
310
|
+
# User.preload(:address, friends: [:address, :followers])
|
|
311
|
+
# # SELECT "users".* FROM "users"
|
|
312
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
|
313
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
|
314
|
+
# # SELECT ...
|
|
205
315
|
def preload(*args)
|
|
206
316
|
check_if_method_has_arguments!(__callee__, args)
|
|
207
317
|
spawn.preload!(*args)
|
|
@@ -226,7 +336,7 @@ module ActiveRecord
|
|
|
226
336
|
end
|
|
227
337
|
|
|
228
338
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
|
229
|
-
# and should therefore be
|
|
339
|
+
# and should therefore be +JOIN+ed in any query rather than loaded separately.
|
|
230
340
|
# This method only works in conjunction with #includes.
|
|
231
341
|
# See #includes for more details.
|
|
232
342
|
#
|
|
@@ -270,10 +380,18 @@ module ActiveRecord
|
|
|
270
380
|
# Model.select(:field, :other_field, :and_one_more)
|
|
271
381
|
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
|
|
272
382
|
#
|
|
383
|
+
# The argument also can be a hash of fields and aliases.
|
|
384
|
+
#
|
|
385
|
+
# Model.select(models: { field: :alias, other_field: :other_alias })
|
|
386
|
+
# # => [#<Model id: nil, alias: "value", other_alias: "value">]
|
|
387
|
+
#
|
|
388
|
+
# Model.select(models: [:field, :other_field])
|
|
389
|
+
# # => [#<Model id: nil, field: "value", other_field: "value">]
|
|
390
|
+
#
|
|
273
391
|
# You can also use one or more strings, which will be used unchanged as SELECT fields.
|
|
274
392
|
#
|
|
275
393
|
# Model.select('field AS field_one', 'other_field AS field_two')
|
|
276
|
-
# # => [#<Model id: nil,
|
|
394
|
+
# # => [#<Model id: nil, field_one: "value", field_two: "value">]
|
|
277
395
|
#
|
|
278
396
|
# If an alias was specified, it will be accessible from the resulting objects:
|
|
279
397
|
#
|
|
@@ -284,7 +402,7 @@ module ActiveRecord
|
|
|
284
402
|
# except +id+ will throw ActiveModel::MissingAttributeError:
|
|
285
403
|
#
|
|
286
404
|
# Model.select(:field).first.other_field
|
|
287
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
|
405
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
|
|
288
406
|
def select(*fields)
|
|
289
407
|
if block_given?
|
|
290
408
|
if fields.any?
|
|
@@ -295,6 +413,8 @@ module ActiveRecord
|
|
|
295
413
|
end
|
|
296
414
|
|
|
297
415
|
check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
|
|
416
|
+
|
|
417
|
+
fields = process_select_args(fields)
|
|
298
418
|
spawn._select!(*fields)
|
|
299
419
|
end
|
|
300
420
|
|
|
@@ -303,6 +423,102 @@ module ActiveRecord
|
|
|
303
423
|
self
|
|
304
424
|
end
|
|
305
425
|
|
|
426
|
+
# Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
|
427
|
+
#
|
|
428
|
+
# Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to
|
|
429
|
+
# use CTE's with MySQL 5.7.
|
|
430
|
+
#
|
|
431
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
|
|
432
|
+
# # => ActiveRecord::Relation
|
|
433
|
+
# # WITH posts_with_tags AS (
|
|
434
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
|
435
|
+
# # )
|
|
436
|
+
# # SELECT * FROM posts
|
|
437
|
+
#
|
|
438
|
+
# You can also pass an array of sub-queries to be joined in a +UNION ALL+.
|
|
439
|
+
#
|
|
440
|
+
# Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
|
|
441
|
+
# # => ActiveRecord::Relation
|
|
442
|
+
# # WITH posts_with_tags_or_comments AS (
|
|
443
|
+
# # (SELECT * FROM posts WHERE (tags_count > 0))
|
|
444
|
+
# # UNION ALL
|
|
445
|
+
# # (SELECT * FROM posts WHERE (comments_count > 0))
|
|
446
|
+
# # )
|
|
447
|
+
# # SELECT * FROM posts
|
|
448
|
+
#
|
|
449
|
+
# Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
|
|
450
|
+
#
|
|
451
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
|
|
452
|
+
# # => ActiveRecord::Relation
|
|
453
|
+
# # WITH posts_with_tags AS (
|
|
454
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
|
455
|
+
# # )
|
|
456
|
+
# # SELECT * FROM posts_with_tags AS posts
|
|
457
|
+
#
|
|
458
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
|
|
459
|
+
# # => ActiveRecord::Relation
|
|
460
|
+
# # WITH posts_with_tags AS (
|
|
461
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
|
462
|
+
# # )
|
|
463
|
+
# # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
|
|
464
|
+
#
|
|
465
|
+
# It is recommended to pass a query as ActiveRecord::Relation. If that is not possible
|
|
466
|
+
# and you have verified it is safe for the database, you can pass it as SQL literal
|
|
467
|
+
# using +Arel+.
|
|
468
|
+
#
|
|
469
|
+
# Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
|
|
470
|
+
#
|
|
471
|
+
# Great caution should be taken to avoid SQL injection vulnerabilities. This method should not
|
|
472
|
+
# be used with unsafe values that include unsanitized input.
|
|
473
|
+
#
|
|
474
|
+
# To add multiple CTEs just pass multiple key-value pairs
|
|
475
|
+
#
|
|
476
|
+
# Post.with(
|
|
477
|
+
# posts_with_comments: Post.where("comments_count > ?", 0),
|
|
478
|
+
# posts_with_tags: Post.where("tags_count > ?", 0)
|
|
479
|
+
# )
|
|
480
|
+
#
|
|
481
|
+
# or chain multiple +.with+ calls
|
|
482
|
+
#
|
|
483
|
+
# Post
|
|
484
|
+
# .with(posts_with_comments: Post.where("comments_count > ?", 0))
|
|
485
|
+
# .with(posts_with_tags: Post.where("tags_count > ?", 0))
|
|
486
|
+
def with(*args)
|
|
487
|
+
raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
|
|
488
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
489
|
+
spawn.with!(*args)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Like #with, but modifies relation in place.
|
|
493
|
+
def with!(*args) # :nodoc:
|
|
494
|
+
self.with_values += args
|
|
495
|
+
self
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
|
499
|
+
#
|
|
500
|
+
# Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
|
|
501
|
+
# # => ActiveRecord::Relation
|
|
502
|
+
# # WITH post_and_replies AS (
|
|
503
|
+
# # (SELECT * FROM posts WHERE id = 42)
|
|
504
|
+
# # UNION ALL
|
|
505
|
+
# # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
|
|
506
|
+
# # )
|
|
507
|
+
# # SELECT * FROM posts
|
|
508
|
+
#
|
|
509
|
+
# See `#with` for more information.
|
|
510
|
+
def with_recursive(*args)
|
|
511
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
512
|
+
spawn.with_recursive!(*args)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# Like #with_recursive but modifies the relation in place.
|
|
516
|
+
def with_recursive!(*args) # :nodoc:
|
|
517
|
+
self.with_values += args
|
|
518
|
+
@with_is_recursive = true
|
|
519
|
+
self
|
|
520
|
+
end
|
|
521
|
+
|
|
306
522
|
# Allows you to change a previously set select statement.
|
|
307
523
|
#
|
|
308
524
|
# Post.select(:title, :body)
|
|
@@ -315,6 +531,7 @@ module ActiveRecord
|
|
|
315
531
|
# Note that we're unscoping the entire select statement.
|
|
316
532
|
def reselect(*args)
|
|
317
533
|
check_if_method_has_arguments!(__callee__, args)
|
|
534
|
+
args = process_select_args(args)
|
|
318
535
|
spawn.reselect!(*args)
|
|
319
536
|
end
|
|
320
537
|
|
|
@@ -354,6 +571,27 @@ module ActiveRecord
|
|
|
354
571
|
self
|
|
355
572
|
end
|
|
356
573
|
|
|
574
|
+
# Allows you to change a previously set group statement.
|
|
575
|
+
#
|
|
576
|
+
# Post.group(:title, :body)
|
|
577
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
|
|
578
|
+
#
|
|
579
|
+
# Post.group(:title, :body).regroup(:title)
|
|
580
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
|
|
581
|
+
#
|
|
582
|
+
# This is short-hand for <tt>unscope(:group).group(fields)</tt>.
|
|
583
|
+
# Note that we're unscoping the entire group statement.
|
|
584
|
+
def regroup(*args)
|
|
585
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
586
|
+
spawn.regroup!(*args)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Same as #regroup but operates on relation in-place instead of copying.
|
|
590
|
+
def regroup!(*args) # :nodoc:
|
|
591
|
+
self.group_values = args
|
|
592
|
+
self
|
|
593
|
+
end
|
|
594
|
+
|
|
357
595
|
# Applies an <code>ORDER BY</code> clause to a query.
|
|
358
596
|
#
|
|
359
597
|
# #order accepts arguments in one of several formats.
|
|
@@ -402,7 +640,7 @@ module ActiveRecord
|
|
|
402
640
|
# User.order(Arel.sql('end_date - start_date'))
|
|
403
641
|
# # SELECT "users".* FROM "users" ORDER BY end_date - start_date
|
|
404
642
|
#
|
|
405
|
-
# Custom query syntax, like JSON columns for
|
|
643
|
+
# Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
|
|
406
644
|
#
|
|
407
645
|
# User.order(Arel.sql("payload->>'kind'"))
|
|
408
646
|
# # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
|
|
@@ -420,22 +658,64 @@ module ActiveRecord
|
|
|
420
658
|
self
|
|
421
659
|
end
|
|
422
660
|
|
|
423
|
-
#
|
|
424
|
-
#
|
|
661
|
+
# Applies an <tt>ORDER BY</tt> clause based on a given +column+,
|
|
662
|
+
# ordered and filtered by a specific set of +values+.
|
|
425
663
|
#
|
|
426
664
|
# User.in_order_of(:id, [1, 5, 3])
|
|
427
|
-
# # SELECT "users".* FROM "users"
|
|
665
|
+
# # SELECT "users".* FROM "users"
|
|
666
|
+
# # WHERE "users"."id" IN (1, 5, 3)
|
|
667
|
+
# # ORDER BY CASE
|
|
668
|
+
# # WHEN "users"."id" = 1 THEN 1
|
|
669
|
+
# # WHEN "users"."id" = 5 THEN 2
|
|
670
|
+
# # WHEN "users"."id" = 3 THEN 3
|
|
671
|
+
# # END ASC
|
|
672
|
+
#
|
|
673
|
+
# +column+ can point to an enum column; the actual query generated may be different depending
|
|
674
|
+
# on the database adapter and the column definition.
|
|
675
|
+
#
|
|
676
|
+
# class Conversation < ActiveRecord::Base
|
|
677
|
+
# enum :status, [ :active, :archived ]
|
|
678
|
+
# end
|
|
679
|
+
#
|
|
680
|
+
# Conversation.in_order_of(:status, [:archived, :active])
|
|
681
|
+
# # SELECT "conversations".* FROM "conversations"
|
|
682
|
+
# # WHERE "conversations"."status" IN (1, 0)
|
|
683
|
+
# # ORDER BY CASE
|
|
684
|
+
# # WHEN "conversations"."status" = 1 THEN 1
|
|
685
|
+
# # WHEN "conversations"."status" = 0 THEN 2
|
|
686
|
+
# # END ASC
|
|
687
|
+
#
|
|
688
|
+
# +values+ can also include +nil+.
|
|
689
|
+
#
|
|
690
|
+
# Conversation.in_order_of(:status, [nil, :archived, :active])
|
|
691
|
+
# # SELECT "conversations".* FROM "conversations"
|
|
692
|
+
# # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
|
|
693
|
+
# # ORDER BY CASE
|
|
694
|
+
# # WHEN "conversations"."status" IS NULL THEN 1
|
|
695
|
+
# # WHEN "conversations"."status" = 1 THEN 2
|
|
696
|
+
# # WHEN "conversations"."status" = 0 THEN 3
|
|
697
|
+
# # END ASC
|
|
428
698
|
#
|
|
429
699
|
def in_order_of(column, values)
|
|
430
|
-
klass.disallow_raw_sql!([column], permit:
|
|
700
|
+
klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
|
|
701
|
+
return spawn.none! if values.empty?
|
|
431
702
|
|
|
432
703
|
references = column_references([column])
|
|
433
704
|
self.references_values |= references unless references.empty?
|
|
434
705
|
|
|
435
706
|
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
|
436
|
-
|
|
707
|
+
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
|
|
708
|
+
|
|
709
|
+
where_clause =
|
|
710
|
+
if values.include?(nil)
|
|
711
|
+
arel_column.in(values.compact).or(arel_column.eq(nil))
|
|
712
|
+
else
|
|
713
|
+
arel_column.in(values)
|
|
714
|
+
end
|
|
437
715
|
|
|
438
|
-
spawn
|
|
716
|
+
spawn
|
|
717
|
+
.order!(build_case_for_value_position(arel_column, values))
|
|
718
|
+
.where!(where_clause)
|
|
439
719
|
end
|
|
440
720
|
|
|
441
721
|
# Replaces any existing order defined on the relation with the specified order.
|
|
@@ -446,7 +726,7 @@ module ActiveRecord
|
|
|
446
726
|
#
|
|
447
727
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
|
448
728
|
#
|
|
449
|
-
# generates a query with
|
|
729
|
+
# generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
|
|
450
730
|
def reorder(*args)
|
|
451
731
|
check_if_method_has_arguments!(__callee__, args) do
|
|
452
732
|
sanitize_order_arguments(args)
|
|
@@ -465,7 +745,8 @@ module ActiveRecord
|
|
|
465
745
|
|
|
466
746
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
|
467
747
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
|
468
|
-
:includes, :
|
|
748
|
+
:includes, :eager_load, :preload, :from, :readonly,
|
|
749
|
+
:having, :optimizer_hints, :with])
|
|
469
750
|
|
|
470
751
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
|
471
752
|
# This is useful when passing around chains of relations and would like to
|
|
@@ -515,7 +796,7 @@ module ActiveRecord
|
|
|
515
796
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
|
516
797
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
|
517
798
|
end
|
|
518
|
-
|
|
799
|
+
assert_modifiable!
|
|
519
800
|
@values.delete(scope)
|
|
520
801
|
when Hash
|
|
521
802
|
scope.each do |key, target_value|
|
|
@@ -575,7 +856,7 @@ module ActiveRecord
|
|
|
575
856
|
# Performs LEFT OUTER JOINs on +args+:
|
|
576
857
|
#
|
|
577
858
|
# User.left_outer_joins(:posts)
|
|
578
|
-
#
|
|
859
|
+
# # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
|
579
860
|
#
|
|
580
861
|
def left_outer_joins(*args)
|
|
581
862
|
check_if_method_has_arguments!(__callee__, args)
|
|
@@ -595,7 +876,7 @@ module ActiveRecord
|
|
|
595
876
|
# SQL is given as an illustration; the actual query generated may be different depending
|
|
596
877
|
# on the database adapter.
|
|
597
878
|
#
|
|
598
|
-
# ===
|
|
879
|
+
# === \String
|
|
599
880
|
#
|
|
600
881
|
# A single string, without additional arguments, is passed to the query
|
|
601
882
|
# constructor as an SQL fragment, and used in the where clause of the query.
|
|
@@ -607,7 +888,7 @@ module ActiveRecord
|
|
|
607
888
|
# to injection attacks if not done properly. As an alternative, it is recommended
|
|
608
889
|
# to use one of the following methods.
|
|
609
890
|
#
|
|
610
|
-
# ===
|
|
891
|
+
# === \Array
|
|
611
892
|
#
|
|
612
893
|
# If an array is passed, then the first element of the array is treated as a template, and
|
|
613
894
|
# the remaining elements are inserted into the template to generate the condition.
|
|
@@ -647,7 +928,7 @@ module ActiveRecord
|
|
|
647
928
|
# dependencies on the underlying database. If your code is intended for general consumption,
|
|
648
929
|
# test with multiple database backends.
|
|
649
930
|
#
|
|
650
|
-
# ===
|
|
931
|
+
# === \Hash
|
|
651
932
|
#
|
|
652
933
|
# #where will also accept a hash condition, in which the keys are fields and the values
|
|
653
934
|
# are values to be searched for.
|
|
@@ -681,6 +962,12 @@ module ActiveRecord
|
|
|
681
962
|
# PriceEstimate.where(estimate_of: treasure)
|
|
682
963
|
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
|
683
964
|
#
|
|
965
|
+
# Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
|
|
966
|
+
# an array of columns with an array of tuples as values.
|
|
967
|
+
#
|
|
968
|
+
# Article.where([:author_id, :id] => [[15, 1], [15, 2]])
|
|
969
|
+
# # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
|
|
970
|
+
#
|
|
684
971
|
# === Joins
|
|
685
972
|
#
|
|
686
973
|
# If the relation is the result of a join, you may create a condition which uses any of the
|
|
@@ -693,17 +980,31 @@ module ActiveRecord
|
|
|
693
980
|
# User.joins(:posts).where("posts.published" => true)
|
|
694
981
|
# User.joins(:posts).where(posts: { published: true })
|
|
695
982
|
#
|
|
696
|
-
# ===
|
|
983
|
+
# === No Argument
|
|
697
984
|
#
|
|
698
985
|
# If no argument is passed, #where returns a new instance of WhereChain, that
|
|
699
|
-
# can be chained with #not
|
|
986
|
+
# can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
|
|
987
|
+
#
|
|
988
|
+
# Chaining with WhereChain#not:
|
|
700
989
|
#
|
|
701
990
|
# User.where.not(name: "Jon")
|
|
702
991
|
# # SELECT * FROM users WHERE name != 'Jon'
|
|
703
992
|
#
|
|
704
|
-
#
|
|
993
|
+
# Chaining with WhereChain#associated:
|
|
994
|
+
#
|
|
995
|
+
# Post.where.associated(:author)
|
|
996
|
+
# # SELECT "posts".* FROM "posts"
|
|
997
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
998
|
+
# # WHERE "authors"."id" IS NOT NULL
|
|
705
999
|
#
|
|
706
|
-
#
|
|
1000
|
+
# Chaining with WhereChain#missing:
|
|
1001
|
+
#
|
|
1002
|
+
# Post.where.missing(:author)
|
|
1003
|
+
# # SELECT "posts".* FROM "posts"
|
|
1004
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
1005
|
+
# # WHERE "authors"."id" IS NULL
|
|
1006
|
+
#
|
|
1007
|
+
# === Blank Condition
|
|
707
1008
|
#
|
|
708
1009
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
|
709
1010
|
# the current relation.
|
|
@@ -736,6 +1037,8 @@ module ActiveRecord
|
|
|
736
1037
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
|
737
1038
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
|
738
1039
|
def rewhere(conditions)
|
|
1040
|
+
return unscope(:where) if conditions.nil?
|
|
1041
|
+
|
|
739
1042
|
scope = spawn
|
|
740
1043
|
where_clause = scope.build_where_clause(conditions)
|
|
741
1044
|
|
|
@@ -841,7 +1144,11 @@ module ActiveRecord
|
|
|
841
1144
|
#
|
|
842
1145
|
def or(other)
|
|
843
1146
|
if other.is_a?(Relation)
|
|
844
|
-
|
|
1147
|
+
if @none
|
|
1148
|
+
other.spawn
|
|
1149
|
+
else
|
|
1150
|
+
spawn.or!(other)
|
|
1151
|
+
end
|
|
845
1152
|
else
|
|
846
1153
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
|
847
1154
|
end
|
|
@@ -854,7 +1161,7 @@ module ActiveRecord
|
|
|
854
1161
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
|
855
1162
|
end
|
|
856
1163
|
|
|
857
|
-
self.where_clause =
|
|
1164
|
+
self.where_clause = where_clause.or(other.where_clause)
|
|
858
1165
|
self.having_clause = having_clause.or(other.having_clause)
|
|
859
1166
|
self.references_values |= other.references_values
|
|
860
1167
|
|
|
@@ -954,15 +1261,29 @@ module ActiveRecord
|
|
|
954
1261
|
end
|
|
955
1262
|
|
|
956
1263
|
def none! # :nodoc:
|
|
957
|
-
|
|
1264
|
+
unless @none
|
|
1265
|
+
where!("1=0")
|
|
1266
|
+
@none = true
|
|
1267
|
+
end
|
|
1268
|
+
self
|
|
958
1269
|
end
|
|
959
1270
|
|
|
960
|
-
|
|
961
|
-
|
|
1271
|
+
def null_relation? # :nodoc:
|
|
1272
|
+
@none
|
|
1273
|
+
end
|
|
1274
|
+
|
|
1275
|
+
# Mark a relation as readonly. Attempting to update a record will result in
|
|
1276
|
+
# an error.
|
|
962
1277
|
#
|
|
963
1278
|
# users = User.readonly
|
|
964
1279
|
# users.first.save
|
|
965
1280
|
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
|
1281
|
+
#
|
|
1282
|
+
# To make a readonly relation writable, pass +false+.
|
|
1283
|
+
#
|
|
1284
|
+
# users.readonly(false)
|
|
1285
|
+
# users.first.save
|
|
1286
|
+
# => true
|
|
966
1287
|
def readonly(value = true)
|
|
967
1288
|
spawn.readonly!(value)
|
|
968
1289
|
end
|
|
@@ -1079,7 +1400,7 @@ module ActiveRecord
|
|
|
1079
1400
|
#
|
|
1080
1401
|
# The object returned is a relation, which can be further extended.
|
|
1081
1402
|
#
|
|
1082
|
-
# === Using a
|
|
1403
|
+
# === Using a \Module
|
|
1083
1404
|
#
|
|
1084
1405
|
# module Pagination
|
|
1085
1406
|
# def page(number)
|
|
@@ -1094,7 +1415,7 @@ module ActiveRecord
|
|
|
1094
1415
|
#
|
|
1095
1416
|
# scope = Model.all.extending(Pagination, SomethingElse)
|
|
1096
1417
|
#
|
|
1097
|
-
# === Using a
|
|
1418
|
+
# === Using a Block
|
|
1098
1419
|
#
|
|
1099
1420
|
# scope = Model.all.extending do
|
|
1100
1421
|
# def page(number)
|
|
@@ -1181,6 +1502,8 @@ module ActiveRecord
|
|
|
1181
1502
|
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
|
1182
1503
|
#
|
|
1183
1504
|
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
|
1505
|
+
#
|
|
1506
|
+
# Some escaping is performed, however untrusted user input should not be used.
|
|
1184
1507
|
def annotate(*args)
|
|
1185
1508
|
check_if_method_has_arguments!(__callee__, args)
|
|
1186
1509
|
spawn.annotate!(*args)
|
|
@@ -1209,6 +1532,9 @@ module ActiveRecord
|
|
|
1209
1532
|
# Post.excluding(post_one, post_two)
|
|
1210
1533
|
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
|
|
1211
1534
|
#
|
|
1535
|
+
# Post.excluding(Post.drafts)
|
|
1536
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
|
|
1537
|
+
#
|
|
1212
1538
|
# This can also be called on associations. As with the above example, either
|
|
1213
1539
|
# a single record of collection thereof may be specified:
|
|
1214
1540
|
#
|
|
@@ -1224,14 +1550,15 @@ module ActiveRecord
|
|
|
1224
1550
|
# is passed in) are not instances of the same model that the relation is
|
|
1225
1551
|
# scoping.
|
|
1226
1552
|
def excluding(*records)
|
|
1553
|
+
relations = records.extract! { |element| element.is_a?(Relation) }
|
|
1227
1554
|
records.flatten!(1)
|
|
1228
1555
|
records.compact!
|
|
1229
1556
|
|
|
1230
|
-
unless records.all?(klass)
|
|
1557
|
+
unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
|
|
1231
1558
|
raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
|
|
1232
1559
|
end
|
|
1233
1560
|
|
|
1234
|
-
spawn.excluding!(records)
|
|
1561
|
+
spawn.excluding!(records + relations.flat_map(&:ids))
|
|
1235
1562
|
end
|
|
1236
1563
|
alias :without :excluding
|
|
1237
1564
|
|
|
@@ -1243,7 +1570,7 @@ module ActiveRecord
|
|
|
1243
1570
|
|
|
1244
1571
|
# Returns the Arel object associated with the relation.
|
|
1245
1572
|
def arel(aliases = nil) # :nodoc:
|
|
1246
|
-
@arel ||= build_arel(aliases)
|
|
1573
|
+
@arel ||= with_connection { |c| build_arel(c, aliases) }
|
|
1247
1574
|
end
|
|
1248
1575
|
|
|
1249
1576
|
def construct_join_dependency(associations, join_type) # :nodoc:
|
|
@@ -1264,13 +1591,29 @@ module ActiveRecord
|
|
|
1264
1591
|
def build_where_clause(opts, rest = []) # :nodoc:
|
|
1265
1592
|
opts = sanitize_forbidden_attributes(opts)
|
|
1266
1593
|
|
|
1594
|
+
if opts.is_a?(Array)
|
|
1595
|
+
opts, *rest = opts
|
|
1596
|
+
end
|
|
1597
|
+
|
|
1267
1598
|
case opts
|
|
1268
|
-
when String
|
|
1269
|
-
|
|
1599
|
+
when String
|
|
1600
|
+
if rest.empty?
|
|
1601
|
+
parts = [Arel.sql(opts)]
|
|
1602
|
+
elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
|
|
1603
|
+
parts = [build_named_bound_sql_literal(opts, rest.first)]
|
|
1604
|
+
elsif opts.include?("?")
|
|
1605
|
+
parts = [build_bound_sql_literal(opts, rest)]
|
|
1606
|
+
else
|
|
1607
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
|
1608
|
+
end
|
|
1270
1609
|
when Hash
|
|
1271
1610
|
opts = opts.transform_keys do |key|
|
|
1272
|
-
|
|
1273
|
-
|
|
1611
|
+
if key.is_a?(Array)
|
|
1612
|
+
key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
|
|
1613
|
+
else
|
|
1614
|
+
key = key.to_s
|
|
1615
|
+
klass.attribute_aliases[key] || key
|
|
1616
|
+
end
|
|
1274
1617
|
end
|
|
1275
1618
|
references = PredicateBuilder.references(opts)
|
|
1276
1619
|
self.references_values |= references unless references.empty?
|
|
@@ -1288,7 +1631,56 @@ module ActiveRecord
|
|
|
1288
1631
|
end
|
|
1289
1632
|
alias :build_having_clause :build_where_clause
|
|
1290
1633
|
|
|
1634
|
+
def async!
|
|
1635
|
+
@async = true
|
|
1636
|
+
self
|
|
1637
|
+
end
|
|
1638
|
+
|
|
1291
1639
|
private
|
|
1640
|
+
def async
|
|
1641
|
+
spawn.async!
|
|
1642
|
+
end
|
|
1643
|
+
|
|
1644
|
+
def build_named_bound_sql_literal(statement, values)
|
|
1645
|
+
bound_values = values.transform_values do |value|
|
|
1646
|
+
if ActiveRecord::Relation === value
|
|
1647
|
+
Arel.sql(value.to_sql)
|
|
1648
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
|
1649
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
|
1650
|
+
values.empty? ? nil : values
|
|
1651
|
+
else
|
|
1652
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
|
1653
|
+
value
|
|
1654
|
+
end
|
|
1655
|
+
end
|
|
1656
|
+
|
|
1657
|
+
begin
|
|
1658
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
|
|
1659
|
+
rescue Arel::BindError => error
|
|
1660
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
|
1661
|
+
end
|
|
1662
|
+
end
|
|
1663
|
+
|
|
1664
|
+
def build_bound_sql_literal(statement, values)
|
|
1665
|
+
bound_values = values.map do |value|
|
|
1666
|
+
if ActiveRecord::Relation === value
|
|
1667
|
+
Arel.sql(value.to_sql)
|
|
1668
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
|
1669
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
|
1670
|
+
values.empty? ? nil : values
|
|
1671
|
+
else
|
|
1672
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
|
1673
|
+
value
|
|
1674
|
+
end
|
|
1675
|
+
end
|
|
1676
|
+
|
|
1677
|
+
begin
|
|
1678
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
|
|
1679
|
+
rescue Arel::BindError => error
|
|
1680
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
|
1681
|
+
end
|
|
1682
|
+
end
|
|
1683
|
+
|
|
1292
1684
|
def lookup_table_klass_from_join_dependencies(table_name)
|
|
1293
1685
|
each_join_dependencies do |join|
|
|
1294
1686
|
return join.base_klass if table_name == join.table_name
|
|
@@ -1303,22 +1695,21 @@ module ActiveRecord
|
|
|
1303
1695
|
end
|
|
1304
1696
|
|
|
1305
1697
|
def build_join_dependencies
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1698
|
+
joins = joins_values | left_outer_joins_values
|
|
1699
|
+
joins |= eager_load_values unless eager_load_values.empty?
|
|
1700
|
+
joins |= includes_values unless includes_values.empty?
|
|
1309
1701
|
|
|
1310
1702
|
join_dependencies = []
|
|
1311
1703
|
join_dependencies.unshift construct_join_dependency(
|
|
1312
|
-
|
|
1704
|
+
select_named_joins(joins, join_dependencies), nil
|
|
1313
1705
|
)
|
|
1314
1706
|
end
|
|
1315
1707
|
|
|
1316
|
-
def
|
|
1317
|
-
raise
|
|
1318
|
-
raise ImmutableRelation if defined?(@arel) && @arel
|
|
1708
|
+
def assert_modifiable!
|
|
1709
|
+
raise UnmodifiableRelation if @loaded || @arel
|
|
1319
1710
|
end
|
|
1320
1711
|
|
|
1321
|
-
def build_arel(aliases = nil)
|
|
1712
|
+
def build_arel(connection, aliases = nil)
|
|
1322
1713
|
arel = Arel::SelectManager.new(table)
|
|
1323
1714
|
|
|
1324
1715
|
build_joins(arel.join_sources, aliases)
|
|
@@ -1330,6 +1721,7 @@ module ActiveRecord
|
|
|
1330
1721
|
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
|
1331
1722
|
|
|
1332
1723
|
build_order(arel)
|
|
1724
|
+
build_with(arel)
|
|
1333
1725
|
build_select(arel)
|
|
1334
1726
|
|
|
1335
1727
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
|
@@ -1365,6 +1757,18 @@ module ActiveRecord
|
|
|
1365
1757
|
end
|
|
1366
1758
|
end
|
|
1367
1759
|
|
|
1760
|
+
def select_named_joins(join_names, stashed_joins = nil, &block)
|
|
1761
|
+
cte_joins, associations = join_names.partition do |join_name|
|
|
1762
|
+
Symbol === join_name && with_values.any? { _1.key?(join_name) }
|
|
1763
|
+
end
|
|
1764
|
+
|
|
1765
|
+
cte_joins.each do |cte_name|
|
|
1766
|
+
block&.call(CTEJoin.new(cte_name))
|
|
1767
|
+
end
|
|
1768
|
+
|
|
1769
|
+
select_association_list(associations, stashed_joins, &block)
|
|
1770
|
+
end
|
|
1771
|
+
|
|
1368
1772
|
def select_association_list(associations, stashed_joins = nil)
|
|
1369
1773
|
result = []
|
|
1370
1774
|
associations.each do |association|
|
|
@@ -1380,20 +1784,21 @@ module ActiveRecord
|
|
|
1380
1784
|
result
|
|
1381
1785
|
end
|
|
1382
1786
|
|
|
1383
|
-
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
|
1384
|
-
end
|
|
1385
|
-
|
|
1386
1787
|
def build_join_buckets
|
|
1387
1788
|
buckets = Hash.new { |h, k| h[k] = [] }
|
|
1388
1789
|
|
|
1389
1790
|
unless left_outer_joins_values.empty?
|
|
1390
1791
|
stashed_left_joins = []
|
|
1391
|
-
left_joins =
|
|
1392
|
-
|
|
1792
|
+
left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
|
|
1793
|
+
if left_join.is_a?(CTEJoin)
|
|
1794
|
+
buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
|
|
1795
|
+
else
|
|
1796
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
|
1797
|
+
end
|
|
1393
1798
|
end
|
|
1394
1799
|
|
|
1395
1800
|
if joins_values.empty?
|
|
1396
|
-
buckets[:
|
|
1801
|
+
buckets[:named_join] = left_joins
|
|
1397
1802
|
buckets[:stashed_join] = stashed_left_joins
|
|
1398
1803
|
return buckets, Arel::Nodes::OuterJoin
|
|
1399
1804
|
else
|
|
@@ -1419,9 +1824,11 @@ module ActiveRecord
|
|
|
1419
1824
|
end
|
|
1420
1825
|
end
|
|
1421
1826
|
|
|
1422
|
-
buckets[:
|
|
1827
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
|
1423
1828
|
if join.is_a?(Arel::Nodes::Join)
|
|
1424
1829
|
buckets[:join_node] << join
|
|
1830
|
+
elsif join.is_a?(CTEJoin)
|
|
1831
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
|
1425
1832
|
else
|
|
1426
1833
|
raise "unknown class: %s" % join.class.name
|
|
1427
1834
|
end
|
|
@@ -1438,16 +1845,16 @@ module ActiveRecord
|
|
|
1438
1845
|
|
|
1439
1846
|
buckets, join_type = build_join_buckets
|
|
1440
1847
|
|
|
1441
|
-
|
|
1442
|
-
stashed_joins
|
|
1443
|
-
leading_joins
|
|
1444
|
-
join_nodes
|
|
1848
|
+
named_joins = buckets[:named_join]
|
|
1849
|
+
stashed_joins = buckets[:stashed_join]
|
|
1850
|
+
leading_joins = buckets[:leading_join]
|
|
1851
|
+
join_nodes = buckets[:join_node]
|
|
1445
1852
|
|
|
1446
1853
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
|
1447
1854
|
|
|
1448
|
-
unless
|
|
1855
|
+
unless named_joins.empty? && stashed_joins.empty?
|
|
1449
1856
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
|
1450
|
-
join_dependency = construct_join_dependency(
|
|
1857
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
|
1451
1858
|
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
|
1452
1859
|
end
|
|
1453
1860
|
|
|
@@ -1465,17 +1872,56 @@ module ActiveRecord
|
|
|
1465
1872
|
end
|
|
1466
1873
|
end
|
|
1467
1874
|
|
|
1875
|
+
def build_with(arel)
|
|
1876
|
+
return if with_values.empty?
|
|
1877
|
+
|
|
1878
|
+
with_statements = with_values.map do |with_value|
|
|
1879
|
+
raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
|
|
1880
|
+
|
|
1881
|
+
build_with_value_from_hash(with_value)
|
|
1882
|
+
end
|
|
1883
|
+
|
|
1884
|
+
@with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
|
|
1885
|
+
end
|
|
1886
|
+
|
|
1887
|
+
def build_with_value_from_hash(hash)
|
|
1888
|
+
hash.map do |name, value|
|
|
1889
|
+
Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
|
|
1890
|
+
end
|
|
1891
|
+
end
|
|
1892
|
+
|
|
1893
|
+
def build_with_expression_from_value(value)
|
|
1894
|
+
case value
|
|
1895
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
|
1896
|
+
when ActiveRecord::Relation then value.arel
|
|
1897
|
+
when Arel::SelectManager then value
|
|
1898
|
+
when Array then value.map { |q| build_with_expression_from_value(q) }.reduce { |result, value| result.union(:all, value) }
|
|
1899
|
+
else
|
|
1900
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
|
1901
|
+
end
|
|
1902
|
+
end
|
|
1903
|
+
|
|
1904
|
+
def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
|
|
1905
|
+
with_table = Arel::Table.new(name)
|
|
1906
|
+
|
|
1907
|
+
table.join(with_table, kind).on(
|
|
1908
|
+
with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
|
|
1909
|
+
).join_sources.first
|
|
1910
|
+
end
|
|
1911
|
+
|
|
1468
1912
|
def arel_columns(columns)
|
|
1469
1913
|
columns.flat_map do |field|
|
|
1470
1914
|
case field
|
|
1471
1915
|
when Symbol
|
|
1472
1916
|
arel_column(field.to_s) do |attr_name|
|
|
1473
|
-
|
|
1917
|
+
adapter_class.quote_table_name(attr_name)
|
|
1474
1918
|
end
|
|
1475
1919
|
when String
|
|
1476
1920
|
arel_column(field, &:itself)
|
|
1477
1921
|
when Proc
|
|
1478
1922
|
field.call
|
|
1923
|
+
when Hash
|
|
1924
|
+
arel_columns_from_hash(field)
|
|
1479
1925
|
else
|
|
1480
1926
|
field
|
|
1481
1927
|
end
|
|
@@ -1500,7 +1946,7 @@ module ActiveRecord
|
|
|
1500
1946
|
|
|
1501
1947
|
def table_name_matches?(from)
|
|
1502
1948
|
table_name = Regexp.escape(table.name)
|
|
1503
|
-
quoted_table_name = Regexp.escape(
|
|
1949
|
+
quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
|
|
1504
1950
|
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
|
1505
1951
|
end
|
|
1506
1952
|
|
|
@@ -1556,7 +2002,9 @@ module ActiveRecord
|
|
|
1556
2002
|
args.each do |arg|
|
|
1557
2003
|
next unless arg.is_a?(Hash)
|
|
1558
2004
|
arg.each do |_key, value|
|
|
1559
|
-
|
|
2005
|
+
if value.is_a?(Hash)
|
|
2006
|
+
validate_order_args([value])
|
|
2007
|
+
elsif VALID_DIRECTIONS.exclude?(value)
|
|
1560
2008
|
raise ArgumentError,
|
|
1561
2009
|
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
|
1562
2010
|
end
|
|
@@ -1564,10 +2012,14 @@ module ActiveRecord
|
|
|
1564
2012
|
end
|
|
1565
2013
|
end
|
|
1566
2014
|
|
|
2015
|
+
def flattened_args(args)
|
|
2016
|
+
args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
|
|
2017
|
+
end
|
|
2018
|
+
|
|
1567
2019
|
def preprocess_order_args(order_args)
|
|
1568
2020
|
@klass.disallow_raw_sql!(
|
|
1569
|
-
order_args
|
|
1570
|
-
permit:
|
|
2021
|
+
flattened_args(order_args),
|
|
2022
|
+
permit: model.adapter_class.column_name_with_order_matcher
|
|
1571
2023
|
)
|
|
1572
2024
|
|
|
1573
2025
|
validate_order_args(order_args)
|
|
@@ -1581,14 +2033,20 @@ module ActiveRecord
|
|
|
1581
2033
|
when Symbol
|
|
1582
2034
|
order_column(arg.to_s).asc
|
|
1583
2035
|
when Hash
|
|
1584
|
-
arg.map
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
2036
|
+
arg.map do |key, value|
|
|
2037
|
+
if value.is_a?(Hash)
|
|
2038
|
+
value.map do |field, dir|
|
|
2039
|
+
order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
|
|
2040
|
+
end
|
|
1588
2041
|
else
|
|
1589
|
-
|
|
2042
|
+
case key
|
|
2043
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
|
2044
|
+
key.public_send(value.downcase)
|
|
2045
|
+
else
|
|
2046
|
+
order_column(key.to_s).public_send(value.downcase)
|
|
2047
|
+
end
|
|
1590
2048
|
end
|
|
1591
|
-
|
|
2049
|
+
end
|
|
1592
2050
|
else
|
|
1593
2051
|
arg
|
|
1594
2052
|
end
|
|
@@ -1602,16 +2060,32 @@ module ActiveRecord
|
|
|
1602
2060
|
end
|
|
1603
2061
|
|
|
1604
2062
|
def column_references(order_args)
|
|
1605
|
-
|
|
2063
|
+
order_args.flat_map do |arg|
|
|
1606
2064
|
case arg
|
|
1607
2065
|
when String, Symbol
|
|
1608
|
-
arg
|
|
2066
|
+
extract_table_name_from(arg)
|
|
1609
2067
|
when Hash
|
|
1610
|
-
arg
|
|
2068
|
+
arg
|
|
2069
|
+
.map do |key, value|
|
|
2070
|
+
case value
|
|
2071
|
+
when Hash
|
|
2072
|
+
key.to_s
|
|
2073
|
+
else
|
|
2074
|
+
extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
|
|
2075
|
+
end
|
|
2076
|
+
end
|
|
2077
|
+
when Arel::Attribute
|
|
2078
|
+
arg.relation.name
|
|
2079
|
+
when Arel::Nodes::Ordering
|
|
2080
|
+
if arg.expr.is_a?(Arel::Attribute)
|
|
2081
|
+
arg.expr.relation.name
|
|
2082
|
+
end
|
|
1611
2083
|
end
|
|
1612
|
-
end
|
|
1613
|
-
|
|
1614
|
-
|
|
2084
|
+
end.compact
|
|
2085
|
+
end
|
|
2086
|
+
|
|
2087
|
+
def extract_table_name_from(string)
|
|
2088
|
+
string.match(/^\W?(\w+)\W?\./) && $1
|
|
1615
2089
|
end
|
|
1616
2090
|
|
|
1617
2091
|
def order_column(field)
|
|
@@ -1619,11 +2093,20 @@ module ActiveRecord
|
|
|
1619
2093
|
if attr_name == "count" && !group_values.empty?
|
|
1620
2094
|
table[attr_name]
|
|
1621
2095
|
else
|
|
1622
|
-
Arel.sql(
|
|
2096
|
+
Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
|
|
1623
2097
|
end
|
|
1624
2098
|
end
|
|
1625
2099
|
end
|
|
1626
2100
|
|
|
2101
|
+
def build_case_for_value_position(column, values)
|
|
2102
|
+
node = Arel::Nodes::Case.new
|
|
2103
|
+
values.each.with_index(1) do |value, order|
|
|
2104
|
+
node.when(column.eq(value)).then(order)
|
|
2105
|
+
end
|
|
2106
|
+
|
|
2107
|
+
Arel::Nodes::Ascending.new(node)
|
|
2108
|
+
end
|
|
2109
|
+
|
|
1627
2110
|
def resolve_arel_attributes(attrs)
|
|
1628
2111
|
attrs.flat_map do |attr|
|
|
1629
2112
|
case attr
|
|
@@ -1675,6 +2158,41 @@ module ActiveRecord
|
|
|
1675
2158
|
end
|
|
1676
2159
|
end
|
|
1677
2160
|
|
|
2161
|
+
def process_select_args(fields)
|
|
2162
|
+
fields.flat_map do |field|
|
|
2163
|
+
if field.is_a?(Hash)
|
|
2164
|
+
arel_columns_from_hash(field)
|
|
2165
|
+
else
|
|
2166
|
+
field
|
|
2167
|
+
end
|
|
2168
|
+
end
|
|
2169
|
+
end
|
|
2170
|
+
|
|
2171
|
+
def arel_columns_from_hash(fields)
|
|
2172
|
+
fields.flat_map do |key, columns_aliases|
|
|
2173
|
+
case columns_aliases
|
|
2174
|
+
when Hash
|
|
2175
|
+
columns_aliases.map do |column, column_alias|
|
|
2176
|
+
if values[:joins]&.include?(key)
|
|
2177
|
+
references = PredicateBuilder.references({ key.to_s => fields[key] })
|
|
2178
|
+
self.references_values |= references unless references.empty?
|
|
2179
|
+
end
|
|
2180
|
+
arel_column("#{key}.#{column}") do
|
|
2181
|
+
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
|
2182
|
+
end.as(column_alias.to_s)
|
|
2183
|
+
end
|
|
2184
|
+
when Array
|
|
2185
|
+
columns_aliases.map do |column|
|
|
2186
|
+
arel_column("#{key}.#{column}", &:itself)
|
|
2187
|
+
end
|
|
2188
|
+
when String, Symbol
|
|
2189
|
+
arel_column(key.to_s) do
|
|
2190
|
+
predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
|
|
2191
|
+
end.as(columns_aliases.to_s)
|
|
2192
|
+
end
|
|
2193
|
+
end
|
|
2194
|
+
end
|
|
2195
|
+
|
|
1678
2196
|
STRUCTURAL_VALUE_METHODS = (
|
|
1679
2197
|
Relation::VALUE_METHODS -
|
|
1680
2198
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|