activerecord 7.0.8.7 → 7.1.0.beta1

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.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1339 -1572
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +15 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -9
  15. data/lib/active_record/associations/collection_proxy.rb +16 -11
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader.rb +12 -9
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +193 -97
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
  73. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  74. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  75. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  76. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
  79. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  80. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
  82. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
  86. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  87. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  88. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  89. data/lib/active_record/connection_adapters.rb +3 -1
  90. data/lib/active_record/connection_handling.rb +71 -94
  91. data/lib/active_record/core.rb +128 -138
  92. data/lib/active_record/counter_cache.rb +46 -25
  93. data/lib/active_record/database_configurations/database_config.rb +9 -3
  94. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  95. data/lib/active_record/database_configurations/url_config.rb +17 -11
  96. data/lib/active_record/database_configurations.rb +86 -33
  97. data/lib/active_record/delegated_type.rb +8 -3
  98. data/lib/active_record/deprecator.rb +7 -0
  99. data/lib/active_record/destroy_association_async_job.rb +2 -0
  100. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  102. data/lib/active_record/encryption/config.rb +25 -1
  103. data/lib/active_record/encryption/configurable.rb +12 -19
  104. data/lib/active_record/encryption/context.rb +10 -3
  105. data/lib/active_record/encryption/contexts.rb +5 -1
  106. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  107. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  108. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
  111. data/lib/active_record/encryption/key_generator.rb +12 -1
  112. data/lib/active_record/encryption/message_serializer.rb +2 -0
  113. data/lib/active_record/encryption/properties.rb +3 -3
  114. data/lib/active_record/encryption/scheme.rb +19 -22
  115. data/lib/active_record/encryption.rb +1 -0
  116. data/lib/active_record/enum.rb +113 -26
  117. data/lib/active_record/errors.rb +89 -15
  118. data/lib/active_record/explain.rb +23 -3
  119. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  120. data/lib/active_record/fixture_set/render_context.rb +2 -0
  121. data/lib/active_record/fixture_set/table_row.rb +29 -8
  122. data/lib/active_record/fixtures.rb +119 -71
  123. data/lib/active_record/future_result.rb +30 -5
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +30 -16
  126. data/lib/active_record/insert_all.rb +55 -8
  127. data/lib/active_record/integration.rb +8 -8
  128. data/lib/active_record/internal_metadata.rb +118 -30
  129. data/lib/active_record/locking/pessimistic.rb +5 -2
  130. data/lib/active_record/log_subscriber.rb +29 -12
  131. data/lib/active_record/marshalling.rb +56 -0
  132. data/lib/active_record/message_pack.rb +124 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  134. data/lib/active_record/middleware/database_selector.rb +5 -7
  135. data/lib/active_record/middleware/shard_selector.rb +3 -1
  136. data/lib/active_record/migration/command_recorder.rb +100 -4
  137. data/lib/active_record/migration/compatibility.rb +131 -5
  138. data/lib/active_record/migration/default_strategy.rb +23 -0
  139. data/lib/active_record/migration/execution_strategy.rb +19 -0
  140. data/lib/active_record/migration.rb +213 -109
  141. data/lib/active_record/model_schema.rb +47 -27
  142. data/lib/active_record/nested_attributes.rb +28 -3
  143. data/lib/active_record/normalization.rb +158 -0
  144. data/lib/active_record/persistence.rb +183 -33
  145. data/lib/active_record/promise.rb +84 -0
  146. data/lib/active_record/query_cache.rb +3 -21
  147. data/lib/active_record/query_logs.rb +77 -52
  148. data/lib/active_record/query_logs_formatter.rb +41 -0
  149. data/lib/active_record/querying.rb +15 -2
  150. data/lib/active_record/railtie.rb +107 -45
  151. data/lib/active_record/railties/controller_runtime.rb +10 -5
  152. data/lib/active_record/railties/databases.rake +139 -145
  153. data/lib/active_record/railties/job_runtime.rb +23 -0
  154. data/lib/active_record/readonly_attributes.rb +32 -5
  155. data/lib/active_record/reflection.rb +169 -45
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  157. data/lib/active_record/relation/batches.rb +190 -61
  158. data/lib/active_record/relation/calculations.rb +152 -63
  159. data/lib/active_record/relation/delegation.rb +22 -8
  160. data/lib/active_record/relation/finder_methods.rb +85 -15
  161. data/lib/active_record/relation/merger.rb +2 -0
  162. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  164. data/lib/active_record/relation/predicate_builder.rb +26 -14
  165. data/lib/active_record/relation/query_attribute.rb +2 -1
  166. data/lib/active_record/relation/query_methods.rb +351 -62
  167. data/lib/active_record/relation/spawn_methods.rb +18 -1
  168. data/lib/active_record/relation.rb +76 -35
  169. data/lib/active_record/result.rb +19 -5
  170. data/lib/active_record/runtime_registry.rb +10 -1
  171. data/lib/active_record/sanitization.rb +51 -11
  172. data/lib/active_record/schema.rb +2 -3
  173. data/lib/active_record/schema_dumper.rb +41 -7
  174. data/lib/active_record/schema_migration.rb +68 -33
  175. data/lib/active_record/scoping/default.rb +15 -5
  176. data/lib/active_record/scoping/named.rb +2 -2
  177. data/lib/active_record/scoping.rb +2 -1
  178. data/lib/active_record/secure_password.rb +60 -0
  179. data/lib/active_record/secure_token.rb +21 -3
  180. data/lib/active_record/signed_id.rb +7 -5
  181. data/lib/active_record/store.rb +8 -8
  182. data/lib/active_record/suppressor.rb +3 -1
  183. data/lib/active_record/table_metadata.rb +10 -1
  184. data/lib/active_record/tasks/database_tasks.rb +127 -105
  185. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  186. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  187. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  188. data/lib/active_record/test_fixtures.rb +113 -96
  189. data/lib/active_record/timestamp.rb +26 -14
  190. data/lib/active_record/token_for.rb +113 -0
  191. data/lib/active_record/touch_later.rb +11 -6
  192. data/lib/active_record/transactions.rb +36 -10
  193. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  194. data/lib/active_record/type/internal/timezone.rb +7 -2
  195. data/lib/active_record/type/time.rb +4 -0
  196. data/lib/active_record/validations/absence.rb +1 -1
  197. data/lib/active_record/validations/numericality.rb +5 -4
  198. data/lib/active_record/validations/presence.rb +5 -28
  199. data/lib/active_record/validations/uniqueness.rb +47 -2
  200. data/lib/active_record/validations.rb +8 -4
  201. data/lib/active_record/version.rb +1 -1
  202. data/lib/active_record.rb +121 -16
  203. data/lib/arel/errors.rb +10 -0
  204. data/lib/arel/factory_methods.rb +4 -0
  205. data/lib/arel/nodes/binary.rb +6 -1
  206. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  207. data/lib/arel/nodes/cte.rb +36 -0
  208. data/lib/arel/nodes/fragments.rb +35 -0
  209. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  210. data/lib/arel/nodes/leading_join.rb +8 -0
  211. data/lib/arel/nodes/node.rb +111 -2
  212. data/lib/arel/nodes/sql_literal.rb +6 -0
  213. data/lib/arel/nodes/table_alias.rb +4 -0
  214. data/lib/arel/nodes.rb +4 -0
  215. data/lib/arel/predications.rb +2 -0
  216. data/lib/arel/table.rb +9 -5
  217. data/lib/arel/visitors/mysql.rb +8 -1
  218. data/lib/arel/visitors/to_sql.rb +81 -17
  219. data/lib/arel/visitors/visitor.rb +2 -2
  220. data/lib/arel.rb +16 -2
  221. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  222. data/lib/rails/generators/active_record/migration.rb +3 -1
  223. data/lib/rails/generators/active_record/model/USAGE +113 -0
  224. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  225. metadata +52 -17
  226. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  227. 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 == 'Jon' AND role == 'admin')
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.
@@ -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 relationships to be included in the result set. For
161
- # example:
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
- # users = User.includes(:address)
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
- # allows you to access the +address+ attribute of the +User+ model without
169
- # firing an additional query. This will often result in a
170
- # performance improvement over a simple join.
179
+ # # SELECT "users".* FROM "users" LIMIT 5
180
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
171
181
  #
