activerecord 7.0.6 → 7.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (231) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1424 -1390
  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 +16 -10
  15. data/lib/active_record/associations/collection_proxy.rb +20 -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 -7
  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 +6 -8
  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 +60 -18
  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 -124
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +496 -102
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +214 -113
  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 +23 -144
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +21 -14
  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 +14 -3
  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/oid/timestamp_with_time_zone.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +15 -8
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  75. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  76. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  77. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  78. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +349 -55
  79. data/lib/active_record/connection_adapters/postgresql_adapter.rb +338 -176
  80. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  81. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  82. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
  83. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  84. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  85. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  86. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +210 -83
  87. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  88. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  89. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  90. data/lib/active_record/connection_adapters.rb +3 -1
  91. data/lib/active_record/connection_handling.rb +71 -94
  92. data/lib/active_record/core.rb +136 -148
  93. data/lib/active_record/counter_cache.rb +46 -25
  94. data/lib/active_record/database_configurations/database_config.rb +9 -3
  95. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  96. data/lib/active_record/database_configurations/url_config.rb +17 -11
  97. data/lib/active_record/database_configurations.rb +86 -33
  98. data/lib/active_record/delegated_type.rb +8 -3
  99. data/lib/active_record/deprecator.rb +7 -0
  100. data/lib/active_record/destroy_association_async_job.rb +2 -0
  101. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  102. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  103. data/lib/active_record/encryption/config.rb +25 -1
  104. data/lib/active_record/encryption/configurable.rb +12 -19
  105. data/lib/active_record/encryption/context.rb +10 -3
  106. data/lib/active_record/encryption/contexts.rb +5 -1
  107. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  108. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  109. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  110. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  111. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  112. data/lib/active_record/encryption/key_generator.rb +12 -1
  113. data/lib/active_record/encryption/message_serializer.rb +2 -0
  114. data/lib/active_record/encryption/properties.rb +3 -3
  115. data/lib/active_record/encryption/scheme.rb +19 -22
  116. data/lib/active_record/encryption.rb +1 -0
  117. data/lib/active_record/enum.rb +113 -26
  118. data/lib/active_record/errors.rb +108 -15
  119. data/lib/active_record/explain.rb +23 -3
  120. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  121. data/lib/active_record/fixture_set/render_context.rb +2 -0
  122. data/lib/active_record/fixture_set/table_row.rb +29 -8
  123. data/lib/active_record/fixtures.rb +119 -71
  124. data/lib/active_record/future_result.rb +30 -5
  125. data/lib/active_record/gem_version.rb +3 -3
  126. data/lib/active_record/inheritance.rb +30 -16
  127. data/lib/active_record/insert_all.rb +55 -8
  128. data/lib/active_record/integration.rb +8 -8
  129. data/lib/active_record/internal_metadata.rb +120 -30
  130. data/lib/active_record/locking/pessimistic.rb +5 -2
  131. data/lib/active_record/log_subscriber.rb +29 -12
  132. data/lib/active_record/marshalling.rb +56 -0
  133. data/lib/active_record/message_pack.rb +124 -0
  134. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  135. data/lib/active_record/middleware/database_selector.rb +5 -7
  136. data/lib/active_record/middleware/shard_selector.rb +3 -1
  137. data/lib/active_record/migration/command_recorder.rb +104 -5
  138. data/lib/active_record/migration/compatibility.rb +142 -58
  139. data/lib/active_record/migration/default_strategy.rb +23 -0
  140. data/lib/active_record/migration/execution_strategy.rb +19 -0
  141. data/lib/active_record/migration.rb +265 -112
  142. data/lib/active_record/model_schema.rb +60 -40
  143. data/lib/active_record/nested_attributes.rb +21 -3
  144. data/lib/active_record/normalization.rb +159 -0
  145. data/lib/active_record/persistence.rb +187 -35
  146. data/lib/active_record/promise.rb +84 -0
  147. data/lib/active_record/query_cache.rb +3 -21
  148. data/lib/active_record/query_logs.rb +77 -52
  149. data/lib/active_record/query_logs_formatter.rb +41 -0
  150. data/lib/active_record/querying.rb +15 -2
  151. data/lib/active_record/railtie.rb +109 -47
  152. data/lib/active_record/railties/controller_runtime.rb +12 -8
  153. data/lib/active_record/railties/databases.rake +139 -145
  154. data/lib/active_record/railties/job_runtime.rb +23 -0
  155. data/lib/active_record/readonly_attributes.rb +32 -5
  156. data/lib/active_record/reflection.rb +162 -44
  157. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  158. data/lib/active_record/relation/batches.rb +190 -61
  159. data/lib/active_record/relation/calculations.rb +160 -63
  160. data/lib/active_record/relation/delegation.rb +22 -8
  161. data/lib/active_record/relation/finder_methods.rb +77 -16
  162. data/lib/active_record/relation/merger.rb +2 -0
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  166. data/lib/active_record/relation/predicate_builder.rb +27 -16
  167. data/lib/active_record/relation/query_attribute.rb +25 -1
  168. data/lib/active_record/relation/query_methods.rb +378 -70
  169. data/lib/active_record/relation/spawn_methods.rb +18 -1
  170. data/lib/active_record/relation.rb +76 -35
  171. data/lib/active_record/result.rb +19 -5
  172. data/lib/active_record/runtime_registry.rb +10 -1
  173. data/lib/active_record/sanitization.rb +51 -11
  174. data/lib/active_record/schema.rb +2 -3
  175. data/lib/active_record/schema_dumper.rb +46 -7
  176. data/lib/active_record/schema_migration.rb +68 -33
  177. data/lib/active_record/scoping/default.rb +15 -5
  178. data/lib/active_record/scoping/named.rb +2 -2
  179. data/lib/active_record/scoping.rb +2 -1
  180. data/lib/active_record/secure_password.rb +60 -0
  181. data/lib/active_record/secure_token.rb +21 -3
  182. data/lib/active_record/signed_id.rb +7 -5
  183. data/lib/active_record/store.rb +8 -8
  184. data/lib/active_record/suppressor.rb +3 -1
  185. data/lib/active_record/table_metadata.rb +11 -2
  186. data/lib/active_record/tasks/database_tasks.rb +127 -105
  187. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  188. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  189. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  190. data/lib/active_record/test_fixtures.rb +113 -96
  191. data/lib/active_record/timestamp.rb +27 -15
  192. data/lib/active_record/token_for.rb +113 -0
  193. data/lib/active_record/touch_later.rb +11 -6
  194. data/lib/active_record/transactions.rb +39 -13
  195. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  196. data/lib/active_record/type/internal/timezone.rb +7 -2
  197. data/lib/active_record/type/serialized.rb +4 -0
  198. data/lib/active_record/type/time.rb +4 -0
  199. data/lib/active_record/validations/absence.rb +1 -1
  200. data/lib/active_record/validations/numericality.rb +5 -4
  201. data/lib/active_record/validations/presence.rb +5 -28
  202. data/lib/active_record/validations/uniqueness.rb +47 -2
  203. data/lib/active_record/validations.rb +8 -4
  204. data/lib/active_record/version.rb +1 -1
  205. data/lib/active_record.rb +121 -16
  206. data/lib/arel/errors.rb +10 -0
  207. data/lib/arel/factory_methods.rb +4 -0
  208. data/lib/arel/nodes/and.rb +4 -0
  209. data/lib/arel/nodes/binary.rb +6 -1
  210. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  211. data/lib/arel/nodes/cte.rb +36 -0
  212. data/lib/arel/nodes/fragments.rb +35 -0
  213. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  214. data/lib/arel/nodes/leading_join.rb +8 -0
  215. data/lib/arel/nodes/node.rb +111 -2
  216. data/lib/arel/nodes/sql_literal.rb +6 -0
  217. data/lib/arel/nodes/table_alias.rb +4 -0
  218. data/lib/arel/nodes.rb +4 -0
  219. data/lib/arel/predications.rb +2 -0
  220. data/lib/arel/table.rb +9 -5
  221. data/lib/arel/visitors/mysql.rb +8 -1
  222. data/lib/arel/visitors/to_sql.rb +81 -17
  223. data/lib/arel/visitors/visitor.rb +2 -2
  224. data/lib/arel.rb +16 -2
  225. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  226. data/lib/rails/generators/active_record/migration.rb +3 -1
  227. data/lib/rails/generators/active_record/model/USAGE +113 -0
  228. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  229. metadata +50 -15
  230. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  231. 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.
