activerecord 7.1.5.2 → 8.1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -72,14 +72,31 @@ module ActiveRecord
72
72
  # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
73
73
  # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
74
74
  # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
75
+ #
76
+ # You can define join type in the scope and +associated+ will not use `JOIN` by default.
77
+ #
78
+ # Post.left_joins(:author).where.associated(:author)
79
+ # # SELECT "posts".* FROM "posts"
80
+ # # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
81
+ # # WHERE "authors"."id" IS NOT NULL
82
+ #
83
+ # Post.left_joins(:comments).where.associated(:author)
84
+ # # SELECT "posts".* FROM "posts"
85
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
86
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
87
+ # # WHERE "author"."id" IS NOT NULL
75
88
  def associated(*associations)
76
89
  associations.each do |association|
77
90
  reflection = scope_association_reflection(association)
78
- @scope.joins!(association)
91
+ unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
92
+ @scope.joins!(association)
93
+ end
94
+
95
+ association_conditions = Array(reflection.association_primary_key).index_with(nil)
79
96
  if reflection.options[:class_name]
80
- self.not(association => { reflection.association_primary_key => nil })
97
+ self.not(association => association_conditions)
81
98
  else
82
- self.not(reflection.table_name => { reflection.association_primary_key => nil })
99
+ self.not(reflection.table_name => association_conditions)
83
100
  end
84
101
  end
85
102
 
@@ -108,10 +125,11 @@ module ActiveRecord
108
125
  associations.each do |association|
109
126
  reflection = scope_association_reflection(association)
110
127
  @scope.left_outer_joins!(association)
128
+ association_conditions = Array(reflection.association_primary_key).index_with(nil)
111
129
  if reflection.options[:class_name]
112
- @scope.where!(association => { reflection.association_primary_key => nil })
130
+ @scope.where!(association => association_conditions)
113
131
  else
114
- @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
132
+ @scope.where!(reflection.table_name => association_conditions)
115
133
  end
116
134
  end
117
135
 
@@ -120,9 +138,10 @@ module ActiveRecord
120
138
 
121
139
  private
122
140
  def scope_association_reflection(association)
123
- reflection = @scope.klass._reflect_on_association(association)
141
+ model = @scope.model
142
+ reflection = model._reflect_on_association(association)
124
143
  unless reflection
125
- raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
144
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{model.name}`.")
126
145
  end
127
146
  reflection
128
147
  end
@@ -157,7 +176,7 @@ module ActiveRecord
157
176
  end # end
158
177
 
159
178
  def #{method_name}=(value) # def includes_values=(value)
160
- assert_mutability! # assert_mutability!
179
+ assert_modifiable! # assert_modifiable!
161
180
  @values[:#{name}] = value # @values[:includes] = value
162
181
  end # end
163
182
  CODE
@@ -238,6 +257,10 @@ module ActiveRecord
238
257
  self
239
258
  end
240
259
 
260
+ def all # :nodoc:
261
+ spawn
262
+ end
263
+
241
264
  # Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
242
265
  # Performs a single query joining all specified associations. For example:
243
266
  #
@@ -419,6 +442,17 @@ module ActiveRecord
419
442
  # # )
420
443
  # # SELECT * FROM posts
421
444
  #
445
+ # You can also pass an array of sub-queries to be joined in a +UNION ALL+.
446
+ #
447
+ # Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
448
+ # # => ActiveRecord::Relation
449
+ # # WITH posts_with_tags_or_comments AS (
450
+ # # (SELECT * FROM posts WHERE (tags_count > 0))
451
+ # # UNION ALL
452
+ # # (SELECT * FROM posts WHERE (comments_count > 0))
453
+ # # )
454
+ # # SELECT * FROM posts
455
+ #
422
456
  # Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
423
457
  #
424
458
  # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
@@ -457,13 +491,40 @@ module ActiveRecord
457
491
  # .with(posts_with_comments: Post.where("comments_count > ?", 0))
458
492
  # .with(posts_with_tags: Post.where("tags_count > ?", 0))
459
493
  def with(*args)
494
+ raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
460
495
  check_if_method_has_arguments!(__callee__, args)
461
496
  spawn.with!(*args)
462
497
  end
463
498
 
464
499
  # Like #with, but modifies relation in place.
465
500
  def with!(*args) # :nodoc:
466
- self.with_values += args
501
+ args = process_with_args(args)
502
+ self.with_values |= args
503
+ self
504
+ end
505
+
506
+ # Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
507
+ #
508
+ # Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
509
+ # # => ActiveRecord::Relation
510
+ # # WITH RECURSIVE post_and_replies AS (
511
+ # # (SELECT * FROM posts WHERE id = 42)
512
+ # # UNION ALL
513
+ # # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
514
+ # # )
515
+ # # SELECT * FROM posts
516
+ #
517
+ # See `#with` for more information.
518
+ def with_recursive(*args)
519
+ check_if_method_has_arguments!(__callee__, args)
520
+ spawn.with_recursive!(*args)
521
+ end
522
+
523
+ # Like #with_recursive but modifies the relation in place.
524
+ def with_recursive!(*args) # :nodoc:
525
+ args = process_with_args(args)
526
+ self.with_values |= args
527
+ @with_is_recursive = true
467
528
  self