172
- # You can also specify multiple relationships, like this:
182
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
183
+ # are loaded with a single query.
173
184
  #
174
- # users = User.includes(:address, :friends)
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.
175
188
  #
176
- # Loading nested relationships is possible using a Hash:
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)
177
196
  #
178
- # users = User.includes(:address, friends: [:address, :followers])
197
+ # Loading nested associations is possible using a Hash:
198
+ #
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 hash, you don't need to call #references
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
- # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
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
210
248
  #
211
- # User.eager_load(:posts)
212
- # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
213
- # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
214
- # # "users"."id"
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.
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
- # Allows preloading of +args+, in the same way that #includes does:
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
226
284
  #
227
- # User.preload(:posts)
228
- # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
285
+ # # SELECT "users".* FROM "users" LIMIT 5
286
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
287
+ #
288
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
289
+ # are loaded with a separate query.
290
+ #
291
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
292
+ # similar to #includes:
293
+ #
294
+ # User.preload(:address, friends: [:address, :followers])
295
+ # # SELECT "users".* FROM "users"
296
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
297
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
298
+ # # SELECT ...
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 JOINed in any query rather than loaded separately.
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: other_field
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.
@@ -463,7 +625,7 @@ module ActiveRecord
463
625
  self.references_values |= references unless references.empty?