@@ -77,7 +76,7 @@ module ActiveRecord
77
76
  associations.each do |association|
78
77
  reflection = scope_association_reflection(association)
79
78
  @scope.joins!(association)
80
- if @scope.table_name == reflection.table_name
79
+ if reflection.options[:class_name]
81
80
  self.not(association => { reflection.association_primary_key => nil })
82
81
  else
83
82
  self.not(reflection.table_name => { reflection.association_primary_key => nil })
@@ -109,7 +108,7 @@ module ActiveRecord
109
108
  associations.each do |association|
110
109
  reflection = scope_association_reflection(association)
111
110
  @scope.left_outer_joins!(association)
112
- if @scope.table_name == reflection.table_name
111
+ if reflection.options[:class_name]
113
112
  @scope.where!(association => { reflection.association_primary_key => nil })
114
113
  else
115
114
  @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
@@ -129,6 +128,15 @@ module ActiveRecord
129
128
  end
130
129
  end
131
130
 
131
+ # A wrapper to distinguish CTE joins from other nodes.
132
+ class CTEJoin # :nodoc:
133
+ attr_reader :name
134
+
135
+ def initialize(name)
136
+ @name = name
137
+ end
138
+ end
139
+
132
140
  FROZEN_EMPTY_ARRAY = [].freeze