468
529
  end
469
530
 
@@ -515,7 +576,7 @@ module ActiveRecord
515
576
  end
516
577
 
517
578
  def group!(*args) # :nodoc:
518
- self.group_values += args
579
+ self.group_values |= args
519
580
  self
520
581
  end
521
582
 
@@ -606,7 +667,8 @@ module ActiveRecord
606
667
  self
607
668
  end
608
669
 
609
- # Allows to specify an order by a specific set of values.
670
+ # Applies an <tt>ORDER BY</tt> clause based on a given +column+,
671
+ # ordered and filtered by a specific set of +values+.
610
672
  #
611
673
  # User.in_order_of(:id, [1, 5, 3])
612
674
  # # SELECT "users".* FROM "users"
@@ -617,26 +679,65 @@ module ActiveRecord
617
679
  # # WHEN "users"."id" = 3 THEN 3
618
680
  # # END ASC
619
681
  #
620
- def in_order_of(column, values)
621
- klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
682
+ # +column+ can point to an enum column; the actual query generated may be different depending
683
+ # on the database adapter and the column definition.
684
+ #
685
+ # class Conversation < ActiveRecord::Base
686
+ # enum :status, [ :active, :archived ]
687
+ # end
688
+ #
689
+ # Conversation.in_order_of(:status, [:archived, :active])
690
+ # # SELECT "conversations".* FROM "conversations"
691
+ # # WHERE "conversations"."status" IN (1, 0)
692
+ # # ORDER BY CASE
693
+ # # WHEN "conversations"."status" = 1 THEN 1
694
+ # # WHEN "conversations"."status" = 0 THEN 2
695
+ # # END ASC
696
+ #
697
+ # +values+ can also include +nil+.
698
+ #
699
+ # Conversation.in_order_of(:status, [nil, :archived, :active])
700
+ # # SELECT "conversations".* FROM "conversations"
701
+ # # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
702
+ # # ORDER BY CASE
703
+ # # WHEN "conversations"."status" IS NULL THEN 1
704
+ # # WHEN "conversations"."status" = 1 THEN 2
705
+ # # WHEN "conversations"."status" = 0 THEN 3
706
+ # # END ASC
707
+ #
708
+ # +filter+ can be set to +false+ to include all results instead of only the ones specified in +values+.
709
+ #
710
+ # Conversation.in_order_of(:status, [:archived, :active], filter: false)
711
+ # # SELECT "conversations".* FROM "conversations"
712
+ # # ORDER BY CASE
713
+ # # WHEN "conversations"."status" = 1 THEN 1
714
+ # # WHEN "conversations"."status" = 0 THEN 2
715
+ # # ELSE 3
716
+ # # END ASC
717
+ def in_order_of(column, values, filter: true)
718
+ model.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
622
719
  return spawn.none! if values.empty?
623
720
 
624
721
  references = column_references([column])
625
722
  self.references_values |= references unless references.empty?
626
723
 