464
626
 
465
627
  values = values.map { |value| type_caster.type_cast_for_database(column, value) }
466
- arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
628
+ arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
467
629
 
468
630
  where_clause =
469
631
  if values.include?(nil)
@@ -485,7 +647,7 @@ module ActiveRecord
485
647
  #
486
648
  # User.order('email DESC').reorder('id ASC').order('name ASC')
487
649
  #
488
- # generates a query with 'ORDER BY id ASC, name ASC'.
650
+ # generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
489
651
  def reorder(*args)
490
652
  check_if_method_has_arguments!(__callee__, args) do
491
653
  sanitize_order_arguments(args)
@@ -504,7 +666,8 @@ module ActiveRecord
504
666
 
505
667
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
506
668
  :limit, :offset, :joins, :left_outer_joins, :annotate,
507
- :includes, :from, :readonly, :having, :optimizer_hints])
669
+ :includes, :eager_load, :preload, :from, :readonly,
670
+ :having, :optimizer_hints])
508
671
 
509
672
  # Removes an unwanted relation that is already defined on a chain of relations.
510
673
  # This is useful when passing around chains of relations and would like to
@@ -614,7 +777,7 @@ module ActiveRecord
614
777
  # Performs LEFT OUTER JOINs on +args+:
615
778
  #
616
779
  # User.left_outer_joins(:posts)
617
- # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
780
+ # # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
618
781
  #
619
782
  def left_outer_joins(*args)
620
783
  check_if_method_has_arguments!(__callee__, args)
@@ -634,7 +797,7 @@ module ActiveRecord
634
797
  # SQL is given as an illustration; the actual query generated may be different depending
635
798
  # on the database adapter.
636
799
  #
637
- # === string
800
+ # === \String
638
801
  #
639
802
  # A single string, without additional arguments, is passed to the query
640
803
  # constructor as an SQL fragment, and used in the where clause of the query.
@@ -646,7 +809,7 @@ module ActiveRecord
646
809
  # to injection attacks if not done properly. As an alternative, it is recommended
647
810
  # to use one of the following methods.
648
811
  #
649
- # === array
812
+ # === \Array
650
813
  #
651
814
  # If an array is passed, then the first element of the array is treated as a template, and
652
815
  # the remaining elements are inserted into the template to generate the condition.
@@ -686,7 +849,7 @@ module ActiveRecord
686
849
  # dependencies on the underlying database. If your code is intended for general consumption,
687
850
  # test with multiple database backends.
688
851
  #
689
- # === hash
852
+ # === \Hash
690
853
  #
691
854
  # #where will also accept a hash condition, in which the keys are fields and the values
692
855
  # are values to be searched for.
@@ -720,6 +883,12 @@ module ActiveRecord
720
883
  # PriceEstimate.where(estimate_of: treasure)
721
884
  # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
722
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
+ #
723
892
  # === Joins
724
893
  #
725
894
  # If the relation is the result of a join, you may create a condition which uses any of the
@@ -732,7 +901,7 @@ module ActiveRecord
732
901
  # User.joins(:posts).where("posts.published" => true)
733
902
  # User.joins(:posts).where(posts: { published: true })
734
903
  #
735
- # === no argument
904
+ # === No Argument
736
905
  #
