activerecord 7.0.4 → 7.1.5.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 +1971 -1243
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +20 -14
- data/lib/active_record/associations/collection_proxy.rb +20 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- 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 +3 -2
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +333 -222
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +21 -8
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -26
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +59 -10
- data/lib/active_record/base.rb +7 -2
- 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 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
- 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 +155 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
- 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 +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -14
- 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 +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- 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 +75 -45
- 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/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 +2 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +365 -61
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +181 -154
- data/lib/active_record/counter_cache.rb +52 -27
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +28 -14
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +15 -10
- 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/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 +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- 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_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- data/lib/active_record/explain.rb +23 -3
- 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 +135 -71
- data/lib/active_record/future_result.rb +40 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +33 -19
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +9 -11
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +105 -7
- data/lib/active_record/migration/compatibility.rb +163 -58
- 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/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +271 -114
- data/lib/active_record/model_schema.rb +69 -44
- data/lib/active_record/nested_attributes.rb +37 -8
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +195 -42
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +4 -22
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +14 -9
- data/lib/active_record/railties/databases.rake +144 -150
- 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 +189 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +232 -81
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +408 -76
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +103 -37
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +50 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -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/signed_id.rb +7 -5
- data/lib/active_record/store.rb +9 -9
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +152 -108
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +114 -96
- data/lib/active_record/timestamp.rb +30 -16
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- 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 +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- 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 +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +130 -17
- 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/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/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/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +5 -1
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +83 -18
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- 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 +51 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -3,14 +3,13 @@
|
|
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 +where+ does not have any parameter.
|
12
|
+
# +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter.
|
14
13
|
# In this case, +where+ can be chained to return a new relation.
|
15
14
|
class WhereChain
|
16
15
|
def initialize(scope) # :nodoc:
|
@@ -39,7 +38,7 @@ 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')
|
43
42
|
#
|
44
43
|
# If there is a non-nil condition on a nullable column in the hash condition, the records that have
|
45
44
|
# nil values on the nullable column won't be returned.
|
@@ -77,7 +76,11 @@ module ActiveRecord
|
|
77
76
|
associations.each do |association|
|
78
77
|
reflection = scope_association_reflection(association)
|
79
78
|
@scope.joins!(association)
|
80
|
-
|
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
|
81
84
|
end
|
82
85
|
|
83
86
|
@scope
|
@@ -105,7 +108,11 @@ module ActiveRecord
|
|
105
108
|
associations.each do |association|
|
106
109
|
reflection = scope_association_reflection(association)
|
107
110
|
@scope.left_outer_joins!(association)
|
108
|
-
|
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
|
109
116
|
end
|
110
117
|
|
111
118
|
@scope
|
@@ -121,6 +128,15 @@ module ActiveRecord
|
|
121
128
|
end
|
122
129
|
end
|
123
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
|
138
|
+
end
|
139
|
+
|
124
140
|
FROZEN_EMPTY_ARRAY = [].freeze
|
125
141
|
FROZEN_EMPTY_HASH = {}.freeze
|
126
142
|
|
@@ -149,45 +165,69 @@ module ActiveRecord
|
|
149
165
|
|
150
166
|
alias extensions extending_values
|
151
167
|
|
152
|
-
# Specify
|
153
|
-
#
|
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:
|
154
173
|
#
|
155
|
-
# users = User.includes(:address)
|
174
|
+
# users = User.includes(:address).limit(5)
|
156
175
|
# users.each do |user|
|
157
176
|
# user.address.city
|
158
177
|
# end
|
159
178
|
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
#
|
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.
|
163
184
|
#
|
164
|
-
#
|
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.
|
165
188
|
#
|
166
|
-
#
|
189
|
+
# You can also specify multiple associations. Each association will result
|
190
|
+
# in an additional query:
|
167
191
|
#
|
168
|
-
#
|
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)
|
169
196
|
#
|
170
|
-
#
|
197
|
+
# Loading nested associations is possible using a Hash:
|
198
|
+
#
|
199
|
+
# User.includes(:address, friends: [:address, :followers])
|
171
200
|
#
|
172
201
|
# === Conditions
|
173
202
|
#
|
174
203
|
# If you want to add string conditions to your included models, you'll have
|
175
204
|
# to explicitly reference them. For example:
|
176
205
|
#
|
177
|
-
# User.includes(:posts).where('posts.name = ?', 'example')
|
206
|
+
# User.includes(:posts).where('posts.name = ?', 'example').to_a
|
178
207
|
#
|
179
208
|
# Will throw an error, but this will work:
|
180
209
|
#
|
181
|
-
# 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.
|
182
217
|
#
|
183
218
|
# Note that #includes works with association names while #references needs
|
184
219
|
# the actual table name.
|
185
220
|
#
|
186
|
-
# If you pass the conditions via
|
221
|
+
# If you pass the conditions via a Hash, you don't need to call #references
|
187
222
|
# explicitly, as #where references the tables for you. For example, this
|
188
223
|
# will work correctly:
|
189
224
|
#
|
190
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.
|
191
231
|
def includes(*args)
|
192
232
|
check_if_method_has_arguments!(__callee__, args)
|
193
233
|
spawn.includes!(*args)
|
@@ -198,12 +238,32 @@ module ActiveRecord
|
|
198
238
|
self
|
199
239
|
end
|
200
240
|
|
201
|
-
#
|
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:
|
202
243
|
#
|
203
|
-
# User.eager_load(:
|
204
|
-
#
|
205
|
-
#
|
206
|
-
#
|
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.
|
207
267
|
def eager_load(*args)
|
208
268
|
check_if_method_has_arguments!(__callee__, args)
|
209
269
|
spawn.eager_load!(*args)
|
@@ -214,10 +274,28 @@ module ActiveRecord
|
|
214
274
|
self
|
215
275
|
end
|
216
276
|
|
217
|
-
#
|
277
|
+
# Specify associations +args+ to be eager loaded using separate queries.
|
278
|
+
# A separate query is performed for each association.
|
279
|
+
#
|
280
|
+
# users = User.preload(:address).limit(5)
|
281
|
+
# users.each do |user|
|
282
|
+
# user.address.city
|
283
|
+
# end
|
218
284
|
#
|
219
|
-
#
|
220
|
-
# # SELECT "
|
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 ...
|
221
299
|
def preload(*args)
|
222
300
|
check_if_method_has_arguments!(__callee__, args)
|
223
301
|
spawn.preload!(*args)
|
@@ -242,7 +320,7 @@ module ActiveRecord
|
|
242
320
|
end
|
243
321
|
|
244
322
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
245
|
-
# and should therefore be
|
323
|
+
# and should therefore be +JOIN+ed in any query rather than loaded separately.
|
246
324
|
# This method only works in conjunction with #includes.
|
247
325
|
# See #includes for more details.
|
248
326
|
#
|
@@ -286,10 +364,18 @@ module ActiveRecord
|
|
286
364
|
# Model.select(:field, :other_field, :and_one_more)
|
287
365
|
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
|
288
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
|
+
#
|
289
375
|
# You can also use one or more strings, which will be used unchanged as SELECT fields.
|
290
376
|
#
|
291
377
|
# Model.select('field AS field_one', 'other_field AS field_two')
|
292
|
-
# # => [#<Model id: nil,
|
378
|
+
# # => [#<Model id: nil, field_one: "value", field_two: "value">]
|
293
379
|
#
|
294
380
|
# If an alias was specified, it will be accessible from the resulting objects:
|
295
381
|
#
|
@@ -300,7 +386,7 @@ module ActiveRecord
|
|
300
386
|
# except +id+ will throw ActiveModel::MissingAttributeError:
|
301
387
|
#
|
302
388
|
# Model.select(:field).first.other_field
|
303
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
389
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
|
304
390
|
def select(*fields)
|
305
391
|
if block_given?
|
306
392
|
if fields.any?
|
@@ -311,6 +397,8 @@ module ActiveRecord
|
|
311
397
|
end
|
312
398
|
|
313
399
|
check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
|
400
|
+
|
401
|
+
fields = process_select_args(fields)
|
314
402
|
spawn._select!(*fields)
|
315
403
|
end
|
316
404
|
|
@@ -319,6 +407,66 @@ module ActiveRecord
|
|
319
407
|
self
|
320
408
|
end
|
321
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
|
+
|
322
470
|
# Allows you to change a previously set select statement.
|
323
471
|
#
|
324
472
|
# Post.select(:title, :body)
|
@@ -331,6 +479,7 @@ module ActiveRecord
|
|
331
479
|
# Note that we're unscoping the entire select statement.
|
332
480
|
def reselect(*args)
|
333
481
|
check_if_method_has_arguments!(__callee__, args)
|
482
|
+
args = process_select_args(args)
|
334
483
|
spawn.reselect!(*args)
|
335
484
|
end
|
336
485
|
|
@@ -370,6 +519,27 @@ module ActiveRecord
|
|
370
519
|
self
|
371
520
|
end
|
372
521
|
|
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
|
+
|
373
543
|
# Applies an <code>ORDER BY</code> clause to a query.
|
374
544
|
#
|
375
545
|
# #order accepts arguments in one of several formats.
|
@@ -418,7 +588,7 @@ module ActiveRecord
|
|
418
588
|
# User.order(Arel.sql('end_date - start_date'))
|
419
589
|
# # SELECT "users".* FROM "users" ORDER BY end_date - start_date
|
420
590
|
#
|
421
|
-
# Custom query syntax, like JSON columns for
|
591
|
+
# Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
|
422
592
|
#
|
423
593
|
# User.order(Arel.sql("payload->>'kind'"))
|
424
594
|
# # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
|
@@ -436,13 +606,16 @@ module ActiveRecord
|
|
436
606
|
self
|
437
607
|
end
|
438
608
|
|
439
|
-
# Allows to specify an order by a specific set of values.
|
440
|
-
# adapter this will either use a CASE statement or a built-in function.
|
609
|
+
# Allows to specify an order by a specific set of values.
|
441
610
|
#
|
442
611
|
# User.in_order_of(:id, [1, 5, 3])
|
443
612
|
# # SELECT "users".* FROM "users"
|
444
|
-
# # ORDER BY FIELD("users"."id", 1, 5, 3)
|
445
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
|
446
619
|
#
|
447
620
|
def in_order_of(column, values)
|
448
621
|
klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
|
@@ -452,11 +625,18 @@ module ActiveRecord
|
|
452
625
|
self.references_values |= references unless references.empty?
|
453
626
|
|
454
627
|
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
455
|
-
arel_column = column.is_a?(
|
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
|
456
636
|
|
457
637
|
spawn
|
458
|
-
.order!(
|
459
|
-
.where!(
|
638
|
+
.order!(build_case_for_value_position(arel_column, values))
|
639
|
+
.where!(where_clause)
|
460
640
|
end
|
461
641
|
|
462
642
|
# Replaces any existing order defined on the relation with the specified order.
|
@@ -467,7 +647,7 @@ module ActiveRecord
|
|
467
647
|
#
|
468
648
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
469
649
|
#
|
470
|
-
# generates a query with
|
650
|
+
# generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
|
471
651
|
def reorder(*args)
|
472
652
|
check_if_method_has_arguments!(__callee__, args) do
|
473
653
|
sanitize_order_arguments(args)
|
@@ -486,7 +666,8 @@ module ActiveRecord
|
|
486
666
|
|
487
667
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
488
668
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
489
|
-
:includes, :
|
669
|
+
:includes, :eager_load, :preload, :from, :readonly,
|
670
|
+
:having, :optimizer_hints])
|
490
671
|
|
491
672
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
492
673
|
# This is useful when passing around chains of relations and would like to
|
@@ -596,7 +777,7 @@ module ActiveRecord
|
|
596
777
|
# Performs LEFT OUTER JOINs on +args+:
|
597
778
|
#
|
598
779
|
# User.left_outer_joins(:posts)
|
599
|
-
#
|
780
|
+
# # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
600
781
|
#
|
601
782
|
def left_outer_joins(*args)
|
602
783
|
check_if_method_has_arguments!(__callee__, args)
|
@@ -616,7 +797,7 @@ module ActiveRecord
|
|
616
797
|
# SQL is given as an illustration; the actual query generated may be different depending
|
617
798
|
# on the database adapter.
|
618
799
|
#
|
619
|
-
# ===
|
800
|
+
# === \String
|
620
801
|
#
|
621
802
|
# A single string, without additional arguments, is passed to the query
|
622
803
|
# constructor as an SQL fragment, and used in the where clause of the query.
|
@@ -628,7 +809,7 @@ module ActiveRecord
|
|
628
809
|
# to injection attacks if not done properly. As an alternative, it is recommended
|
629
810
|
# to use one of the following methods.
|
630
811
|
#
|
631
|
-
# ===
|
812
|
+
# === \Array
|
632
813
|
#
|
633
814
|
# If an array is passed, then the first element of the array is treated as a template, and
|
634
815
|
# the remaining elements are inserted into the template to generate the condition.
|
@@ -668,7 +849,7 @@ module ActiveRecord
|
|
668
849
|
# dependencies on the underlying database. If your code is intended for general consumption,
|
669
850
|
# test with multiple database backends.
|
670
851
|
#
|
671
|
-
# ===
|
852
|
+
# === \Hash
|
672
853
|
#
|
673
854
|
# #where will also accept a hash condition, in which the keys are fields and the values
|
674
855
|
# are values to be searched for.
|
@@ -702,6 +883,12 @@ module ActiveRecord
|
|
702
883
|
# PriceEstimate.where(estimate_of: treasure)
|
703
884
|
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
704
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
|
+
#
|
705
892
|
# === Joins
|
706
893
|
#
|
707
894
|
# If the relation is the result of a join, you may create a condition which uses any of the
|
@@ -714,7 +901,7 @@ module ActiveRecord
|
|
714
901
|
# User.joins(:posts).where("posts.published" => true)
|
715
902
|
# User.joins(:posts).where(posts: { published: true })
|
716
903
|
#
|
717
|
-
# ===
|
904
|
+
# === No Argument
|
718
905
|
#
|
719
906
|
# If no argument is passed, #where returns a new instance of WhereChain, that
|
720
907
|
# can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
|
@@ -738,7 +925,7 @@ module ActiveRecord
|
|
738
925
|
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
739
926
|
# # WHERE "authors"."id" IS NULL
|
740
927
|
#
|
741
|
-
# ===
|
928
|
+
# === Blank Condition
|
742
929
|
#
|
743
930
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
744
931
|
# the current relation.
|
@@ -771,6 +958,8 @@ module ActiveRecord
|
|
771
958
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
772
959
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
773
960
|
def rewhere(conditions)
|
961
|
+
return unscope(:where) if conditions.nil?
|
962
|
+
|
774
963
|
scope = spawn
|
775
964
|
where_clause = scope.build_where_clause(conditions)
|
776
965
|
|
@@ -876,7 +1065,11 @@ module ActiveRecord
|
|
876
1065
|
#
|
877
1066
|
def or(other)
|
878
1067
|
if other.is_a?(Relation)
|
879
|
-
|
1068
|
+
if @none
|
1069
|
+
other.spawn
|
1070
|
+
else
|
1071
|
+
spawn.or!(other)
|
1072
|
+
end
|
880
1073
|
else
|
881
1074
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
882
1075
|
end
|
@@ -989,15 +1182,29 @@ module ActiveRecord
|
|
989
1182
|
end
|
990
1183
|
|
991
1184
|
def none! # :nodoc:
|
992
|
-
|
1185
|
+
unless @none
|
1186
|
+
where!("1=0")
|
1187
|
+
@none = true
|
1188
|
+
end
|
1189
|
+
self
|
993
1190
|
end
|
994
1191
|
|
995
|
-
|
996
|
-
|
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.
|
997
1198
|
#
|
998
1199
|
# users = User.readonly
|
999
1200
|
# users.first.save
|
1000
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
|
1001
1208
|
def readonly(value = true)
|
1002
1209
|
spawn.readonly!(value)
|
1003
1210
|
end
|
@@ -1114,7 +1321,7 @@ module ActiveRecord
|
|
1114
1321
|
#
|
1115
1322
|
# The object returned is a relation, which can be further extended.
|
1116
1323
|
#
|
1117
|
-
# === Using a
|
1324
|
+
# === Using a \Module
|
1118
1325
|
#
|
1119
1326
|
# module Pagination
|
1120
1327
|
# def page(number)
|
@@ -1129,7 +1336,7 @@ module ActiveRecord
|
|
1129
1336
|
#
|
1130
1337
|
# scope = Model.all.extending(Pagination, SomethingElse)
|
1131
1338
|
#
|
1132
|
-
# === Using a
|
1339
|
+
# === Using a Block
|
1133
1340
|
#
|
1134
1341
|
# scope = Model.all.extending do
|
1135
1342
|
# def page(number)
|
@@ -1216,6 +1423,8 @@ module ActiveRecord
|
|
1216
1423
|
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
1217
1424
|
#
|
1218
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.
|
1219
1428
|
def annotate(*args)
|
1220
1429
|
check_if_method_has_arguments!(__callee__, args)
|
1221
1430
|
spawn.annotate!(*args)
|
@@ -1304,8 +1513,12 @@ module ActiveRecord
|
|
1304
1513
|
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1305
1514
|
when Hash
|
1306
1515
|
opts = opts.transform_keys do |key|
|
1307
|
-
|
1308
|
-
|
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
|
1309
1522
|
end
|
1310
1523
|
references = PredicateBuilder.references(opts)
|
1311
1524
|
self.references_values |= references unless references.empty?
|
@@ -1323,7 +1536,16 @@ module ActiveRecord
|
|
1323
1536
|
end
|
1324
1537
|
alias :build_having_clause :build_where_clause
|
1325
1538
|
|
1539
|
+
def async!
|
1540
|
+
@async = true
|
1541
|
+
self
|
1542
|
+
end
|
1543
|
+
|
1326
1544
|
private
|
1545
|
+
def async
|
1546
|
+
spawn.async!
|
1547
|
+
end
|
1548
|
+
|
1327
1549
|
def lookup_table_klass_from_join_dependencies(table_name)
|
1328
1550
|
each_join_dependencies do |join|
|
1329
1551
|
return join.base_klass if table_name == join.table_name
|
@@ -1338,13 +1560,13 @@ module ActiveRecord
|
|
1338
1560
|
end
|
1339
1561
|
|
1340
1562
|
def build_join_dependencies
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
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?
|
1344
1566
|
|
1345
1567
|
join_dependencies = []
|
1346
1568
|
join_dependencies.unshift construct_join_dependency(
|
1347
|
-
|
1569
|
+
select_named_joins(joins, join_dependencies), nil
|
1348
1570
|
)
|
1349
1571
|
end
|
1350
1572
|
|
@@ -1365,6 +1587,7 @@ module ActiveRecord
|
|
1365
1587
|
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1366
1588
|
|
1367
1589
|
build_order(arel)
|
1590
|
+
build_with(arel)
|
1368
1591
|
build_select(arel)
|
1369
1592
|
|
1370
1593
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
@@ -1400,6 +1623,18 @@ module ActiveRecord
|
|
1400
1623
|
end
|
1401
1624
|
end
|
1402
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
|
+
|
1403
1638
|
def select_association_list(associations, stashed_joins = nil)
|
1404
1639
|
result = []
|
1405
1640
|
associations.each do |association|
|
@@ -1415,20 +1650,21 @@ module ActiveRecord
|
|
1415
1650
|
result
|
1416
1651
|
end
|
1417
1652
|
|
1418
|
-
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1419
|
-
end
|
1420
|
-
|
1421
1653
|
def build_join_buckets
|
1422
1654
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1423
1655
|
|
1424
1656
|
unless left_outer_joins_values.empty?
|
1425
1657
|
stashed_left_joins = []
|
1426
|
-
left_joins =
|
1427
|
-
|
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
|
1428
1664
|
end
|
1429
1665
|
|
1430
1666
|
if joins_values.empty?
|
1431
|
-
buckets[:
|
1667
|
+
buckets[:named_join] = left_joins
|
1432
1668
|
buckets[:stashed_join] = stashed_left_joins
|
1433
1669
|
return buckets, Arel::Nodes::OuterJoin
|
1434
1670
|
else
|
@@ -1454,9 +1690,11 @@ module ActiveRecord
|
|
1454
1690
|
end
|
1455
1691
|
end
|
1456
1692
|
|
1457
|
-
buckets[:
|
1693
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
1458
1694
|
if join.is_a?(Arel::Nodes::Join)
|
1459
1695
|
buckets[:join_node] << join
|
1696
|
+
elsif join.is_a?(CTEJoin)
|
1697
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
1460
1698
|
else
|
1461
1699
|
raise "unknown class: %s" % join.class.name
|
1462
1700
|
end
|
@@ -1473,16 +1711,16 @@ module ActiveRecord
|
|
1473
1711
|
|
1474
1712
|
buckets, join_type = build_join_buckets
|
1475
1713
|
|
1476
|
-
|
1477
|
-
stashed_joins
|
1478
|
-
leading_joins
|
1479
|
-
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]
|
1480
1718
|
|
1481
1719
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1482
1720
|
|
1483
|
-
unless
|
1721
|
+
unless named_joins.empty? && stashed_joins.empty?
|
1484
1722
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1485
|
-
join_dependency = construct_join_dependency(
|
1723
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
1486
1724
|
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1487
1725
|
end
|
1488
1726
|
|
@@ -1500,6 +1738,40 @@ module ActiveRecord
|
|
1500
1738
|
end
|
1501
1739
|
end
|
1502
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
|
+
|
1503
1775
|
def arel_columns(columns)
|
1504
1776
|
columns.flat_map do |field|
|
1505
1777
|
case field
|
@@ -1618,7 +1890,7 @@ module ActiveRecord
|
|
1618
1890
|
when Hash
|
1619
1891
|
arg.map { |field, dir|
|
1620
1892
|
case field
|
1621
|
-
when Arel::Nodes::SqlLiteral
|
1893
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
1622
1894
|
field.public_send(dir.downcase)
|
1623
1895
|
else
|
1624
1896
|
order_column(field.to_s).public_send(dir.downcase)
|
@@ -1637,16 +1909,32 @@ module ActiveRecord
|
|
1637
1909
|
end
|
1638
1910
|
|
1639
1911
|
def column_references(order_args)
|
1640
|
-
|
1912
|
+
order_args.flat_map do |arg|
|
1641
1913
|
case arg
|
1642
1914
|
when String, Symbol
|
1643
|
-
arg
|
1915
|
+
extract_table_name_from(arg)
|
1644
1916
|
when Hash
|
1645
|
-
arg
|
1917
|
+
arg
|
1918
|
+
.map do |key, value|
|
1919
|
+
case value
|
1920
|
+
when Hash
|
1921
|
+
key.to_s
|
1922
|
+
else
|
1923
|
+
extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
|
1924
|
+
end
|
1925
|
+
end
|
1926
|
+
when Arel::Attribute
|
1927
|
+
arg.relation.name
|
1928
|
+
when Arel::Nodes::Ordering
|
1929
|
+
if arg.expr.is_a?(Arel::Attribute)
|
1930
|
+
arg.expr.relation.name
|
1931
|
+
end
|
1646
1932
|
end
|
1647
|
-
end
|
1648
|
-
|
1649
|
-
|
1933
|
+
end.compact
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
def extract_table_name_from(string)
|
1937
|
+
string.match(/^\W?(\w+)\W?\./) && $1
|
1650
1938
|
end
|
1651
1939
|
|
1652
1940
|
def order_column(field)
|
@@ -1659,6 +1947,15 @@ module ActiveRecord
|
|
1659
1947
|
end
|
1660
1948
|
end
|
1661
1949
|
|
1950
|
+
def build_case_for_value_position(column, values)
|
1951
|
+
node = Arel::Nodes::Case.new
|
1952
|
+
values.each.with_index(1) do |value, order|
|
1953
|
+
node.when(column.eq(value)).then(order)
|
1954
|
+
end
|
1955
|
+
|
1956
|
+
Arel::Nodes::Ascending.new(node)
|
1957
|
+
end
|
1958
|
+
|
1662
1959
|
def resolve_arel_attributes(attrs)
|
1663
1960
|
attrs.flat_map do |attr|
|
1664
1961
|
case attr
|
@@ -1710,6 +2007,41 @@ module ActiveRecord
|
|
1710
2007
|
end
|
1711
2008
|
end
|
1712
2009
|
|
2010
|
+
def process_select_args(fields)
|
2011
|
+
fields.flat_map do |field|
|
2012
|
+
if field.is_a?(Hash)
|
2013
|
+
transform_select_hash_values(field)
|
2014
|
+
else
|
2015
|
+
field
|
2016
|
+
end
|
2017
|
+
end
|
2018
|
+
end
|
2019
|
+
|
2020
|
+
def transform_select_hash_values(fields)
|
2021
|
+
fields.flat_map do |key, columns_aliases|
|
2022
|
+
case columns_aliases
|
2023
|
+
when Hash
|
2024
|
+
columns_aliases.map do |column, column_alias|
|
2025
|
+
if values[:joins]&.include?(key)
|
2026
|
+
references = PredicateBuilder.references({ key.to_s => fields[key] })
|
2027
|
+
self.references_values |= references unless references.empty?
|
2028
|
+
end
|
2029
|
+
arel_column("#{key}.#{column}") do
|
2030
|
+
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
2031
|
+
end.as(column_alias.to_s)
|
2032
|
+
end
|
2033
|
+
when Array
|
2034
|
+
columns_aliases.map do |column|
|
2035
|
+
arel_column("#{key}.#{column}", &:itself)
|
2036
|
+
end
|
2037
|
+
when String, Symbol
|
2038
|
+
arel_column(key.to_s) do
|
2039
|
+
predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
|
2040
|
+
end.as(columns_aliases.to_s)
|
2041
|
+
end
|
2042
|
+
end
|
2043
|
+
end
|
2044
|
+
|
1713
2045
|
STRUCTURAL_VALUE_METHODS = (
|
1714
2046
|
Relation::VALUE_METHODS -
|
1715
2047
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|