627
- values = values.map { |value| type_caster.type_cast_for_database(column, value) }
724
+ values = values.map { |value| model.type_caster.type_cast_for_database(column, value) }
628
725
  arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
629
726
 
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
727
+ scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))
636
728
 
637
- spawn
638
- .order!(build_case_for_value_position(arel_column, values))
639
- .where!(where_clause)
729
+ if filter
730
+ where_clause =
731
+ if values.include?(nil)
732
+ arel_column.in(values.compact).or(arel_column.eq(nil))
733
+ else
734
+ arel_column.in(values)
735
+ end
736
+
737
+ scope = scope.where!(where_clause)
738
+ end
739
+
740
+ scope
640
741
  end
641
742
 
642
743
  # Replaces any existing order defined on the relation with the specified order.
@@ -667,7 +768,7 @@ module ActiveRecord
667
768
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
668
769
  :limit, :offset, :joins, :left_outer_joins, :annotate,
669
770
  :includes, :eager_load, :preload, :from, :readonly,
670
- :having, :optimizer_hints])
771
+ :having, :optimizer_hints, :with])
671
772
 
672
773
  # Removes an unwanted relation that is already defined on a chain of relations.
673
774
  # This is useful when passing around chains of relations and would like to
@@ -708,7 +809,7 @@ module ActiveRecord
708
809
  end
709
810
 
710
811
  def unscope!(*args) # :nodoc:
711
- self.unscope_values += args
812
+ self.unscope_values |= args
712
813
 
713
814
  args.each do |scope|
714
815
  case scope
@@ -717,7 +818,7 @@ module ActiveRecord
717
818
  if !VALID_UNSCOPING_VALUES.include?(scope)
718
819
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
719
820
  end
720
- assert_mutability!
821
+ assert_modifiable!
721
822
  @values.delete(scope)
722
823
  when Hash
723
824
  scope.each do |key, target_value|
@@ -1082,7 +1183,7 @@ module ActiveRecord
1082
1183
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
1083
1184
  end
1084
1185
 
1085
- self.where_clause = self.where_clause.or(other.where_clause)
1186
+ self.where_clause = where_clause.or(other.where_clause)
1086
1187
  self.having_clause = having_clause.or(other.having_clause)
1087
1188
  self.references_values |= other.references_values
1088
1189
 
@@ -1112,6 +1213,7 @@ module ActiveRecord
1112
1213
  end
1113
1214
 
1114
1215
  def limit!(value) # :nodoc:
1216
+ value = Integer(value) unless value.nil?
1115
1217
  self.limit_value = value
1116
1218
  self
1117
1219
  end
@@ -1198,13 +1300,13 @@ module ActiveRecord
1198
1300
  #
1199
1301
  # users = User.readonly
1200
1302
  # users.first.save
1201
- # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1303
+ # # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1202
1304
  #
1203
1305
  # To make a readonly relation writable, pass +false+.
1204
1306
  #
1205
1307
  # users.readonly(false)
1206
1308
  # users.first.save
1207
- # => true
1309
+ # # => true
1208
1310
  def readonly(value = true)
1209
1311
  spawn.readonly!(value)
1210
1312
  end
@@ -1219,7 +1321,7 @@ module ActiveRecord
1219
1321
  #
1220
1322
  # user = User.strict_loading.first
1221
1323
  # user.comments.to_a
1222
- # => ActiveRecord::StrictLoadingViolationError
1324
+ # # => ActiveRecord::StrictLoadingViolationError
1223
1325
  def strict_loading(value = true)
1224
1326
  spawn.strict_loading!(value)
1225
1327
  end
@@ -1364,7 +1466,7 @@ module ActiveRecord
1364
1466
  modules << Module.new(&block) if block
1365
1467
  modules.flatten!
1366
1468
 
1367
- self.extending_values += modules
1469
+ self.extending_values |= modules
1368
1470
  extend(*extending_values) if extending_values.any?
1369
1471
 
1370
1472
  self
@@ -1432,7 +1534,7 @@ module ActiveRecord
1432
1534
 
1433
1535
  # Like #annotate, but modifies relation in place.
1434
1536
  def annotate!(*args) # :nodoc:
1435
- self.annotate_values += args
1537
+ self.annotate_values |= args
1436
1538
  self
1437
1539
  end
1438
1540
 
@@ -1453,6 +1555,9 @@ module ActiveRecord
1453
1555
  # Post.excluding(post_one, post_two)
1454
1556
  # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1455
1557
  #
1558
+ # Post.excluding(Post.drafts)
1559
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
1560
+ #
1456
1561
  # This can also be called on associations. As with the above example, either
1457
1562
  # a single record of collection thereof may be specified:
1458
1563
  #
@@ -1468,14 +1573,15 @@ module ActiveRecord
1468
1573
  # is passed in) are not instances of the same model that the relation is
1469
1574
  # scoping.
1470
1575
  def excluding(*records)
1576
+ relations = records.extract! { |element| element.is_a?(Relation) }
1471
1577
  records.flatten!(1)
1472
1578
  records.compact!
1473
1579
 
1474
- unless records.all?(klass)
1475
- raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1580
+ unless records.all?(model) && relations.all? { |relation| relation.model == model }
1581
+ raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}."
1476
1582
  end
1477
1583
 
1478
- spawn.excluding!(records)
1584
+ spawn.excluding!(records + relations.flat_map(&:ids))
1479
1585
  end
1480
1586
  alias :without :excluding
1481
1587
 
@@ -1492,7 +1598,7 @@ module ActiveRecord
1492
1598
 
1493
1599
  def construct_join_dependency(associations, join_type) # :nodoc:
1494
1600
  ActiveRecord::Associations::JoinDependency.new(
1495
- klass, table, associations, join_type
1601
+ model, table, associations, join_type
1496
1602
  )
1497
1603
  end
1498
1604
 
@@ -1508,16 +1614,28 @@ module ActiveRecord
1508
1614
  def build_where_clause(opts, rest = []) # :nodoc:
1509
1615
  opts = sanitize_forbidden_attributes(opts)
1510
1616
 
1617
+ if opts.is_a?(Array)
1618
+ opts, *rest = opts
1619
+ end
1620
+
1511
1621
  case opts
1512
- when String, Array
1513
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1622
+ when String
1623
+ if rest.empty?
1624
+ parts = [Arel.sql(opts)]
1625
+ elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
1626
+ parts = [build_named_bound_sql_literal(opts, rest.first)]
1627
+ elsif opts.include?("?")
1628
+ parts = [build_bound_sql_literal(opts, rest)]
1629
+ else
1630
+ parts = [Arel.sql(model.sanitize_sql([opts, *rest]))]
1631
+ end
1514
1632
  when Hash
1515
1633
  opts = opts.transform_keys do |key|
1516
1634
  if key.is_a?(Array)
1517
- key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
1635
+ key.map { |k| model.attribute_aliases[k.to_s] || k.to_s }
1518
1636
  else
1519
1637
  key = key.to_s
1520
- klass.attribute_aliases[key] || key
1638
+ model.attribute_aliases[key] || key
1521
1639
  end
1522
1640
  end
1523
1641
  references = PredicateBuilder.references(opts)
@@ -1536,16 +1654,71 @@ module ActiveRecord
1536
1654
  end
1537
1655
  alias :build_having_clause :build_where_clause
1538
1656
 
1539
- def async!
1657
+ def async! # :nodoc:
1540
1658
  @async = true
1541
1659
  self
1542
1660
  end
1543
1661
 
1662
+ def arel_columns(columns) # :nodoc:
1663
+ columns.flat_map do |field|
1664
+ case field
1665
+ when Symbol, String
1666
+ arel_column(field)
1667
+ when Proc
1668
+ field.call
1669
+ when Hash
1670
+ arel_columns_from_hash(field)
1671
+ else
1672
+ field
1673
+ end
1674
+ end
1675
+ end
1676
+
1544
1677
  private
1545
1678
  def async
1546
1679
  spawn.async!
1547
1680
  end
1548
1681
 