737
906
  # If no argument is passed, #where returns a new instance of WhereChain, that
738
907
  # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
@@ -756,7 +925,7 @@ module ActiveRecord
756
925
  # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
757
926
  # # WHERE "authors"."id" IS NULL
758
927
  #
759
- # === blank condition
928
+ # === Blank Condition
760
929
  #
761
930
  # If the condition is any blank-ish object, then #where is a no-op and returns
762
931
  # the current relation.
@@ -789,6 +958,8 @@ module ActiveRecord
789
958
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
790
959
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
791
960
  def rewhere(conditions)
961
+ return unscope(:where) if conditions.nil?
962
+
792
963
  scope = spawn
793
964
  where_clause = scope.build_where_clause(conditions)
794
965
 
@@ -894,7 +1065,11 @@ module ActiveRecord
894
1065
  #
895
1066
  def or(other)
896
1067
  if other.is_a?(Relation)
897
- spawn.or!(other)
1068
+ if @none
1069
+ other.spawn
1070
+ else
1071
+ spawn.or!(other)
1072
+ end
898
1073
  else
899
1074
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
900
1075
  end
@@ -1007,15 +1182,29 @@ module ActiveRecord
1007
1182
  end
1008
1183
 
1009
1184
  def none! # :nodoc:
1010
- where!("1=0").extending!(NullRelation)
1185
+ unless @none
1186
+ where!("1=0")
1187
+ @none = true
1188
+ end
1189
+ self
1190
+ end
1191
+
1192
+ def null_relation? # :nodoc:
1193
+ @none
1011
1194
  end
1012
1195
 
1013
- # Sets readonly attributes for the returned relation. If value is
1014
- # true (default), attempting to update a record will result in an error.
1196
+ # Mark a relation as readonly. Attempting to update a record will result in
1197
+ # an error.
1015
1198
  #
1016
1199
  # users = User.readonly
1017
1200
  # users.first.save
1018
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
1019
1208
  def readonly(value = true)
1020
1209
  spawn.readonly!(value)
1021
1210
  end
@@ -1132,7 +1321,7 @@ module ActiveRecord
1132
1321
  #
1133
1322
  # The object returned is a relation, which can be further extended.
1134
1323
  #
1135
- # === Using a module
1324
+ # === Using a \Module
1136
1325
  #
1137
1326
  # module Pagination
1138
1327
  # def page(number)
@@ -1147,7 +1336,7 @@ module ActiveRecord
1147
1336
  #
1148
1337
  # scope = Model.all.extending(Pagination, SomethingElse)
1149
1338
  #
1150
- # === Using a block
1339
+ # === Using a Block
1151
1340
  #
1152
1341
  # scope = Model.all.extending do
1153
1342
  # def page(number)
@@ -1324,8 +1513,12 @@ module ActiveRecord
1324
1513
  parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1325
1514
  when Hash
1326
1515
  opts = opts.transform_keys do |key|
1327
- key = key.to_s
1328
- klass.attribute_aliases[key] || key
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
1329
1522
  end
1330
1523
  references = PredicateBuilder.references(opts)
1331
1524
  self.references_values |= references unless references.empty?
@@ -1343,7 +1536,16 @@ module ActiveRecord
1343
1536
  end
1344
1537
  alias :build_having_clause :build_where_clause
1345
1538
 
1539
+ def async!
1540
+ @async = true
1541
+ self
1542
+ end
1543
+
1346
1544
  private
1545
+ def async
1546
+ spawn.async!
1547
+ end
1548
+
1347
1549
  def lookup_table_klass_from_join_dependencies(table_name)
1348
1550
  each_join_dependencies do |join|
1349
1551
  return join.base_klass if table_name == join.table_name
@@ -1358,13 +1560,13 @@ module ActiveRecord
1358
1560
  end
1359
1561
 
1360
1562
  def build_join_dependencies
1361
- associations = joins_values | left_outer_joins_values
1362
- associations |= eager_load_values unless eager_load_values.empty?
1363
- associations |= includes_values unless includes_values.empty?
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?
1364
1566
 
1365
1567
  join_dependencies = []
1366
1568
  join_dependencies.unshift construct_join_dependency(
1367
- select_association_list(associations, join_dependencies), nil
1569
+ select_named_joins(joins, join_dependencies), nil
1368
1570
  )