133
141
  FROZEN_EMPTY_HASH = {}.freeze
134
142
 
@@ -157,45 +165,69 @@ module ActiveRecord
157
165
 
158
166
  alias extensions extending_values
159
167
 
160
- # Specify 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)
181
+ #
182
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
183
+ # are loaded with a single query.
184
+ #
185
+ # Loading the associations in a separate query will often result in a
186
+ # performance improvement over a simple join, as a join can result in many
187
+ # rows that contain redundant data and it performs poorly at scale.
171
188
  #
172
- # You can also specify multiple relationships, like this:
189
+ # You can also specify multiple associations. Each association will result
190
+ # in an additional query:
173
191
  #
174
- # users = User.includes(:address, :friends)
192
+ # User.includes(:address, :friends).to_a
193
+ # # SELECT "users".* FROM "users"
194
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
195
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
175
196
  #
176
- # Loading nested relationships is possible using a Hash:
197
+ # Loading nested associations is possible using a Hash:
177
198
  #
178
- # users = User.includes(:address, friends: [:address, :followers])
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
248
+ #
249
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
250
+ # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
251
+ # # LIMIT 5
210
252
  #
211
- # 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"
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
284
+ #
285
+ # # SELECT "users".* FROM "users" LIMIT 5
286
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
226
287
  #
227
- # User.preload(:posts)
228
- # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
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.
@@ -426,7 +588,7 @@ module ActiveRecord
426
588
  # User.order(Arel.sql('end_date - start_date'))
427
589
  # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
428
590
  #
429
- # Custom query syntax, like JSON columns for Postgres, is supported in this way.
591
+ # Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
430
592
  #
431
593
  # User.order(Arel.sql("payload->>'kind'"))
432
594
  # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
@@ -444,13 +606,16 @@ module ActiveRecord
444
606
  self
445
607
  end
446
608
 
447
- # Allows to specify an order by a specific set of values. Depending on your
448
- # adapter this will either use a CASE statement or a built-in function.
609
+ # Allows to specify an order by a specific set of values.
449
610
  #
450
611
  # User.in_order_of(:id, [1, 5, 3])
