activerecord 7.0.4.3 → 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 +1657 -1274
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +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 -3
- 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/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +327 -222
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +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/time_zone_conversion.rb +0 -4
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +108 -26
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +16 -32
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +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 +155 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +290 -124
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -102
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -112
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -14
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +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 +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +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 +362 -60
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -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/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +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/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +135 -71
- data/lib/active_record/future_result.rb +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/optimistic.rb +32 -18
- 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 +9 -11
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +105 -7
- data/lib/active_record/migration/compatibility.rb +157 -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 +195 -42
- 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 +182 -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 +232 -81
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +387 -71
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +91 -35
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +50 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +9 -9
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +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 +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/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 +48 -12
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -3,14 +3,13 @@
|
|
3
3
|
require "active_record/relation/from_clause"
|
4
4
|
require "active_record/relation/query_attribute"
|
5
5
|
require "active_record/relation/where_clause"
|
6
|
-
require "active_model/forbidden_attributes_protection"
|
7
6
|
require "active_support/core_ext/array/wrap"
|
8
7
|
|
9
8
|
module ActiveRecord
|
10
9
|
module QueryMethods
|
11
10
|
include ActiveModel::ForbiddenAttributesProtection
|
12
11
|
|
13
|
-
# WhereChain objects act as placeholder for queries in which +where+ does not have any parameter.
|
12
|
+
# +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter.
|
14
13
|
# In this case, +where+ can be chained to return a new relation.
|
15
14
|
class WhereChain
|
16
15
|
def initialize(scope) # :nodoc:
|
@@ -39,7 +38,7 @@ module ActiveRecord
|
|
39
38
|
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
40
39
|
#
|
41
40
|
# User.where.not(name: "Jon", role: "admin")
|
42
|
-
# # SELECT * FROM users WHERE NOT (name
|
41
|
+
# # SELECT * FROM users WHERE NOT (name = 'Jon' AND role = 'admin')
|
43
42
|
#
|
44
43
|
# If there is a non-nil condition on a nullable column in the hash condition, the records that have
|
45
44
|
# nil values on the nullable column won't be returned.
|
@@ -77,7 +76,11 @@ module ActiveRecord
|
|
77
76
|
associations.each do |association|
|
78
77
|
reflection = scope_association_reflection(association)
|
79
78
|
@scope.joins!(association)
|
80
|
-
|
79
|
+
if reflection.options[:class_name]
|
80
|
+
self.not(association => { reflection.association_primary_key => nil })
|
81
|
+
else
|
82
|
+
self.not(reflection.table_name => { reflection.association_primary_key => nil })
|
83
|
+
end
|
81
84
|
end
|
82
85
|
|
83
86
|
@scope
|
@@ -105,7 +108,11 @@ module ActiveRecord
|
|
105
108
|
associations.each do |association|
|
106
109
|
reflection = scope_association_reflection(association)
|
107
110
|
@scope.left_outer_joins!(association)
|
108
|
-
|
111
|
+
if reflection.options[:class_name]
|
112
|
+
@scope.where!(association => { reflection.association_primary_key => nil })
|
113
|
+
else
|
114
|
+
@scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
|
115
|
+
end
|
109
116
|
end
|
110
117
|
|
111
118
|
@scope
|
@@ -121,6 +128,15 @@ module ActiveRecord
|
|
121
128
|
end
|
122
129
|
end
|
123
130
|
|
131
|
+
# A wrapper to distinguish CTE joins from other nodes.
|
132
|
+
class CTEJoin # :nodoc:
|
133
|
+
attr_reader :name
|
134
|
+
|
135
|
+
def initialize(name)
|
136
|
+
@name = name
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
124
140
|
FROZEN_EMPTY_ARRAY = [].freeze
|
125
141
|
FROZEN_EMPTY_HASH = {}.freeze
|
126
142
|
|
@@ -149,45 +165,69 @@ module ActiveRecord
|
|
149
165
|
|
150
166
|
alias extensions extending_values
|
151
167
|
|
152
|
-
# Specify
|
153
|
-
#
|
168
|
+
# Specify associations +args+ to be eager loaded to prevent N + 1 queries.
|
169
|
+
# A separate query is performed for each association, unless a join is
|
170
|
+
# required by conditions.
|
154
171
|
#
|
155
|
-
#
|
172
|
+
# For example:
|
173
|
+
#
|
174
|
+
# users = User.includes(:address).limit(5)
|
156
175
|
# users.each do |user|
|
157
176
|
# user.address.city
|
158
177
|
# end
|
159
178
|
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
#
|
179
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
180
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
181
|
+
#
|
182
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
183
|
+
# are loaded with a single query.
|
163
184
|
#
|
164
|
-
#
|
185
|
+
# Loading the associations in a separate query will often result in a
|
186
|
+
# performance improvement over a simple join, as a join can result in many
|
187
|
+
# rows that contain redundant data and it performs poorly at scale.
|
165
188
|
#
|
166
|
-
#
|
189
|
+
# You can also specify multiple associations. Each association will result
|
190
|
+
# in an additional query:
|
191
|
+
#
|
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)
|
167
196
|
#
|
168
|
-
# Loading nested
|
197
|
+
# Loading nested associations is possible using a Hash:
|
169
198
|
#
|
170
|
-
#
|
199
|
+
# User.includes(:address, friends: [:address, :followers])
|
171
200
|
#
|
172
201
|
# === Conditions
|
173
202
|
#
|
174
203
|
# If you want to add string conditions to your included models, you'll have
|
175
204
|
# to explicitly reference them. For example:
|
176
205
|
#
|
177
|
-
# User.includes(:posts).where('posts.name = ?', 'example')
|
206
|
+
# User.includes(:posts).where('posts.name = ?', 'example').to_a
|
178
207
|
#
|
179
208
|
# Will throw an error, but this will work:
|
180
209
|
#
|
181
|
-
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
210
|
+
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
|
211
|
+
# # SELECT "users"."id" AS t0_r0, ... FROM "users"
|
212
|
+
# # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
213
|
+
# # WHERE "posts"."name" = ? [["name", "example"]]
|
214
|
+
#
|
215
|
+
# As the <tt>LEFT OUTER JOIN</tt> already contains the posts, the second query for
|
216
|
+
# the posts is no longer performed.
|
182
217
|
#
|
183
218
|
# Note that #includes works with association names while #references needs
|
184
219
|
# the actual table name.
|
185
220
|
#
|
186
|
-
# If you pass the conditions via
|
221
|
+
# If you pass the conditions via a Hash, you don't need to call #references
|
187
222
|
# explicitly, as #where references the tables for you. For example, this
|
188
223
|
# will work correctly:
|
189
224
|
#
|
190
225
|
# User.includes(:posts).where(posts: { name: 'example' })
|
226
|
+
#
|
227
|
+
# NOTE: Conditions affect both sides of an association. For example, the
|
228
|
+
# above code will return only users that have a post named "example",
|
229
|
+
# <em>and will only include posts named "example"</em>, even when a
|
230
|
+
# matching user has other additional posts.
|
191
231
|
def includes(*args)
|
192
232
|
check_if_method_has_arguments!(__callee__, args)
|
193
233
|
spawn.includes!(*args)
|
@@ -198,12 +238,32 @@ module ActiveRecord
|
|
198
238
|
self
|
199
239
|
end
|
200
240
|
|
201
|
-
#
|
241
|
+
# Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
|
242
|
+
# Performs a single query joining all specified associations. For example:
|
202
243
|
#
|
203
|
-
# User.eager_load(:
|
204
|
-
#
|
205
|
-
#
|
206
|
-
#
|
244
|
+
# users = User.eager_load(:address).limit(5)
|
245
|
+
# users.each do |user|
|
246
|
+
# user.address.city
|
247
|
+
# end
|
248
|
+
#
|
249
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
250
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
251
|
+
# # LIMIT 5
|
252
|
+
#
|
253
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
254
|
+
# are loaded with a single joined query.
|
255
|
+
#
|
256
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
257
|
+
# similar to #includes:
|
258
|
+
#
|
259
|
+
# User.eager_load(:address, friends: [:address, :followers])
|
260
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
261
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
262
|
+
# # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
|
263
|
+
# # ...
|
264
|
+
#
|
265
|
+
# NOTE: Loading the associations in a join can result in many rows that
|
266
|
+
# contain redundant data and it performs poorly at scale.
|
207
267
|
def eager_load(*args)
|
208
268
|
check_if_method_has_arguments!(__callee__, args)
|
209
269
|
spawn.eager_load!(*args)
|
@@ -214,10 +274,28 @@ module ActiveRecord
|
|
214
274
|
self
|
215
275
|
end
|
216
276
|
|
217
|
-
#
|
277
|
+
# Specify associations +args+ to be eager loaded using separate queries.
|
278
|
+
# A separate query is performed for each association.
|
279
|
+
#
|
280
|
+
# users = User.preload(:address).limit(5)
|
281
|
+
# users.each do |user|
|
282
|
+
# user.address.city
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
286
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
287
|
+
#
|
288
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
289
|
+
# are loaded with a separate query.
|
218
290
|
#
|
219
|
-
#
|
220
|
-
#
|
291
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
292
|
+
# similar to #includes:
|
293
|
+
#
|
294
|
+
# User.preload(:address, friends: [:address, :followers])
|
295
|
+
# # SELECT "users".* FROM "users"
|
296
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
297
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
298
|
+
# # SELECT ...
|
221
299
|
def preload(*args)
|
222
300
|
check_if_method_has_arguments!(__callee__, args)
|
223
301
|
spawn.preload!(*args)
|
@@ -242,7 +320,7 @@ module ActiveRecord
|
|
242
320
|
end
|
243
321
|
|
244
322
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
245
|
-
# and should therefore be
|
323
|
+
# and should therefore be +JOIN+ed in any query rather than loaded separately.
|
246
324
|
# This method only works in conjunction with #includes.
|
247
325
|
# See #includes for more details.
|
248
326
|
#
|
@@ -286,10 +364,18 @@ module ActiveRecord
|
|
286
364
|
# Model.select(:field, :other_field, :and_one_more)
|
287
365
|
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
|
288
366
|
#
|
367
|
+
# The argument also can be a hash of fields and aliases.
|
368
|
+
#
|
369
|
+
# Model.select(models: { field: :alias, other_field: :other_alias })
|
370
|
+
# # => [#<Model id: nil, alias: "value", other_alias: "value">]
|
371
|
+
#
|
372
|
+
# Model.select(models: [:field, :other_field])
|
373
|
+
# # => [#<Model id: nil, field: "value", other_field: "value">]
|
374
|
+
#
|
289
375
|
# You can also use one or more strings, which will be used unchanged as SELECT fields.
|
290
376
|
#
|
291
377
|
# Model.select('field AS field_one', 'other_field AS field_two')
|
292
|
-
# # => [#<Model id: nil,
|
378
|
+
# # => [#<Model id: nil, field_one: "value", field_two: "value">]
|
293
379
|
#
|
294
380
|
# If an alias was specified, it will be accessible from the resulting objects:
|
295
381
|
#
|
@@ -300,7 +386,7 @@ module ActiveRecord
|
|
300
386
|
# except +id+ will throw ActiveModel::MissingAttributeError:
|
301
387
|
#
|
302
388
|
# Model.select(:field).first.other_field
|
303
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
389
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
|
304
390
|
def select(*fields)
|
305
391
|
if block_given?
|
306
392
|
if fields.any?
|
@@ -311,6 +397,8 @@ module ActiveRecord
|
|
311
397
|
end
|
312
398
|
|
313
399
|
check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
|
400
|
+
|
401
|
+
fields = process_select_args(fields)
|
314
402
|
spawn._select!(*fields)
|
315
403
|
end
|
316
404
|
|
@@ -319,6 +407,66 @@ module ActiveRecord
|
|
319
407
|
self
|
320
408
|
end
|
321
409
|
|
410
|
+
# Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
411
|
+
#
|
412
|
+
# Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to
|
413
|
+
# use CTE's with MySQL 5.7.
|
414
|
+
#
|
415
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
|
416
|
+
# # => ActiveRecord::Relation
|
417
|
+
# # WITH posts_with_tags AS (
|
418
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
419
|
+
# # )
|
420
|
+
# # SELECT * FROM posts
|
421
|
+
#
|
422
|
+
# Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
|
423
|
+
#
|
424
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
|
425
|
+
# # => ActiveRecord::Relation
|
426
|
+
# # WITH posts_with_tags AS (
|
427
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
428
|
+
# # )
|
429
|
+
# # SELECT * FROM posts_with_tags AS posts
|
430
|
+
#
|
431
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
|
432
|
+
# # => ActiveRecord::Relation
|
433
|
+
# # WITH posts_with_tags AS (
|
434
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
435
|
+
# # )
|
436
|
+
# # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
|
437
|
+
#
|
438
|
+
# It is recommended to pass a query as ActiveRecord::Relation. If that is not possible
|
439
|
+
# and you have verified it is safe for the database, you can pass it as SQL literal
|
440
|
+
# using +Arel+.
|
441
|
+
#
|
442
|
+
# Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
|
443
|
+
#
|
444
|
+
# Great caution should be taken to avoid SQL injection vulnerabilities. This method should not
|
445
|
+
# be used with unsafe values that include unsanitized input.
|
446
|
+
#
|
447
|
+
# To add multiple CTEs just pass multiple key-value pairs
|
448
|
+
#
|
449
|
+
# Post.with(
|
450
|
+
# posts_with_comments: Post.where("comments_count > ?", 0),
|
451
|
+
# posts_with_tags: Post.where("tags_count > ?", 0)
|
452
|
+
# )
|
453
|
+
#
|
454
|
+
# or chain multiple +.with+ calls
|
455
|
+
#
|
456
|
+
# Post
|
457
|
+
# .with(posts_with_comments: Post.where("comments_count > ?", 0))
|
458
|
+
# .with(posts_with_tags: Post.where("tags_count > ?", 0))
|
459
|
+
def with(*args)
|
460
|
+
check_if_method_has_arguments!(__callee__, args)
|
461
|
+
spawn.with!(*args)
|
462
|
+
end
|
463
|
+
|
464
|
+
# Like #with, but modifies relation in place.
|
465
|
+
def with!(*args) # :nodoc:
|
466
|
+
self.with_values += args
|
467
|
+
self
|
468
|
+
end
|
469
|
+
|
322
470
|
# Allows you to change a previously set select statement.
|
323
471
|
#
|
324
472
|
# Post.select(:title, :body)
|
@@ -331,6 +479,7 @@ module ActiveRecord
|
|
331
479
|
# Note that we're unscoping the entire select statement.
|
332
480
|
def reselect(*args)
|
333
481
|
check_if_method_has_arguments!(__callee__, args)
|
482
|
+
args = process_select_args(args)
|
334
483
|
spawn.reselect!(*args)
|
335
484
|
end
|
336
485
|
|
@@ -370,6 +519,27 @@ module ActiveRecord
|
|
370
519
|
self
|
371
520
|
end
|
372
521
|
|
522
|
+
# Allows you to change a previously set group statement.
|
523
|
+
#
|
524
|
+
# Post.group(:title, :body)
|
525
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
|
526
|
+
#
|
527
|
+
# Post.group(:title, :body).regroup(:title)
|
528
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
|
529
|
+
#
|
530
|
+
# This is short-hand for <tt>unscope(:group).group(fields)</tt>.
|
531
|
+
# Note that we're unscoping the entire group statement.
|
532
|
+
def regroup(*args)
|
533
|
+
check_if_method_has_arguments!(__callee__, args)
|
534
|
+
spawn.regroup!(*args)
|
535
|
+
end
|
536
|
+
|
537
|
+
# Same as #regroup but operates on relation in-place instead of copying.
|
538
|
+
def regroup!(*args) # :nodoc:
|
539
|
+
self.group_values = args
|
540
|
+
self
|
541
|
+
end
|
542
|
+
|
373
543
|
# Applies an <code>ORDER BY</code> clause to a query.
|
374
544
|
#
|
375
545
|
# #order accepts arguments in one of several formats.
|
@@ -418,7 +588,7 @@ module ActiveRecord
|
|
418
588
|
# User.order(Arel.sql('end_date - start_date'))
|
419
589
|
# # SELECT "users".* FROM "users" ORDER BY end_date - start_date
|
420
590
|
#
|
421
|
-
# Custom query syntax, like JSON columns for
|
591
|
+
# Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
|
422
592
|
#
|
423
593
|
# User.order(Arel.sql("payload->>'kind'"))
|
424
594
|
# # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
|
@@ -436,13 +606,16 @@ module ActiveRecord
|
|
436
606
|
self
|
437
607
|
end
|
438
608
|
|
439
|
-
# Allows to specify an order by a specific set of values.
|
440
|
-
# adapter this will either use a CASE statement or a built-in function.
|
609
|
+
# Allows to specify an order by a specific set of values.
|
441
610
|
#
|
442
611
|
# User.in_order_of(:id, [1, 5, 3])
|
443
612
|
# # SELECT "users".* FROM "users"
|
444
|
-
# # ORDER BY FIELD("users"."id", 1, 5, 3)
|
445
613
|
# # WHERE "users"."id" IN (1, 5, 3)
|
614
|
+
# # ORDER BY CASE
|
615
|
+
# # WHEN "users"."id" = 1 THEN 1
|
616
|
+
# # WHEN "users"."id" = 5 THEN 2
|
617
|
+
# # WHEN "users"."id" = 3 THEN 3
|
618
|
+
# # END ASC
|
446
619
|
#
|
447
620
|
def in_order_of(column, values)
|
448
621
|
klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
|
@@ -452,11 +625,18 @@ module ActiveRecord
|
|
452
625
|
self.references_values |= references unless references.empty?
|
453
626
|
|
454
627
|
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
455
|
-
arel_column = column.is_a?(
|
628
|
+
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
|
629
|
+
|
630
|
+
where_clause =
|
631
|
+
if values.include?(nil)
|
632
|
+
arel_column.in(values.compact).or(arel_column.eq(nil))
|
633
|
+
else
|
634
|
+
arel_column.in(values)
|
635
|
+
end
|
456
636
|
|
457
637
|
spawn
|
458
|
-
.order!(
|
459
|
-
.where!(
|
638
|
+
.order!(build_case_for_value_position(arel_column, values))
|
639
|
+
.where!(where_clause)
|
460
640
|
end
|
461
641
|
|
462
642
|
# Replaces any existing order defined on the relation with the specified order.
|
@@ -467,7 +647,7 @@ module ActiveRecord
|
|
467
647
|
#
|
468
648
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
469
649
|
#
|
470
|
-
# generates a query with
|
650
|
+
# generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
|
471
651
|
def reorder(*args)
|
472
652
|
check_if_method_has_arguments!(__callee__, args) do
|
473
653
|
sanitize_order_arguments(args)
|
@@ -486,7 +666,8 @@ module ActiveRecord
|
|
486
666
|
|
487
667
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
488
668
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
489
|
-
:includes, :
|
669
|
+
:includes, :eager_load, :preload, :from, :readonly,
|
670
|
+
:having, :optimizer_hints])
|
490
671
|
|
491
672
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
492
673
|
# This is useful when passing around chains of relations and would like to
|
@@ -596,7 +777,7 @@ module ActiveRecord
|
|
596
777
|
# Performs LEFT OUTER JOINs on +args+:
|
597
778
|
#
|
598
779
|
# User.left_outer_joins(:posts)
|
599
|
-
#
|
780
|
+
# # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
600
781
|
#
|
601
782
|
def left_outer_joins(*args)
|
602
783
|
check_if_method_has_arguments!(__callee__, args)
|
@@ -616,7 +797,7 @@ module ActiveRecord
|
|
616
797
|
# SQL is given as an illustration; the actual query generated may be different depending
|
617
798
|
# on the database adapter.
|
618
799
|
#
|
619
|
-
# ===
|
800
|
+
# === \String
|
620
801
|
#
|
621
802
|
# A single string, without additional arguments, is passed to the query
|
622
803
|
# constructor as an SQL fragment, and used in the where clause of the query.
|
@@ -628,7 +809,7 @@ module ActiveRecord
|
|
628
809
|
# to injection attacks if not done properly. As an alternative, it is recommended
|
629
810
|
# to use one of the following methods.
|
630
811
|
#
|
631
|
-
# ===
|
812
|
+
# === \Array
|
632
813
|
#
|
633
814
|
# If an array is passed, then the first element of the array is treated as a template, and
|
634
815
|
# the remaining elements are inserted into the template to generate the condition.
|
@@ -668,7 +849,7 @@ module ActiveRecord
|
|
668
849
|
# dependencies on the underlying database. If your code is intended for general consumption,
|
669
850
|
# test with multiple database backends.
|
670
851
|
#
|
671
|
-
# ===
|
852
|
+
# === \Hash
|
672
853
|
#
|
673
854
|
# #where will also accept a hash condition, in which the keys are fields and the values
|
674
855
|
# are values to be searched for.
|
@@ -702,6 +883,12 @@ module ActiveRecord
|
|
702
883
|
# PriceEstimate.where(estimate_of: treasure)
|
703
884
|
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
704
885
|
#
|
886
|
+
# Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
|
887
|
+
# an array of columns with an array of tuples as values.
|
888
|
+
#
|
889
|
+
# Article.where([:author_id, :id] => [[15, 1], [15, 2]])
|
890
|
+
# # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
|
891
|
+
#
|
705
892
|
# === Joins
|
706
893
|
#
|
707
894
|
# If the relation is the result of a join, you may create a condition which uses any of the
|
@@ -714,7 +901,7 @@ module ActiveRecord
|
|
714
901
|
# User.joins(:posts).where("posts.published" => true)
|
715
902
|
# User.joins(:posts).where(posts: { published: true })
|
716
903
|
#
|
717
|
-
# ===
|
904
|
+
# === No Argument
|
718
905
|
#
|
719
906
|
# If no argument is passed, #where returns a new instance of WhereChain, that
|
720
907
|
# can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
|
@@ -738,7 +925,7 @@ module ActiveRecord
|
|
738
925
|
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
739
926
|
# # WHERE "authors"."id" IS NULL
|
740
927
|
#
|
741
|
-
# ===
|
928
|
+
# === Blank Condition
|
742
929
|
#
|
743
930
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
744
931
|
# the current relation.
|
@@ -771,6 +958,8 @@ module ActiveRecord
|
|
771
958
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
772
959
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
773
960
|
def rewhere(conditions)
|
961
|
+
return unscope(:where) if conditions.nil?
|
962
|
+
|
774
963
|
scope = spawn
|
775
964
|
where_clause = scope.build_where_clause(conditions)
|
776
965
|
|
@@ -876,7 +1065,11 @@ module ActiveRecord
|
|
876
1065
|
#
|
877
1066
|
def or(other)
|
878
1067
|
if other.is_a?(Relation)
|
879
|
-
|
1068
|
+
if @none
|
1069
|
+
other.spawn
|
1070
|
+
else
|
1071
|
+
spawn.or!(other)
|
1072
|
+
end
|
880
1073
|
else
|
881
1074
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
882
1075
|
end
|
@@ -989,15 +1182,29 @@ module ActiveRecord
|
|
989
1182
|
end
|
990
1183
|
|
991
1184
|
def none! # :nodoc:
|
992
|
-
|
1185
|
+
unless @none
|
1186
|
+
where!("1=0")
|
1187
|
+
@none = true
|
1188
|
+
end
|
1189
|
+
self
|
993
1190
|
end
|
994
1191
|
|
995
|
-
|
996
|
-
|
1192
|
+
def null_relation? # :nodoc:
|
1193
|
+
@none
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
# Mark a relation as readonly. Attempting to update a record will result in
|
1197
|
+
# an error.
|
997
1198
|
#
|
998
1199
|
# users = User.readonly
|
999
1200
|
# users.first.save
|
1000
1201
|
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
1202
|
+
#
|
1203
|
+
# To make a readonly relation writable, pass +false+.
|
1204
|
+
#
|
1205
|
+
# users.readonly(false)
|
1206
|
+
# users.first.save
|
1207
|
+
# => true
|
1001
1208
|
def readonly(value = true)
|
1002
1209
|
spawn.readonly!(value)
|
1003
1210
|
end
|
@@ -1114,7 +1321,7 @@ module ActiveRecord
|
|
1114
1321
|
#
|
1115
1322
|
# The object returned is a relation, which can be further extended.
|
1116
1323
|
#
|
1117
|
-
# === Using a
|
1324
|
+
# === Using a \Module
|
1118
1325
|
#
|
1119
1326
|
# module Pagination
|
1120
1327
|
# def page(number)
|
@@ -1129,7 +1336,7 @@ module ActiveRecord
|
|
1129
1336
|
#
|
1130
1337
|
# scope = Model.all.extending(Pagination, SomethingElse)
|
1131
1338
|
#
|
1132
|
-
# === Using a
|
1339
|
+
# === Using a Block
|
1133
1340
|
#
|
1134
1341
|
# scope = Model.all.extending do
|
1135
1342
|
# def page(number)
|
@@ -1306,8 +1513,12 @@ module ActiveRecord
|
|
1306
1513
|
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1307
1514
|
when Hash
|
1308
1515
|
opts = opts.transform_keys do |key|
|
1309
|
-
|
1310
|
-
|
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
|
1311
1522
|
end
|
1312
1523
|
references = PredicateBuilder.references(opts)
|
1313
1524
|
self.references_values |= references unless references.empty?
|
@@ -1325,7 +1536,16 @@ module ActiveRecord
|
|
1325
1536
|
end
|
1326
1537
|
alias :build_having_clause :build_where_clause
|
1327
1538
|
|
1539
|
+
def async!
|
1540
|
+
@async = true
|
1541
|
+
self
|
1542
|
+
end
|
1543
|
+
|
1328
1544
|
private
|
1545
|
+
def async
|
1546
|
+
spawn.async!
|
1547
|
+
end
|
1548
|
+
|
1329
1549
|
def lookup_table_klass_from_join_dependencies(table_name)
|
1330
1550
|
each_join_dependencies do |join|
|
1331
1551
|
return join.base_klass if table_name == join.table_name
|
@@ -1340,13 +1560,13 @@ module ActiveRecord
|
|
1340
1560
|
end
|
1341
1561
|
|
1342
1562
|
def build_join_dependencies
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
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?
|
1346
1566
|
|
1347
1567
|
join_dependencies = []
|
1348
1568
|
join_dependencies.unshift construct_join_dependency(
|
1349
|
-
|
1569
|
+
select_named_joins(joins, join_dependencies), nil
|
1350
1570
|
)
|
1351
1571
|
end
|
1352
1572
|
|
@@ -1367,6 +1587,7 @@ module ActiveRecord
|
|
1367
1587
|
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1368
1588
|
|
1369
1589
|
build_order(arel)
|
1590
|
+
build_with(arel)
|
1370
1591
|
build_select(arel)
|
1371
1592
|
|
1372
1593
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
@@ -1402,6 +1623,18 @@ module ActiveRecord
|
|
1402
1623
|
end
|
1403
1624
|
end
|
1404
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
|
+
|
1405
1638
|
def select_association_list(associations, stashed_joins = nil)
|
1406
1639
|
result = []
|
1407
1640
|
associations.each do |association|
|
@@ -1417,20 +1650,21 @@ module ActiveRecord
|
|
1417
1650
|
result
|
1418
1651
|
end
|
1419
1652
|
|
1420
|
-
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1421
|
-
end
|
1422
|
-
|
1423
1653
|
def build_join_buckets
|
1424
1654
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1425
1655
|
|
1426
1656
|
unless left_outer_joins_values.empty?
|
1427
1657
|
stashed_left_joins = []
|
1428
|
-
left_joins =
|
1429
|
-
|
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
|
1430
1664
|
end
|
1431
1665
|
|
1432
1666
|
if joins_values.empty?
|
1433
|
-
buckets[:
|
1667
|
+
buckets[:named_join] = left_joins
|
1434
1668
|
buckets[:stashed_join] = stashed_left_joins
|
1435
1669
|
return buckets, Arel::Nodes::OuterJoin
|
1436
1670
|
else
|
@@ -1456,9 +1690,11 @@ module ActiveRecord
|
|
1456
1690
|
end
|
1457
1691
|
end
|
1458
1692
|
|
1459
|
-
buckets[:
|
1693
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
1460
1694
|
if join.is_a?(Arel::Nodes::Join)
|
1461
1695
|
buckets[:join_node] << join
|
1696
|
+
elsif join.is_a?(CTEJoin)
|
1697
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
1462
1698
|
else
|
1463
1699
|
raise "unknown class: %s" % join.class.name
|
1464
1700
|
end
|
@@ -1475,16 +1711,16 @@ module ActiveRecord
|
|
1475
1711
|
|
1476
1712
|
buckets, join_type = build_join_buckets
|
1477
1713
|
|
1478
|
-
|
1479
|
-
stashed_joins
|
1480
|
-
leading_joins
|
1481
|
-
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]
|
1482
1718
|
|
1483
1719
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1484
1720
|
|
1485
|
-
unless
|
1721
|
+
unless named_joins.empty? && stashed_joins.empty?
|
1486
1722
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1487
|
-
join_dependency = construct_join_dependency(
|
1723
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
1488
1724
|
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1489
1725
|
end
|
1490
1726
|
|
@@ -1502,6 +1738,40 @@ module ActiveRecord
|
|
1502
1738
|
end
|
1503
1739
|
end
|
1504
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
|
+
|
1505
1775
|
def arel_columns(columns)
|
1506
1776
|
columns.flat_map do |field|
|
1507
1777
|
case field
|
@@ -1620,7 +1890,7 @@ module ActiveRecord
|
|
1620
1890
|
when Hash
|
1621
1891
|
arg.map { |field, dir|
|
1622
1892
|
case field
|
1623
|
-
when Arel::Nodes::SqlLiteral
|
1893
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
1624
1894
|
field.public_send(dir.downcase)
|
1625
1895
|
else
|
1626
1896
|
order_column(field.to_s).public_send(dir.downcase)
|
@@ -1644,7 +1914,9 @@ module ActiveRecord
|
|
1644
1914
|
when String, Symbol
|
1645
1915
|
arg
|
1646
1916
|
when Hash
|
1647
|
-
arg.keys
|
1917
|
+
arg.keys.map do |key|
|
1918
|
+
key if key.is_a?(String) || key.is_a?(Symbol)
|
1919
|
+
end
|
1648
1920
|
end
|
1649
1921
|
end
|
1650
1922
|
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
@@ -1661,6 +1933,15 @@ module ActiveRecord
|
|
1661
1933
|
end
|
1662
1934
|
end
|
1663
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
|
+
|
1664
1945
|
def resolve_arel_attributes(attrs)
|
1665
1946
|
attrs.flat_map do |attr|
|
1666
1947
|
case attr
|
@@ -1712,6 +1993,41 @@ module ActiveRecord
|
|
1712
1993
|
end
|
1713
1994
|
end
|
1714
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
|
+
|
1715
2031
|
STRUCTURAL_VALUE_METHODS = (
|
1716
2032
|
Relation::VALUE_METHODS -
|
1717
2033
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|