1369
1571
  end
1370
1572
 
@@ -1385,6 +1587,7 @@ module ActiveRecord
1385
1587
  arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1386
1588
 
1387
1589
  build_order(arel)
1590
+ build_with(arel)
1388
1591
  build_select(arel)
1389
1592
 
1390
1593
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
@@ -1420,6 +1623,18 @@ module ActiveRecord
1420
1623
  end
1421
1624
  end
1422
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
+
1423
1638
  def select_association_list(associations, stashed_joins = nil)
1424
1639
  result = []
1425
1640
  associations.each do |association|
@@ -1435,20 +1650,21 @@ module ActiveRecord
1435
1650
  result
1436
1651
  end
1437
1652
 
1438
- class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1439
- end
1440
-
1441
1653
  def build_join_buckets
1442
1654
  buckets = Hash.new { |h, k| h[k] = [] }
1443
1655
 
1444
1656
  unless left_outer_joins_values.empty?
1445
1657
  stashed_left_joins = []
1446
- left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1447
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
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
1448
1664
  end
1449
1665
 
1450
1666
  if joins_values.empty?
1451
- buckets[:association_join] = left_joins
1667
+ buckets[:named_join] = left_joins
1452
1668
  buckets[:stashed_join] = stashed_left_joins
1453
1669
  return buckets, Arel::Nodes::OuterJoin
1454
1670
  else
@@ -1474,9 +1690,11 @@ module ActiveRecord
1474
1690
  end
1475
1691
  end
1476
1692
 
1477
- buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1693
+ buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
1478
1694
  if join.is_a?(Arel::Nodes::Join)
1479
1695
  buckets[:join_node] << join
1696
+ elsif join.is_a?(CTEJoin)
1697
+ buckets[:join_node] << build_with_join_node(join.name)
1480
1698
  else
1481
1699
  raise "unknown class: %s" % join.class.name
1482
1700
  end
@@ -1493,16 +1711,16 @@ module ActiveRecord
1493
1711
 
1494
1712
  buckets, join_type = build_join_buckets
1495
1713
 
1496
- association_joins = buckets[:association_join]
1497
- stashed_joins = buckets[:stashed_join]
1498
- leading_joins = buckets[:leading_join]
1499
- join_nodes = buckets[:join_node]
1714
+ named_joins = buckets[:named_join]
1715
+ stashed_joins = buckets[:stashed_join]
1716
+ leading_joins = buckets[:leading_join]
1717
+ join_nodes = buckets[:join_node]
1500
1718
 
1501
1719
  join_sources.concat(leading_joins) unless leading_joins.empty?
1502
1720
 
1503
- unless association_joins.empty? && stashed_joins.empty?
1721
+ unless named_joins.empty? && stashed_joins.empty?
1504
1722
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1505
- join_dependency = construct_join_dependency(association_joins, join_type)
1723
+ join_dependency = construct_join_dependency(named_joins, join_type)
1506
1724
  join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1507
1725
  end
1508
1726
 
@@ -1520,6 +1738,40 @@ module ActiveRecord
1520
1738
  end
1521
1739
  end
1522
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
+
1523
1775
  def arel_columns(columns)
1524
1776
  columns.flat_map do |field|
1525
1777
  case field
@@ -1638,7 +1890,7 @@ module ActiveRecord
1638
1890
  when Hash
1639
1891
  arg.map { |field, dir|
1640
1892
  case field
1641
- when Arel::Nodes::SqlLiteral
1893
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
1642
1894
  field.public_send(dir.downcase)
1643
1895
  else
1644
1896
  order_column(field.to_s).public_send(dir.downcase)
@@ -1662,7 +1914,9 @@ module ActiveRecord
1662
1914
  when String, Symbol
1663
1915
  arg
1664
1916
  when Hash
1665
- arg.keys
1917
+ arg.keys.map do |key|
1918
+ key if key.is_a?(String) || key.is_a?(Symbol)
1919
+ end
1666
1920
  end
1667
1921
  end
1668
1922
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
@@ -1739,6 +1993,41 @@ module ActiveRecord
1739
1993
  end
1740
1994
  end
1741
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
+
1742
2031
  STRUCTURAL_VALUE_METHODS = (
1743
2032
  Relation::VALUE_METHODS -
1744
2033
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]