1682
+ def build_named_bound_sql_literal(statement, values)
1683
+ bound_values = values.transform_values do |value|
1684
+ if ActiveRecord::Relation === value
1685
+ Arel.sql(value.to_sql)
1686
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1687
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1688
+ values.empty? ? nil : values
1689
+ else
1690
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1691
+ value
1692
+ end
1693
+ end
1694
+
1695
+ begin
1696
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
1697
+ rescue Arel::BindError => error
1698
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1699
+ end
1700
+ end
1701
+
1702
+ def build_bound_sql_literal(statement, values)
1703
+ bound_values = values.map do |value|
1704
+ if ActiveRecord::Relation === value
1705
+ Arel.sql(value.to_sql)
1706
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1707
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1708
+ values.empty? ? nil : values
1709
+ else
1710
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1711
+ value
1712
+ end
1713
+ end
1714
+
1715
+ begin
1716
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
1717
+ rescue Arel::BindError => error
1718
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1719
+ end
1720
+ end
1721
+
1549
1722
  def lookup_table_klass_from_join_dependencies(table_name)
1550
1723
  each_join_dependencies do |join|
1551
1724
  return join.base_klass if table_name == join.table_name
@@ -1570,37 +1743,31 @@ module ActiveRecord
1570
1743
  )
1571
1744
  end
1572
1745
 
1573
- def assert_mutability!
1574
- raise ImmutableRelation if @loaded
1575
- raise ImmutableRelation if defined?(@arel) && @arel
1746
+ def assert_modifiable!
1747
+ raise UnmodifiableRelation if @loaded || @arel
1576
1748
  end
1577
1749
 
1578
- def build_arel(aliases = nil)
1750
+ def build_arel(aliases)
1579
1751
  arel = Arel::SelectManager.new(table)
1580
1752
 
1581
1753
  build_joins(arel.join_sources, aliases)
1582
1754
 
1583
1755
  arel.where(where_clause.ast) unless where_clause.empty?
1584
1756
  arel.having(having_clause.ast) unless having_clause.empty?
1585
- arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1757
+ arel.take(build_cast_value("LIMIT", limit_value)) if limit_value
1586
1758
  arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1587
- arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1759
+ arel.group(*arel_columns(group_values)) unless group_values.empty?
1588
1760
 
1589
1761
  build_order(arel)
1590
1762
  build_with(arel)
1591
1763
  build_select(arel)
1592
1764
 
1593
1765
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1766
+ arel.comment(*annotate_values) unless annotate_values.empty?
1594
1767
  arel.distinct(distinct_value)
1595
1768
  arel.from(build_from) unless from_clause.empty?
1596
1769
  arel.lock(lock_value) if lock_value
1597
1770
 
1598
- unless annotate_values.empty?
1599
- annotates = annotate_values
1600
- annotates = annotates.uniq if annotates.size > 1
1601
- arel.comment(*annotates)
1602
- end
1603
-
1604
1771
  arel
1605
1772
  end
1606
1773
 
@@ -1674,7 +1841,7 @@ module ActiveRecord
1674
1841
 
1675
1842
  joins = joins_values.dup
1676
1843
  if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1677
- stashed_eager_load = joins.pop if joins.last.base_klass == klass
1844
+ stashed_eager_load = joins.pop if joins.last.base_klass == model
1678
1845
  end
1679
1846
 
1680
1847
  joins.each_with_index do |join, i|
@@ -1731,8 +1898,8 @@ module ActiveRecord
1731
1898
  def build_select(arel)
1732
1899
  if select_values.any?
1733
1900
  arel.project(*arel_columns(select_values))
1734
- elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1735
- arel.project(*klass.column_names.map { |field| table[field] })
1901
+ elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements
1902
+ arel.project(*model.column_names.map { |field| table[field] })
1736
1903
  else
1737
1904
  arel.project(table[Arel.star])
1738
1905
  end
@@ -1742,25 +1909,41 @@ module ActiveRecord
1742
1909
  return if with_values.empty?
1743
1910
 
1744
1911
  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
1912
  build_with_value_from_hash(with_value)
1748
1913
  end
1749
1914
 
1750
- arel.with(with_statements)
1915
+ @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
1751
1916
  end
1752
1917
 
1753
1918
  def build_with_value_from_hash(hash)
