activerecord 7.0.6 → 7.1.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1598 -1367
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- 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 +16 -10
- 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 -7
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +6 -8
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +313 -217
- 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 +52 -34
- 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 +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +60 -18
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- 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 +74 -51
- 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 +41 -6
- 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 +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +505 -102
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +214 -113
- 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 +21 -14
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- 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 +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
- 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 +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +15 -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 +361 -60
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- 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 +210 -83
- 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 +262 -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 +175 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- 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 +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- 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 +21 -6
- 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 +19 -22
- data/lib/active_record/encryption.rb +1 -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/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 +31 -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/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- 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 +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +104 -5
- data/lib/active_record/migration/compatibility.rb +150 -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 +64 -44
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +191 -38
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +109 -47
- data/lib/active_record/railties/controller_runtime.rb +14 -9
- data/lib/active_record/railties/databases.rake +142 -148
- 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 +174 -44
- 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 +187 -63
- 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 +11 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +27 -16
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +378 -70
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +91 -35
- data/lib/active_record/result.rb +19 -5
- 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 +46 -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 +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +11 -2
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- 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 +113 -96
- data/lib/active_record/timestamp.rb +27 -15
- 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 +4 -0
- 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 +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- 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/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/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- 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,7 @@ module ActiveRecord
|
|
77
76
|
associations.each do |association|
|
78
77
|
reflection = scope_association_reflection(association)
|
79
78
|
@scope.joins!(association)
|
80
|
-
if
|
79
|
+
if reflection.options[:class_name]
|
81
80
|
self.not(association => { reflection.association_primary_key => nil })
|
82
81
|
else
|
83
82
|
self.not(reflection.table_name => { reflection.association_primary_key => nil })
|
@@ -109,7 +108,7 @@ module ActiveRecord
|
|
109
108
|
associations.each do |association|
|
110
109
|
reflection = scope_association_reflection(association)
|
111
110
|
@scope.left_outer_joins!(association)
|
112
|
-
if
|
111
|
+
if reflection.options[:class_name]
|
113
112
|
@scope.where!(association => { reflection.association_primary_key => nil })
|
114
113
|
else
|
115
114
|
@scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
|
@@ -129,6 +128,15 @@ module ActiveRecord
|
|
129
128
|
end
|
130
129
|
end
|
131
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
|
+
|
132
140
|
FROZEN_EMPTY_ARRAY = [].freeze
|
133
141
|
FROZEN_EMPTY_HASH = {}.freeze
|
134
142
|
|
@@ -157,45 +165,69 @@ module ActiveRecord
|
|
157
165
|
|
158
166
|
alias extensions extending_values
|
159
167
|
|
160
|
-
# Specify
|
161
|
-
#
|
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.
|
162
171
|
#
|
163
|
-
#
|
172
|
+
# For example:
|
173
|
+
#
|
174
|
+
# users = User.includes(:address).limit(5)
|
164
175
|
# users.each do |user|
|
165
176
|
# user.address.city
|
166
177
|
# end
|
167
178
|
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
#
|
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.
|
184
|
+
#
|
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.
|
171
188
|
#
|
172
|
-
# You can also specify multiple
|
189
|
+
# You can also specify multiple associations. Each association will result
|
190
|
+
# in an additional query:
|
173
191
|
#
|
174
|
-
#
|
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)
|
175
196
|
#
|
176
|
-
# Loading nested
|
197
|
+
# Loading nested associations is possible using a Hash:
|
177
198
|
#
|
178
|
-
#
|
199
|
+
# User.includes(:address, friends: [:address, :followers])
|
179
200
|
#
|
180
201
|
# === Conditions
|
181
202
|
#
|
182
203
|
# If you want to add string conditions to your included models, you'll have
|
183
204
|
# to explicitly reference them. For example:
|
184
205
|
#
|
185
|
-
# User.includes(:posts).where('posts.name = ?', 'example')
|
206
|
+
# User.includes(:posts).where('posts.name = ?', 'example').to_a
|
186
207
|
#
|
187
208
|
# Will throw an error, but this will work:
|
188
209
|
#
|
189
|
-
# 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.
|
190
217
|
#
|
191
218
|
# Note that #includes works with association names while #references needs
|
192
219
|
# the actual table name.
|
193
220
|
#
|
194
|
-
# If you pass the conditions via
|
221
|
+
# If you pass the conditions via a Hash, you don't need to call #references
|
195
222
|
# explicitly, as #where references the tables for you. For example, this
|
196
223
|
# will work correctly:
|
197
224
|
#
|
198
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.
|
199
231
|
def includes(*args)
|
200
232
|
check_if_method_has_arguments!(__callee__, args)
|
201
233
|
spawn.includes!(*args)
|
@@ -206,12 +238,32 @@ module ActiveRecord
|
|
206
238
|
self
|
207
239
|
end
|
208
240
|
|
209
|
-
#
|
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:
|
243
|
+
#
|
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
|
210
252
|
#
|
211
|
-
#
|
212
|
-
#
|
213
|
-
#
|
214
|
-
#
|
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.
|
215
267
|
def eager_load(*args)
|
216
268
|
check_if_method_has_arguments!(__callee__, args)
|
217
269
|
spawn.eager_load!(*args)
|
@@ -222,10 +274,28 @@ module ActiveRecord
|
|
222
274
|
self
|
223
275
|
end
|
224
276
|
|
225
|
-
#
|
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
|
284
|
+
#
|
285
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
286
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
226
287
|
#
|
227
|
-
#
|
228
|
-
#
|
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 ...
|
229
299
|
def preload(*args)
|
230
300
|
check_if_method_has_arguments!(__callee__, args)
|
231
301
|
spawn.preload!(*args)
|
@@ -250,7 +320,7 @@ module ActiveRecord
|
|
250
320
|
end
|
251
321
|
|
252
322
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
253
|
-
# and should therefore be
|
323
|
+
# and should therefore be +JOIN+ed in any query rather than loaded separately.
|
254
324
|
# This method only works in conjunction with #includes.
|
255
325
|
# See #includes for more details.
|
256
326
|
#
|
@@ -294,6 +364,14 @@ module ActiveRecord
|
|
294
364
|
# Model.select(:field, :other_field, :and_one_more)
|
295
365
|
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
|
296
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
|
+
#
|
297
375
|
# You can also use one or more strings, which will be used unchanged as SELECT fields.
|
298
376
|
#
|
299
377
|
# Model.select('field AS field_one', 'other_field AS field_two')
|
@@ -308,7 +386,7 @@ module ActiveRecord
|
|
308
386
|
# except +id+ will throw ActiveModel::MissingAttributeError:
|
309
387
|
#
|
310
388
|
# Model.select(:field).first.other_field
|
311
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
389
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
|
312
390
|
def select(*fields)
|
313
391
|
if block_given?
|
314
392
|
if fields.any?
|
@@ -319,6 +397,8 @@ module ActiveRecord
|
|
319
397
|
end
|
320
398
|
|
321
399
|
check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
|
400
|
+
|
401
|
+
fields = process_select_args(fields)
|
322
402
|
spawn._select!(*fields)
|
323
403
|
end
|
324
404
|
|
@@ -327,6 +407,66 @@ module ActiveRecord
|
|
327
407
|
self
|
328
408
|
end
|
329
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
|
+
|
330
470
|
# Allows you to change a previously set select statement.
|
331
471
|
#
|
332
472
|
# Post.select(:title, :body)
|
@@ -339,6 +479,7 @@ module ActiveRecord
|
|
339
479
|
# Note that we're unscoping the entire select statement.
|
340
480
|
def reselect(*args)
|
341
481
|
check_if_method_has_arguments!(__callee__, args)
|
482
|
+
args = process_select_args(args)
|
342
483
|
spawn.reselect!(*args)
|
343
484
|
end
|
344
485
|
|
@@ -378,6 +519,27 @@ module ActiveRecord
|
|
378
519
|
self
|
379
520
|
end
|
380
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
|
+
|
381
543
|
# Applies an <code>ORDER BY</code> clause to a query.
|
382
544
|
#
|
383
545
|
# #order accepts arguments in one of several formats.
|
@@ -426,7 +588,7 @@ module ActiveRecord
|
|
426
588
|
# User.order(Arel.sql('end_date - start_date'))
|
427
589
|
# # SELECT "users".* FROM "users" ORDER BY end_date - start_date
|
428
590
|
#
|
429
|
-
# Custom query syntax, like JSON columns for
|
591
|
+
# Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
|
430
592
|
#
|
431
593
|
# User.order(Arel.sql("payload->>'kind'"))
|
432
594
|
# # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
|
@@ -444,13 +606,16 @@ module ActiveRecord
|
|
444
606
|
self
|
445
607
|
end
|
446
608
|
|
447
|
-
# Allows to specify an order by a specific set of values.
|
448
|
-
# 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.
|
449
610
|
#
|
450
611
|
# User.in_order_of(:id, [1, 5, 3])
|
451
612
|
# # SELECT "users".* FROM "users"
|
452
|
-
# # ORDER BY FIELD("users"."id", 1, 5, 3)
|
453
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
|
454
619
|
#
|
455
620
|
def in_order_of(column, values)
|
456
621
|
klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
|
@@ -460,11 +625,18 @@ module ActiveRecord
|
|
460
625
|
self.references_values |= references unless references.empty?
|
461
626
|
|
462
627
|
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
463
|
-
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
|
464
636
|
|
465
637
|
spawn
|
466
|
-
.order!(
|
467
|
-
.where!(
|
638
|
+
.order!(build_case_for_value_position(arel_column, values))
|
639
|
+
.where!(where_clause)
|
468
640
|
end
|
469
641
|
|
470
642
|
# Replaces any existing order defined on the relation with the specified order.
|
@@ -475,7 +647,7 @@ module ActiveRecord
|
|
475
647
|
#
|
476
648
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
477
649
|
#
|
478
|
-
# generates a query with
|
650
|
+
# generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
|
479
651
|
def reorder(*args)
|
480
652
|
check_if_method_has_arguments!(__callee__, args) do
|
481
653
|
sanitize_order_arguments(args)
|
@@ -494,7 +666,8 @@ module ActiveRecord
|
|
494
666
|
|
495
667
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
496
668
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
497
|
-
:includes, :
|
669
|
+
:includes, :eager_load, :preload, :from, :readonly,
|
670
|
+
:having, :optimizer_hints])
|
498
671
|
|
499
672
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
500
673
|
# This is useful when passing around chains of relations and would like to
|
@@ -604,7 +777,7 @@ module ActiveRecord
|
|
604
777
|
# Performs LEFT OUTER JOINs on +args+:
|
605
778
|
#
|
606
779
|
# User.left_outer_joins(:posts)
|
607
|
-
#
|
780
|
+
# # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
608
781
|
#
|
609
782
|
def left_outer_joins(*args)
|
610
783
|
check_if_method_has_arguments!(__callee__, args)
|
@@ -624,7 +797,7 @@ module ActiveRecord
|
|
624
797
|
# SQL is given as an illustration; the actual query generated may be different depending
|
625
798
|
# on the database adapter.
|
626
799
|
#
|
627
|
-
# ===
|
800
|
+
# === \String
|
628
801
|
#
|
629
802
|
# A single string, without additional arguments, is passed to the query
|
630
803
|
# constructor as an SQL fragment, and used in the where clause of the query.
|
@@ -636,7 +809,7 @@ module ActiveRecord
|
|
636
809
|
# to injection attacks if not done properly. As an alternative, it is recommended
|
637
810
|
# to use one of the following methods.
|
638
811
|
#
|
639
|
-
# ===
|
812
|
+
# === \Array
|
640
813
|
#
|
641
814
|
# If an array is passed, then the first element of the array is treated as a template, and
|
642
815
|
# the remaining elements are inserted into the template to generate the condition.
|
@@ -676,7 +849,7 @@ module ActiveRecord
|
|
676
849
|
# dependencies on the underlying database. If your code is intended for general consumption,
|
677
850
|
# test with multiple database backends.
|
678
851
|
#
|
679
|
-
# ===
|
852
|
+
# === \Hash
|
680
853
|
#
|
681
854
|
# #where will also accept a hash condition, in which the keys are fields and the values
|
682
855
|
# are values to be searched for.
|
@@ -710,6 +883,12 @@ module ActiveRecord
|
|
710
883
|
# PriceEstimate.where(estimate_of: treasure)
|
711
884
|
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
712
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
|
+
#
|
713
892
|
# === Joins
|
714
893
|
#
|
715
894
|
# If the relation is the result of a join, you may create a condition which uses any of the
|
@@ -722,7 +901,7 @@ module ActiveRecord
|
|
722
901
|
# User.joins(:posts).where("posts.published" => true)
|
723
902
|
# User.joins(:posts).where(posts: { published: true })
|
724
903
|
#
|
725
|
-
# ===
|
904
|
+
# === No Argument
|
726
905
|
#
|
727
906
|
# If no argument is passed, #where returns a new instance of WhereChain, that
|
728
907
|
# can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
|
@@ -746,7 +925,7 @@ module ActiveRecord
|
|
746
925
|
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
747
926
|
# # WHERE "authors"."id" IS NULL
|
748
927
|
#
|
749
|
-
# ===
|
928
|
+
# === Blank Condition
|
750
929
|
#
|
751
930
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
752
931
|
# the current relation.
|
@@ -779,6 +958,8 @@ module ActiveRecord
|
|
779
958
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
780
959
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
781
960
|
def rewhere(conditions)
|
961
|
+
return unscope(:where) if conditions.nil?
|
962
|
+
|
782
963
|
scope = spawn
|
783
964
|
where_clause = scope.build_where_clause(conditions)
|
784
965
|
|
@@ -884,7 +1065,11 @@ module ActiveRecord
|
|
884
1065
|
#
|
885
1066
|
def or(other)
|
886
1067
|
if other.is_a?(Relation)
|
887
|
-
|
1068
|
+
if @none
|
1069
|
+
other.spawn
|
1070
|
+
else
|
1071
|
+
spawn.or!(other)
|
1072
|
+
end
|
888
1073
|
else
|
889
1074
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
890
1075
|
end
|
@@ -997,15 +1182,29 @@ module ActiveRecord
|
|
997
1182
|
end
|
998
1183
|
|
999
1184
|
def none! # :nodoc:
|
1000
|
-
|
1185
|
+
unless @none
|
1186
|
+
where!("1=0")
|
1187
|
+
@none = true
|
1188
|
+
end
|
1189
|
+
self
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
def null_relation? # :nodoc:
|
1193
|
+
@none
|
1001
1194
|
end
|
1002
1195
|
|
1003
|
-
#
|
1004
|
-
#
|
1196
|
+
# Mark a relation as readonly. Attempting to update a record will result in
|
1197
|
+
# an error.
|
1005
1198
|
#
|
1006
1199
|
# users = User.readonly
|
1007
1200
|
# users.first.save
|
1008
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
|
1009
1208
|
def readonly(value = true)
|
1010
1209
|
spawn.readonly!(value)
|
1011
1210
|
end
|
@@ -1122,7 +1321,7 @@ module ActiveRecord
|
|
1122
1321
|
#
|
1123
1322
|
# The object returned is a relation, which can be further extended.
|
1124
1323
|
#
|
1125
|
-
# === Using a
|
1324
|
+
# === Using a \Module
|
1126
1325
|
#
|
1127
1326
|
# module Pagination
|
1128
1327
|
# def page(number)
|
@@ -1137,7 +1336,7 @@ module ActiveRecord
|
|
1137
1336
|
#
|
1138
1337
|
# scope = Model.all.extending(Pagination, SomethingElse)
|
1139
1338
|
#
|
1140
|
-
# === Using a
|
1339
|
+
# === Using a Block
|
1141
1340
|
#
|
1142
1341
|
# scope = Model.all.extending do
|
1143
1342
|
# def page(number)
|
@@ -1314,8 +1513,12 @@ module ActiveRecord
|
|
1314
1513
|
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1315
1514
|
when Hash
|
1316
1515
|
opts = opts.transform_keys do |key|
|
1317
|
-
|
1318
|
-
|
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
|
1319
1522
|
end
|
1320
1523
|
references = PredicateBuilder.references(opts)
|
1321
1524
|
self.references_values |= references unless references.empty?
|
@@ -1333,7 +1536,16 @@ module ActiveRecord
|
|
1333
1536
|
end
|
1334
1537
|
alias :build_having_clause :build_where_clause
|
1335
1538
|
|
1539
|
+
def async!
|
1540
|
+
@async = true
|
1541
|
+
self
|
1542
|
+
end
|
1543
|
+
|
1336
1544
|
private
|
1545
|
+
def async
|
1546
|
+
spawn.async!
|
1547
|
+
end
|
1548
|
+
|
1337
1549
|
def lookup_table_klass_from_join_dependencies(table_name)
|
1338
1550
|
each_join_dependencies do |join|
|
1339
1551
|
return join.base_klass if table_name == join.table_name
|
@@ -1348,13 +1560,13 @@ module ActiveRecord
|
|
1348
1560
|
end
|
1349
1561
|
|
1350
1562
|
def build_join_dependencies
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
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?
|
1354
1566
|
|
1355
1567
|
join_dependencies = []
|
1356
1568
|
join_dependencies.unshift construct_join_dependency(
|
1357
|
-
|
1569
|
+
select_named_joins(joins, join_dependencies), nil
|
1358
1570
|
)
|
1359
1571
|
end
|
1360
1572
|
|
@@ -1375,6 +1587,7 @@ module ActiveRecord
|
|
1375
1587
|
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1376
1588
|
|
1377
1589
|
build_order(arel)
|
1590
|
+
build_with(arel)
|
1378
1591
|
build_select(arel)
|
1379
1592
|
|
1380
1593
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
@@ -1410,6 +1623,18 @@ module ActiveRecord
|
|
1410
1623
|
end
|
1411
1624
|
end
|
1412
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
|
+
|
1413
1638
|
def select_association_list(associations, stashed_joins = nil)
|
1414
1639
|
result = []
|
1415
1640
|
associations.each do |association|
|
@@ -1425,20 +1650,21 @@ module ActiveRecord
|
|
1425
1650
|
result
|
1426
1651
|
end
|
1427
1652
|
|
1428
|
-
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1429
|
-
end
|
1430
|
-
|
1431
1653
|
def build_join_buckets
|
1432
1654
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1433
1655
|
|
1434
1656
|
unless left_outer_joins_values.empty?
|
1435
1657
|
stashed_left_joins = []
|
1436
|
-
left_joins =
|
1437
|
-
|
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
|
1438
1664
|
end
|
1439
1665
|
|
1440
1666
|
if joins_values.empty?
|
1441
|
-
buckets[:
|
1667
|
+
buckets[:named_join] = left_joins
|
1442
1668
|
buckets[:stashed_join] = stashed_left_joins
|
1443
1669
|
return buckets, Arel::Nodes::OuterJoin
|
1444
1670
|
else
|
@@ -1464,9 +1690,11 @@ module ActiveRecord
|
|
1464
1690
|
end
|
1465
1691
|
end
|
1466
1692
|
|
1467
|
-
buckets[:
|
1693
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
1468
1694
|
if join.is_a?(Arel::Nodes::Join)
|
1469
1695
|
buckets[:join_node] << join
|
1696
|
+
elsif join.is_a?(CTEJoin)
|
1697
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
1470
1698
|
else
|
1471
1699
|
raise "unknown class: %s" % join.class.name
|
1472
1700
|
end
|
@@ -1483,16 +1711,16 @@ module ActiveRecord
|
|
1483
1711
|
|
1484
1712
|
buckets, join_type = build_join_buckets
|
1485
1713
|
|
1486
|
-
|
1487
|
-
stashed_joins
|
1488
|
-
leading_joins
|
1489
|
-
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]
|
1490
1718
|
|
1491
1719
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1492
1720
|
|
1493
|
-
unless
|
1721
|
+
unless named_joins.empty? && stashed_joins.empty?
|
1494
1722
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1495
|
-
join_dependency = construct_join_dependency(
|
1723
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
1496
1724
|
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1497
1725
|
end
|
1498
1726
|
|
@@ -1510,6 +1738,40 @@ module ActiveRecord
|
|
1510
1738
|
end
|
1511
1739
|
end
|
1512
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
|
+
|
1513
1775
|
def arel_columns(columns)
|
1514
1776
|
columns.flat_map do |field|
|
1515
1777
|
case field
|
@@ -1628,7 +1890,7 @@ module ActiveRecord
|
|
1628
1890
|
when Hash
|
1629
1891
|
arg.map { |field, dir|
|
1630
1892
|
case field
|
1631
|
-
when Arel::Nodes::SqlLiteral
|
1893
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
1632
1894
|
field.public_send(dir.downcase)
|
1633
1895
|
else
|
1634
1896
|
order_column(field.to_s).public_send(dir.downcase)
|
@@ -1652,7 +1914,9 @@ module ActiveRecord
|
|
1652
1914
|
when String, Symbol
|
1653
1915
|
arg
|
1654
1916
|
when Hash
|
1655
|
-
arg.keys
|
1917
|
+
arg.keys.map do |key|
|
1918
|
+
key if key.is_a?(String) || key.is_a?(Symbol)
|
1919
|
+
end
|
1656
1920
|
end
|
1657
1921
|
end
|
1658
1922
|
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
@@ -1669,6 +1933,15 @@ module ActiveRecord
|
|
1669
1933
|
end
|
1670
1934
|
end
|
1671
1935
|
|
1936
|
+
def build_case_for_value_position(column, values)
|
1937
|
+
node = Arel::Nodes::Case.new
|
1938
|
+
values.each.with_index(1) do |value, order|
|
1939
|
+
node.when(column.eq(value)).then(order)
|
1940
|
+
end
|
1941
|
+
|
1942
|
+
Arel::Nodes::Ascending.new(node)
|
1943
|
+
end
|
1944
|
+
|
1672
1945
|
def resolve_arel_attributes(attrs)
|
1673
1946
|
attrs.flat_map do |attr|
|
1674
1947
|
case attr
|
@@ -1720,6 +1993,41 @@ module ActiveRecord
|
|
1720
1993
|
end
|
1721
1994
|
end
|
1722
1995
|
|
1996
|
+
def process_select_args(fields)
|
1997
|
+
fields.flat_map do |field|
|
1998
|
+
if field.is_a?(Hash)
|
1999
|
+
transform_select_hash_values(field)
|
2000
|
+
else
|
2001
|
+
field
|
2002
|
+
end
|
2003
|
+
end
|
2004
|
+
end
|
2005
|
+
|
2006
|
+
def transform_select_hash_values(fields)
|
2007
|
+
fields.flat_map do |key, columns_aliases|
|
2008
|
+
case columns_aliases
|
2009
|
+
when Hash
|
2010
|
+
columns_aliases.map do |column, column_alias|
|
2011
|
+
if values[:joins]&.include?(key)
|
2012
|
+
references = PredicateBuilder.references({ key.to_s => fields[key] })
|
2013
|
+
self.references_values |= references unless references.empty?
|
2014
|
+
end
|
2015
|
+
arel_column("#{key}.#{column}") do
|
2016
|
+
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
2017
|
+
end.as(column_alias.to_s)
|
2018
|
+
end
|
2019
|
+
when Array
|
2020
|
+
columns_aliases.map do |column|
|
2021
|
+
arel_column("#{key}.#{column}", &:itself)
|
2022
|
+
end
|
2023
|
+
when String, Symbol
|
2024
|
+
arel_column(key.to_s) do
|
2025
|
+
predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
|
2026
|
+
end.as(columns_aliases.to_s)
|
2027
|
+
end
|
2028
|
+
end
|
2029
|
+
end
|
2030
|
+
|
1723
2031
|
STRUCTURAL_VALUE_METHODS = (
|
1724
2032
|
Relation::VALUE_METHODS -
|
1725
2033
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|