activerecord 7.0.6 → 7.1.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 +1424 -1390
- data/MIT-LICENSE +1 -1
- data/README.rdoc +15 -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 +18 -3
- 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 +27 -6
- 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 +295 -199
- 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 +40 -26
- 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 +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- 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 +496 -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 +148 -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 +71 -40
- 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 +349 -55
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +338 -176
- 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 +45 -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 +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +71 -94
- data/lib/active_record/core.rb +136 -148
- 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 +8 -3
- 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 +36 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
- 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 +113 -26
- data/lib/active_record/errors.rb +108 -15
- 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 +119 -71
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- 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 +5 -7
- 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 +142 -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.rb +265 -112
- data/lib/active_record/model_schema.rb +60 -40
- data/lib/active_record/nested_attributes.rb +21 -3
- data/lib/active_record/normalization.rb +159 -0
- data/lib/active_record/persistence.rb +187 -35
- 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 +12 -8
- data/lib/active_record/railties/databases.rake +139 -145
- 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 +162 -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 +160 -63
- data/lib/active_record/relation/delegation.rb +22 -8
- 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 +76 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +10 -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 +0 -8
- 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 +50 -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]
|