1754
1919
  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)
1920
+ Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
1921
+ end
1922
+ end
1923
+
1924
+ def build_with_expression_from_value(value, nested = false)
1925
+ case value
1926
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::BoundSqlLiteral
1927
+ Arel::Nodes::Grouping.new(value)
1928
+ when ActiveRecord::Relation
1929
+ if nested
1930
+ value.arel.ast
1931
+ else
1932
+ value.arel
1933
+ end
1934
+ when Arel::SelectManager then value
1935
+ when Array
1936
+ return build_with_expression_from_value(value.first, false) if value.size == 1
1937
+
1938
+ parts = value.map do |query|
1939
+ build_with_expression_from_value(query, true)
1940
+ end
1941
+
1942
+ parts.reduce do |result, value|
1943
+ Arel::Nodes::UnionAll.new(result, value)
1944
+ end
1945
+ else
1946
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1764
1947
  end
1765
1948
  end
1766
1949
 
@@ -1768,54 +1951,76 @@ module ActiveRecord
1768
1951
  with_table = Arel::Table.new(name)
1769
1952
 
1770
1953
  table.join(with_table, kind).on(
1771
- with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1954
+ with_table[model.model_name.to_s.foreign_key].eq(table[model.primary_key])
1772
1955
  ).join_sources.first
1773
1956
  end
1774
1957
 
1775
- def arel_columns(columns)
1776
- columns.flat_map do |field|
1777
- case field
1778
- when Symbol
1779
- arel_column(field.to_s) do |attr_name|
1780
- connection.quote_table_name(attr_name)
1958
+ def arel_columns_from_hash(fields)
1959
+ fields.flat_map do |table_name, columns|
1960
+ table_name = table_name.name if table_name.is_a?(Symbol)
1961
+ case columns
1962
+ when Symbol, String
1963
+ arel_column_with_table(table_name, columns)
1964
+ when Array
1965
+ columns.map do |column|
1966
+ arel_column_with_table(table_name, column)
1781
1967
  end
1782
- when String
1783
- arel_column(field, &:itself)
1784
- when Proc
1785
- field.call
1786
1968
  else
1787
- field
1969
+ raise TypeError, "Expected Symbol, String or Array, got: #{columns.class}"
1788
1970
  end
1789
1971
  end
1790
1972
  end
1791
1973
 
1974
+ def arel_column_with_table(table_name, column_name)
1975
+ self.references_values |= [Arel.sql(table_name, retryable: true)]
1976
+
1977
+ if column_name.is_a?(Symbol) || !column_name.match?(/\W/)
1978
+ predicate_builder.resolve_arel_attribute(table_name, column_name) do
1979
+ lookup_table_klass_from_join_dependencies(table_name)
1980
+ end
1981
+ else
1982
+ Arel.sql("#{model.adapter_class.quote_table_name(table_name)}.#{column_name}")
1983
+ end
1984
+ end
1985
+
1792
1986
  def arel_column(field)
1793
- field = klass.attribute_aliases[field] || field
1987
+ field = field.name if is_symbol = field.is_a?(Symbol)
1988
+
1989
+ field = model.attribute_aliases[field] || field
1794
1990
  from = from_clause.name || from_clause.value
1795
1991
 
1796
- if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1992
+ if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
1797
1993
  table[field]
1798
- elsif field.match?(/\A\w+\.\w+\z/)
1799
- table, column = field.split(".")
1800
- predicate_builder.resolve_arel_attribute(table, column) do
1801
- lookup_table_klass_from_join_dependencies(table)
1802
- end
1803
- else
1994
+ elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
1995
+ arel_column_with_table(table, column)
1996
+ elsif block_given?
1804
1997
  yield field
1998
+ elsif Arel.arel_node?(field)
1999
+ field
2000
+ elsif is_symbol
2001
+ Arel.sql(model.adapter_class.quote_table_name(field), retryable: true)
2002
+ else
2003
+ Arel.sql(field)
1805
2004
  end
1806
2005
  end
1807
2006
 
1808
2007
  def table_name_matches?(from)
1809
2008
  table_name = Regexp.escape(table.name)
