activerecord 7.0.8 → 7.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1401 -1513
  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 +15 -9
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  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 +13 -10
  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 +295 -199
  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 +128 -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 +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -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 +18 -13
  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 +71 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -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 +53 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +347 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +337 -176
  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 +45 -39
  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 +209 -79
  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 +134 -146
  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 +3 -3
  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 +108 -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 +3 -3
  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 +60 -40
  142. data/lib/active_record/nested_attributes.rb +23 -3
  143. data/lib/active_record/normalization.rb +159 -0
  144. data/lib/active_record/persistence.rb +184 -34
  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 +108 -46
  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 +162 -44
  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 +77 -16
  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/polymorphic_array_value.rb +4 -6
  164. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  165. data/lib/active_record/relation/predicate_builder.rb +26 -14
  166. data/lib/active_record/relation/query_attribute.rb +2 -1
  167. data/lib/active_record/relation/query_methods.rb +351 -62
  168. data/lib/active_record/relation/spawn_methods.rb +18 -1
  169. data/lib/active_record/relation.rb +76 -35
  170. data/lib/active_record/result.rb +19 -5
  171. data/lib/active_record/runtime_registry.rb +10 -1
  172. data/lib/active_record/sanitization.rb +51 -11
  173. data/lib/active_record/schema.rb +2 -3
  174. data/lib/active_record/schema_dumper.rb +46 -7
  175. data/lib/active_record/schema_migration.rb +68 -33
  176. data/lib/active_record/scoping/default.rb +15 -5
  177. data/lib/active_record/scoping/named.rb +2 -2
  178. data/lib/active_record/scoping.rb +2 -1
  179. data/lib/active_record/secure_password.rb +60 -0
  180. data/lib/active_record/secure_token.rb +21 -3
  181. data/lib/active_record/signed_id.rb +7 -5
  182. data/lib/active_record/store.rb +8 -8
  183. data/lib/active_record/suppressor.rb +3 -1
  184. data/lib/active_record/table_metadata.rb +10 -1
  185. data/lib/active_record/tasks/database_tasks.rb +127 -105
  186. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  187. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  188. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  189. data/lib/active_record/test_fixtures.rb +113 -96
  190. data/lib/active_record/timestamp.rb +26 -14
  191. data/lib/active_record/token_for.rb +113 -0
  192. data/lib/active_record/touch_later.rb +11 -6
  193. data/lib/active_record/transactions.rb +36 -10
  194. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  195. data/lib/active_record/type/internal/timezone.rb +7 -2
  196. data/lib/active_record/type/time.rb +4 -0
  197. data/lib/active_record/validations/absence.rb +1 -1
  198. data/lib/active_record/validations/numericality.rb +5 -4
  199. data/lib/active_record/validations/presence.rb +5 -28
  200. data/lib/active_record/validations/uniqueness.rb +47 -2
  201. data/lib/active_record/validations.rb +8 -4
  202. data/lib/active_record/version.rb +1 -1
  203. data/lib/active_record.rb +121 -16
  204. data/lib/arel/errors.rb +10 -0
  205. data/lib/arel/factory_methods.rb +4 -0
  206. data/lib/arel/nodes/binary.rb +6 -1
  207. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  208. data/lib/arel/nodes/cte.rb +36 -0
  209. data/lib/arel/nodes/fragments.rb +35 -0
  210. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  211. data/lib/arel/nodes/leading_join.rb +8 -0
  212. data/lib/arel/nodes/node.rb +111 -2
  213. data/lib/arel/nodes/sql_literal.rb +6 -0
  214. data/lib/arel/nodes/table_alias.rb +4 -0
  215. data/lib/arel/nodes.rb +4 -0
  216. data/lib/arel/predications.rb +2 -0
  217. data/lib/arel/table.rb +9 -5
  218. data/lib/arel/visitors/mysql.rb +8 -1
  219. data/lib/arel/visitors/to_sql.rb +81 -17
  220. data/lib/arel/visitors/visitor.rb +2 -2
  221. data/lib/arel.rb +16 -2
  222. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  223. data/lib/rails/generators/active_record/migration.rb +3 -1
  224. data/lib/rails/generators/active_record/model/USAGE +113 -0
  225. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  226. metadata +46 -11
  227. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  228. 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]