451
612
  # # SELECT "users".* FROM "users"
452
- # # ORDER BY FIELD("users"."id", 1, 5, 3)
453
613
  # # WHERE "users"."id" IN (1, 5, 3)
614
+ # # ORDER BY CASE
615
+ # # WHEN "users"."id" = 1 THEN 1
616
+ # # WHEN "users"."id" = 5 THEN 2
617
+ # # WHEN "users"."id" = 3 THEN 3
618
+ # # END ASC
454
619
  #
455
620
  def in_order_of(column, values)
456
621
  klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
@@ -460,11 +625,18 @@ module ActiveRecord
460
625
  self.references_values |= references unless references.empty?
461
626
 
462
627
  values = values.map { |value| type_caster.type_cast_for_database(column, value) }
463
- arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
628
+ arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
629
+
630
+ where_clause =
631
+ if values.include?(nil)
632
+ arel_column.in(values.compact).or(arel_column.eq(nil))
633
+ else
634
+ arel_column.in(values)
635
+ end
464
636
 
465
637
  spawn
466
- .order!(connection.field_ordered_value(arel_column, values))
467
- .where!(arel_column.in(values))
638
+ .order!(build_case_for_value_position(arel_column, values))
639
+ .where!(where_clause)
468
640
  end
469
641
 
470
642
  # Replaces any existing order defined on the relation with the specified order.
@@ -475,7 +647,7 @@ module ActiveRecord
475
647
  #
476
648
  # User.order('email DESC').reorder('id ASC').order('name ASC')
477
649
  #
478
- # generates a query with 'ORDER BY id ASC, name ASC'.
650
+ # generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
479
651
  def reorder(*args)
480
652
  check_if_method_has_arguments!(__callee__, args) do
481
653
  sanitize_order_arguments(args)
@@ -494,7 +666,8 @@ module ActiveRecord
494
666
 
495
667
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
496
668
  :limit, :offset, :joins, :left_outer_joins, :annotate,
497
- :includes, :from, :readonly, :having, :optimizer_hints])
669
+ :includes, :eager_load, :preload, :from, :readonly,
670
+ :having, :optimizer_hints])
498
671
 
499
672
  # Removes an unwanted relation that is already defined on a chain of relations.
500
673
  # This is useful when passing around chains of relations and would like to
@@ -604,7 +777,7 @@ module ActiveRecord
604
777
  # Performs LEFT OUTER JOINs on +args+:
605
778
  #
606
779
  # User.left_outer_joins(:posts)
607
- # => 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"
608
781
  #
609
782
  def left_outer_joins(*args)
610
783
  check_if_method_has_arguments!(__callee__, args)
@@ -624,7 +797,7 @@ module ActiveRecord
624
797
  # SQL is given as an illustration; the actual query generated may be different depending
625
798
  # on the database adapter.
626
799
  #
627
- # === string
800
+ # === \String
628
801
  #
629
802
  # A single string, without additional arguments, is passed to the query
630
803
  # constructor as an SQL fragment, and used in the where clause of the query.
@@ -636,7 +809,7 @@ module ActiveRecord
636
809
  # to injection attacks if not done properly. As an alternative, it is recommended
637
810
  # to use one of the following methods.
638
811
  #
639
- # === array
812
+ # === \Array
640
813
  #
641
814
  # If an array is passed, then the first element of the array is treated as a template, and
642
815
  # the remaining elements are inserted into the template to generate the condition.
@@ -676,7 +849,7 @@ module ActiveRecord
676
849
  # dependencies on the underlying database. If your code is intended for general consumption,
677
850
  # test with multiple database backends.
678
851
  #
679
- # === hash
852
+ # === \Hash
680
853
  #
681
854
  # #where will also accept a hash condition, in which the keys are fields and the values
682
855
  # are values to be searched for.
@@ -710,6 +883,12 @@ module ActiveRecord
710
883
  # PriceEstimate.where(estimate_of: treasure)
711
884
  # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
712
885
  #