1810
- quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
2009
+ quoted_table_name = Regexp.escape(model.adapter_class.quote_table_name(table.name))
1811
2010
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1812
2011
  end
1813
2012
 
1814
2013
  def reverse_sql_order(order_query)
1815
2014
  if order_query.empty?
1816
- return [table[primary_key].desc] if primary_key
1817
- raise IrreversibleOrderError,
1818
- "Relation has no current order and table has no primary key to be used as default order"
2015
+ if !_reverse_order_columns.empty?
2016
+ return _reverse_order_columns.map { |column| table[column].desc }
2017
+ end
2018
+
2019
+ raise IrreversibleOrderError, <<~MSG.squish
2020
+ Relation has no order values, and #{model} has no order columns to use as a default.
2021
+ Set at least one of `implicit_order_column`, or `primary_key` on the model when no
2022
+ `order `is specified on the relation.
2023
+ MSG
1819
2024
  end
1820
2025
 
1821
2026
  order_query.flat_map do |o|
@@ -1840,6 +2045,13 @@ module ActiveRecord
1840
2045
  end
1841
2046
  end
1842
2047
 
2048
+ def _reverse_order_columns
2049
+ roc = []
2050
+ roc << model.implicit_order_column if model.implicit_order_column
2051
+ roc << model.primary_key if model.primary_key
2052
+ roc.flatten.uniq.compact
2053
+ end
2054
+
1843
2055
  def does_not_support_reverse?(order)
1844
2056
  # Account for String subclasses like Arel::Nodes::SqlLiteral that
1845
2057
  # override methods like #count.
@@ -1863,7 +2075,9 @@ module ActiveRecord
1863
2075
  args.each do |arg|
1864
2076
  next unless arg.is_a?(Hash)
1865
2077
  arg.each do |_key, value|
1866
- unless VALID_DIRECTIONS.include?(value)
2078
+ if value.is_a?(Hash)
2079
+ validate_order_args([value])
2080
+ elsif VALID_DIRECTIONS.exclude?(value)
1867
2081
  raise ArgumentError,
1868
2082
  "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1869
2083
  end
@@ -1871,10 +2085,14 @@ module ActiveRecord
1871
2085
  end
1872
2086
  end
1873
2087
 
2088
+ def flattened_args(args)
2089
+ args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
2090
+ end
2091
+
1874
2092
  def preprocess_order_args(order_args)
1875
- @klass.disallow_raw_sql!(
1876
- order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1877
- permit: connection.column_name_with_order_matcher
2093
+ model.disallow_raw_sql!(
2094
+ flattened_args(order_args),
2095
+ permit: model.adapter_class.column_name_with_order_matcher
1878
2096
  )
1879
2097
 
1880
2098
  validate_order_args(order_args)
@@ -1888,14 +2106,20 @@ module ActiveRecord
1888
2106
  when Symbol
1889
2107
  order_column(arg.to_s).asc
1890
2108
  when Hash
1891
- arg.map { |field, dir|
1892
- case field
1893
- when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
1894
- field.public_send(dir.downcase)
2109
+ arg.map do |key, value|
2110
+ if value.is_a?(Hash)
2111
+ value.map do |field, dir|
2112
+ order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
2113
+ end
1895
2114
  else
1896
- order_column(field.to_s).public_send(dir.downcase)
2115
+ case key
2116
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
2117
+ key.public_send(value.downcase)
2118
+ else
2119
+ order_column(key.to_s).public_send(value.downcase)
2120
+ end
1897
2121
  end
1898
- }
2122
+ end
1899
2123
  else
1900
2124
  arg
1901
2125
  end
@@ -1904,7 +2128,7 @@ module ActiveRecord
1904
2128
 
1905
2129
  def sanitize_order_arguments(order_args)
1906
2130
  order_args.map! do |arg|
1907
- klass.sanitize_sql_for_order(arg)
2131
+ model.sanitize_sql_for_order(arg)
1908
2132
  end
1909
2133
  end
1910
2134
 
@@ -1930,7 +2154,7 @@ module ActiveRecord
1930
2154
  arg.expr.relation.name
1931
2155
  end
1932
2156
  end
1933
- end.compact
2157
+ end.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
1934
2158
  end
1935
2159
 
1936
2160
  def extract_table_name_from(string)
@@ -1942,17 +2166,18 @@ module ActiveRecord
1942
2166
  if attr_name == "count" && !group_values.empty?
1943
2167
  table[attr_name]
1944
2168
  else
1945
- Arel.sql(connection.quote_table_name(attr_name))
2169
+ Arel.sql(model.adapter_class.quote_table_name(attr_name), retryable: true)
1946
2170
  end
1947
2171
  end
1948
2172
  end
1949
2173
 
1950
- def build_case_for_value_position(column, values)
2174
+ def build_case_for_value_position(column, values, filter: true)
1951
2175
  node = Arel::Nodes::Case.new
1952
2176
  values.each.with_index(1) do |value, order|
1953
2177
  node.when(column.eq(value)).then(order)
1954
2178
  end
1955
2179
 
2180
+ node = node.else(values.length + 1) unless filter
1956
2181
  Arel::Nodes::Ascending.new(node)
1957
2182
  end
1958
2183
 
@@ -2010,38 +2235,40 @@ module ActiveRecord
2010
2235
  def process_select_args(fields)
2011
2236
  fields.flat_map do |field|
2012
2237
  if field.is_a?(Hash)
2013
- transform_select_hash_values(field)
2238
+ arel_column_aliases_from_hash(field)
2014
2239
  else
2015
2240
  field
2016
2241
  end
2017
2242
  end
2018
2243
  end
2019
2244
 
2020
- def transform_select_hash_values(fields)
2245
+ def arel_column_aliases_from_hash(fields)
2021
2246
  fields.flat_map do |key, columns_aliases|
2247
+ table_name = key.is_a?(Symbol) ? key.name : key
2022
2248
  case columns_aliases
2023
2249
  when Hash
2024
2250
  columns_aliases.map do |column, column_alias|
2025
- if values[:joins]&.include?(key)
2026
- references = PredicateBuilder.references({ key.to_s => fields[key] })
2027
- self.references_values |= references unless references.empty?
2028
- end
2029
- arel_column("#{key}.#{column}") do
2030
- predicate_builder.resolve_arel_attribute(key.to_s, column)
2031
- end.as(column_alias.to_s)
2251
+ arel_column_with_table(table_name, column)
2252
+ .as(model.adapter_class.quote_column_name(column_alias.to_s))
2032
2253
  end
2033
2254
  when Array
2034
2255
  columns_aliases.map do |column|
2035
- arel_column("#{key}.#{column}", &:itself)
2256
+ arel_column_with_table(table_name, column)
2036
2257
  end
2037
2258
  when String, Symbol
2038
- arel_column(key.to_s) do
2039
- predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
2040
- end.as(columns_aliases.to_s)
2259
+ arel_column(key)
2260
+ .as(model.adapter_class.quote_column_name(columns_aliases.to_s))
2041
2261
  end
2042
2262
  end
2043
2263
  end
2044
2264
 
2265
+ def process_with_args(args)
2266
+ args.flat_map do |arg|
2267
+ raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
2268
+ arg.map { |k, v| { k => v } }
2269
+ end
2270
+ end
2271
+
2045
2272
  STRUCTURAL_VALUE_METHODS = (
2046
2273
  Relation::VALUE_METHODS -
2047
2274
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
@@ -2051,11 +2278,11 @@ module ActiveRecord
2051
2278
  values = other.values
2052
2279
  STRUCTURAL_VALUE_METHODS.reject do |method|
2053
2280
  v1, v2 = @values[method], values[method]
2054
- if v1.is_a?(Array)
2055
- next true unless v2.is_a?(Array)
2056
- v1 = v1.uniq
2057
- v2 = v2.uniq
2058
- end
2281
+
2282
+ # `and`/`or` are focused to combine where-like clauses, so it relaxes
2283
+ # the difference when other's multi values are uninitialized.
2284
+ next true if v1.is_a?(Array) && v2.nil?
2285
+
2059
2286
  v1 == v2
2060
2287
  end
2061
2288
  end