886
+ # Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
887
+ # an array of columns with an array of tuples as values.
888
+ #
889
+ # Article.where([:author_id, :id] => [[15, 1], [15, 2]])
890
+ # # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
891
+ #
713
892
  # === Joins
714
893
  #
715
894
  # If the relation is the result of a join, you may create a condition which uses any of the
@@ -722,7 +901,7 @@ module ActiveRecord
722
901
  # User.joins(:posts).where("posts.published" => true)
723
902
  # User.joins(:posts).where(posts: { published: true })
724
903
  #
725
- # === no argument
904
+ # === No Argument
726
905
  #
727
906
  # If no argument is passed, #where returns a new instance of WhereChain, that
728
907
  # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
@@ -746,7 +925,7 @@ module ActiveRecord
746
925
  # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
747
926
  # # WHERE "authors"."id" IS NULL
748
927
  #
749
- # === blank condition
928
+ # === Blank Condition
750
929
  #
751
930
  # If the condition is any blank-ish object, then #where is a no-op and returns
752
931
  # the current relation.
@@ -779,6 +958,8 @@ module ActiveRecord
779
958
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
780
959
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
781
960
  def rewhere(conditions)
961
+ return unscope(:where) if conditions.nil?
962
+
782
963
  scope = spawn
783
964
  where_clause = scope.build_where_clause(conditions)
784
965
 
@@ -884,7 +1065,11 @@ module ActiveRecord
884
1065
  #
885
1066
  def or(other)
886
1067
  if other.is_a?(Relation)
887
- spawn.or!(other)
1068
+ if @none
1069
+ other.spawn
1070
+ else
1071
+ spawn.or!(other)
1072
+ end
888
1073
  else
889
1074
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
890
1075
  end
@@ -997,15 +1182,29 @@ module ActiveRecord
997
1182
  end
998
1183
 
999
1184
  def none! # :nodoc:
1000
- 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
1001
1194
  end
1002
1195
 
1003
- # Sets readonly attributes for the returned relation. If value is
1004
- # 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.
1005
1198
  #
1006
1199
  # users = User.readonly
1007
1200
  # users.first.save
1008
1201
  # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1202
+ #
1203
+ # To make a readonly relation writable, pass +false+.
1204
+ #
1205
+ # users.readonly(false)
1206
+ # users.first.save
1207
+ # => true
1009
1208
  def readonly(value = true)
1010
1209
  spawn.readonly!(value)
1011
1210
  end
@@ -1122,7 +1321,7 @@ module ActiveRecord
1122
1321
  #
1123
1322
  # The object returned is a relation, which can be further extended.
1124
1323
  #
1125
- # === Using a module
1324
+ # === Using a \Module
1126
1325
  #
1127
1326
  # module Pagination
1128
1327
  # def page(number)
@@ -1137,7 +1336,7 @@ module ActiveRecord
1137
1336
  #
1138
1337
  # scope = Model.all.extending(Pagination, SomethingElse)
1139
1338
  #
1140
- # === Using a block
1339
+ # === Using a Block
1141
1340
  #
1142
1341
  # scope = Model.all.extending do
1143
1342
  # def page(number)
@@ -1314,8 +1513,12 @@ module ActiveRecord
1314
1513
  parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1315
1514
  when Hash
1316
1515
  opts = opts.transform_keys do |key|
1317
- key = key.to_s
1318
- 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
1319
1522
  end
1320
1523
  references = PredicateBuilder.references(opts)
1321
1524
  self.references_values |= references unless references.empty?
@@ -1333,7 +1536,16 @@ module ActiveRecord
1333
1536
  end
1334
1537
  alias :build_having_clause :build_where_clause
1335
1538
 
1539
+ def async!
1540
+ @async = true
1541
+ self
1542
+ end
1543
+
1336
1544
  private
1545
+ def async
1546
+ spawn.async!
1547
+ end
1548
+
1337
1549
  def lookup_table_klass_from_join_dependencies(table_name)
1338
1550
  each_join_dependencies do |join|
1339
1551
  return join.base_klass if table_name == join.table_name
@@ -1348,13 +1560,13 @@ module ActiveRecord
1348
1560
  end
1349
1561
 
1350
1562
  def build_join_dependencies
1351
- associations = joins_values | left_outer_joins_values
1352
- associations |= eager_load_values unless eager_load_values.empty?
1353
- 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?
1354
1566
 
1355
1567
  join_dependencies = []
1356
1568
  join_dependencies.unshift construct_join_dependency(
1357
- select_association_list(associations, join_dependencies), nil
1569
+ select_named_joins(joins, join_dependencies), nil
1358
1570
  )
1359
1571
  end
1360
1572
 
@@ -1375,6 +1587,7 @@ module ActiveRecord
1375
1587
  arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1376
1588
 
1377
1589
  build_order(arel)
1590
+ build_with(arel)
1378
1591
  build_select(arel)
1379
1592
 
1380
1593
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
@@ -1410,6 +1623,18 @@ module ActiveRecord
1410
1623
  end
1411
1624
  end
1412
1625
 
1626
+ def select_named_joins(join_names, stashed_joins = nil, &block)
1627
+ cte_joins, associations = join_names.partition do |join_name|
1628
+ Symbol === join_name && with_values.any? { _1.key?(join_name) }
1629
+ end
1630
+
1631
+ cte_joins.each do |cte_name|
1632
+ block&.call(CTEJoin.new(cte_name))
1633
+ end
1634
+
1635
+ select_association_list(associations, stashed_joins, &block)
1636
+ end
1637
+
1413
1638
  def select_association_list(associations, stashed_joins = nil)
1414
1639
  result = []
1415
1640
  associations.each do |association|
@@ -1425,20 +1650,21 @@ module ActiveRecord
1425
1650
  result
1426
1651
  end
1427
1652
 
1428
- class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1429
- end
1430
-
1431
1653
  def build_join_buckets
1432
1654
  buckets = Hash.new { |h, k| h[k] = [] }
1433
1655
 
1434
1656
  unless left_outer_joins_values.empty?
1435
1657
  stashed_left_joins = []
1436
- left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1437
- 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
1438
1664
  end
1439
1665
 
1440
1666
  if joins_values.empty?
1441
- buckets[:association_join] = left_joins
1667
+ buckets[:named_join] = left_joins
1442
1668
  buckets[:stashed_join] = stashed_left_joins
1443
1669
  return buckets, Arel::Nodes::OuterJoin
1444
1670
  else
@@ -1464,9 +1690,11 @@ module ActiveRecord
1464
1690
  end
1465
1691
  end
1466
1692
 
1467
- buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1693
+ buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
1468
1694
  if join.is_a?(Arel::Nodes::Join)
1469
1695
  buckets[:join_node] << join
1696
+ elsif join.is_a?(CTEJoin)
1697
+ buckets[:join_node] << build_with_join_node(join.name)
1470
1698
  else
1471
1699
  raise "unknown class: %s" % join.class.name
1472
1700
  end
@@ -1483,16 +1711,16 @@ module ActiveRecord
1483
1711
 
1484
1712
  buckets, join_type = build_join_buckets
1485
1713
 
1486
- association_joins = buckets[:association_join]
1487
- stashed_joins = buckets[:stashed_join]
1488
- leading_joins = buckets[:leading_join]
1489
- 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]
1490
1718
 
1491
1719
  join_sources.concat(leading_joins) unless leading_joins.empty?
1492
1720
 
1493
- unless association_joins.empty? && stashed_joins.empty?
1721
+ unless named_joins.empty? && stashed_joins.empty?
1494
1722
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1495
- join_dependency = construct_join_dependency(association_joins, join_type)
1723
+ join_dependency = construct_join_dependency(named_joins, join_type)
1496
1724
  join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1497
1725
  end
1498
1726
 
@@ -1510,6 +1738,40 @@ module ActiveRecord
1510
1738
  end
1511
1739
  end
1512
1740
 
1741
+ def build_with(arel)
1742
+ return if with_values.empty?
1743
+
1744
+ with_statements = with_values.map do |with_value|
1745
+ raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
1746
+
1747
+ build_with_value_from_hash(with_value)
1748
+ end
1749
+
1750
+ arel.with(with_statements)
1751
+ end
1752
+
1753
+ def build_with_value_from_hash(hash)
1754
+ hash.map do |name, value|
1755
+ expression =
1756
+ case value
1757
+ when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1758
+ when ActiveRecord::Relation then value.arel
1759
+ when Arel::SelectManager then value
1760
+ else
1761
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1762
+ end
1763
+ Arel::Nodes::TableAlias.new(expression, name)
1764
+ end
1765
+ end
1766
+
1767
+ def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
1768
+ with_table = Arel::Table.new(name)
1769
+
1770
+ table.join(with_table, kind).on(
1771
+ with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1772
+ ).join_sources.first
1773
+ end
1774
+
1513
1775
  def arel_columns(columns)
1514
1776
  columns.flat_map do |field|
1515
1777
  case field
@@ -1628,7 +1890,7 @@ module ActiveRecord
1628
1890
  when Hash
1629
1891
  arg.map { |field, dir|
1630
1892
  case field
1631
- when Arel::Nodes::SqlLiteral
1893
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
1632
1894
  field.public_send(dir.downcase)
1633
1895
  else
1634
1896
  order_column(field.to_s).public_send(dir.downcase)
@@ -1652,7 +1914,9 @@ module ActiveRecord
1652
1914
  when String, Symbol
1653
1915
  arg
1654
1916
  when Hash
1655
- arg.keys
1917
+ arg.keys.map do |key|
1918
+ key if key.is_a?(String) || key.is_a?(Symbol)
1919
+ end
1656
1920
  end
1657
1921
  end
1658
1922
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
@@ -1669,6 +1933,15 @@ module ActiveRecord
1669
1933
  end
1670
1934
  end
1671
1935
 
1936
+ def build_case_for_value_position(column, values)
1937
+ node = Arel::Nodes::Case.new
1938
+ values.each.with_index(1) do |value, order|
1939
+ node.when(column.eq(value)).then(order)
1940
+ end
1941
+
1942
+ Arel::Nodes::Ascending.new(node)
1943
+ end
1944
+
1672
1945
  def resolve_arel_attributes(attrs)
1673
1946
  attrs.flat_map do |attr|
1674
1947
  case attr
@@ -1720,6 +1993,41 @@ module ActiveRecord
1720
1993
  end
1721
1994
  end
1722
1995
 
1996
+ def process_select_args(fields)
1997
+ fields.flat_map do |field|
1998
+ if field.is_a?(Hash)
1999
+ transform_select_hash_values(field)
2000
+ else
2001
+ field
2002
+ end
2003
+ end
2004
+ end
2005
+
2006
+ def transform_select_hash_values(fields)
2007
+ fields.flat_map do |key, columns_aliases|
2008
+ case columns_aliases
2009
+ when Hash
2010
+ columns_aliases.map do |column, column_alias|
2011
+ if values[:joins]&.include?(key)
2012
+ references = PredicateBuilder.references({ key.to_s => fields[key] })
2013
+ self.references_values |= references unless references.empty?
2014
+ end
2015
+ arel_column("#{key}.#{column}") do
2016
+ predicate_builder.resolve_arel_attribute(key.to_s, column)
2017
+ end.as(column_alias.to_s)
2018
+ end
2019
+ when Array
2020
+ columns_aliases.map do |column|
2021
+ arel_column("#{key}.#{column}", &:itself)
2022
+ end
2023
+ when String, Symbol
2024
+ arel_column(key.to_s) do
2025
+ predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
2026
+ end.as(columns_aliases.to_s)
2027
+ end
2028
+ end
2029
+ end
2030
+
1723
2031
  STRUCTURAL_VALUE_METHODS = (
1724
2032
  Relation::VALUE_METHODS -
1725
2033
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]