activerecord 7.1.5.1 → 8.0.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +369 -2484
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +2 -1
  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 +21 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +7 -6
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  13. data/lib/active_record/associations/builder/has_many.rb +3 -4
  14. data/lib/active_record/associations/builder/has_one.rb +3 -4
  15. data/lib/active_record/associations/collection_association.rb +17 -9
  16. data/lib/active_record/associations/collection_proxy.rb +14 -1
  17. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  18. data/lib/active_record/associations/errors.rb +265 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  21. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +4 -3
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +14 -3
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +92 -295
  29. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  30. data/lib/active_record/attribute_assignment.rb +0 -2
  31. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  32. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  33. data/lib/active_record/attribute_methods/read.rb +1 -13
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
  36. data/lib/active_record/attribute_methods.rb +71 -75
  37. data/lib/active_record/attributes.rb +63 -49
  38. data/lib/active_record/autosave_association.rb +92 -57
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/callbacks.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
  42. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
  48. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  49. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  50. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
  51. data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
  52. data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
  53. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
  60. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
  73. data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
  74. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  75. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  77. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  78. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  79. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
  80. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
  81. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
  82. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  83. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
  84. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  85. data/lib/active_record/connection_adapters.rb +65 -0
  86. data/lib/active_record/connection_handling.rb +74 -37
  87. data/lib/active_record/core.rb +132 -51
  88. data/lib/active_record/counter_cache.rb +19 -10
  89. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  90. data/lib/active_record/database_configurations/database_config.rb +23 -4
  91. data/lib/active_record/database_configurations/hash_config.rb +46 -34
  92. data/lib/active_record/database_configurations/url_config.rb +20 -1
  93. data/lib/active_record/database_configurations.rb +1 -1
  94. data/lib/active_record/delegated_type.rb +41 -17
  95. data/lib/active_record/dynamic_matchers.rb +2 -2
  96. data/lib/active_record/encryption/config.rb +3 -1
  97. data/lib/active_record/encryption/encryptable_record.rb +7 -7
  98. data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
  99. data/lib/active_record/encryption/encryptor.rb +28 -6
  100. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  101. data/lib/active_record/encryption/key_provider.rb +1 -1
  102. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  103. data/lib/active_record/encryption/message_serializer.rb +4 -0
  104. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  105. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  106. data/lib/active_record/encryption/scheme.rb +8 -1
  107. data/lib/active_record/enum.rb +20 -16
  108. data/lib/active_record/errors.rb +54 -20
  109. data/lib/active_record/explain.rb +13 -24
  110. data/lib/active_record/fixtures.rb +37 -33
  111. data/lib/active_record/future_result.rb +21 -13
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +4 -2
  114. data/lib/active_record/insert_all.rb +19 -16
  115. data/lib/active_record/integration.rb +4 -1
  116. data/lib/active_record/internal_metadata.rb +48 -34
  117. data/lib/active_record/locking/optimistic.rb +8 -7
  118. data/lib/active_record/log_subscriber.rb +5 -32
  119. data/lib/active_record/message_pack.rb +1 -1
  120. data/lib/active_record/migration/command_recorder.rb +33 -14
  121. data/lib/active_record/migration/compatibility.rb +8 -3
  122. data/lib/active_record/migration/default_strategy.rb +4 -5
  123. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  124. data/lib/active_record/migration.rb +104 -98
  125. data/lib/active_record/model_schema.rb +32 -70
  126. data/lib/active_record/nested_attributes.rb +15 -9
  127. data/lib/active_record/normalization.rb +3 -7
  128. data/lib/active_record/persistence.rb +127 -451
  129. data/lib/active_record/query_cache.rb +19 -8
  130. data/lib/active_record/query_logs.rb +104 -37
  131. data/lib/active_record/query_logs_formatter.rb +17 -28
  132. data/lib/active_record/querying.rb +24 -12
  133. data/lib/active_record/railtie.rb +26 -68
  134. data/lib/active_record/railties/controller_runtime.rb +13 -4
  135. data/lib/active_record/railties/databases.rake +43 -61
  136. data/lib/active_record/reflection.rb +112 -53
  137. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  138. data/lib/active_record/relation/batches.rb +138 -72
  139. data/lib/active_record/relation/calculations.rb +122 -82
  140. data/lib/active_record/relation/delegation.rb +30 -22
  141. data/lib/active_record/relation/finder_methods.rb +32 -18
  142. data/lib/active_record/relation/merger.rb +12 -14
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  145. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  146. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  147. data/lib/active_record/relation/predicate_builder.rb +16 -3
  148. data/lib/active_record/relation/query_attribute.rb +1 -1
  149. data/lib/active_record/relation/query_methods.rb +317 -101
  150. data/lib/active_record/relation/spawn_methods.rb +3 -19
  151. data/lib/active_record/relation/where_clause.rb +7 -19
  152. data/lib/active_record/relation.rb +561 -119
  153. data/lib/active_record/result.rb +95 -46
  154. data/lib/active_record/runtime_registry.rb +39 -0
  155. data/lib/active_record/sanitization.rb +31 -25
  156. data/lib/active_record/schema.rb +8 -6
  157. data/lib/active_record/schema_dumper.rb +53 -20
  158. data/lib/active_record/schema_migration.rb +31 -14
  159. data/lib/active_record/scoping/named.rb +6 -2
  160. data/lib/active_record/signed_id.rb +24 -4
  161. data/lib/active_record/statement_cache.rb +19 -19
  162. data/lib/active_record/store.rb +7 -3
  163. data/lib/active_record/table_metadata.rb +2 -13
  164. data/lib/active_record/tasks/database_tasks.rb +87 -58
  165. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
  166. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  167. data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
  168. data/lib/active_record/test_fixtures.rb +98 -89
  169. data/lib/active_record/testing/query_assertions.rb +121 -0
  170. data/lib/active_record/timestamp.rb +2 -2
  171. data/lib/active_record/token_for.rb +22 -12
  172. data/lib/active_record/touch_later.rb +1 -1
  173. data/lib/active_record/transaction.rb +132 -0
  174. data/lib/active_record/transactions.rb +72 -17
  175. data/lib/active_record/translation.rb +0 -2
  176. data/lib/active_record/type/serialized.rb +1 -3
  177. data/lib/active_record/type_caster/connection.rb +4 -4
  178. data/lib/active_record/validations/associated.rb +9 -3
  179. data/lib/active_record/validations/uniqueness.rb +23 -18
  180. data/lib/active_record/validations.rb +4 -1
  181. data/lib/active_record.rb +138 -57
  182. data/lib/arel/alias_predication.rb +1 -1
  183. data/lib/arel/collectors/bind.rb +4 -2
  184. data/lib/arel/collectors/composite.rb +7 -0
  185. data/lib/arel/collectors/sql_string.rb +2 -2
  186. data/lib/arel/collectors/substitute_binds.rb +3 -3
  187. data/lib/arel/nodes/binary.rb +1 -7
  188. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  189. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  190. data/lib/arel/nodes/node.rb +5 -4
  191. data/lib/arel/nodes/sql_literal.rb +8 -1
  192. data/lib/arel/nodes.rb +2 -2
  193. data/lib/arel/predications.rb +1 -1
  194. data/lib/arel/select_manager.rb +1 -1
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/tree_manager.rb +3 -2
  197. data/lib/arel/update_manager.rb +2 -1
  198. data/lib/arel/visitors/dot.rb +1 -0
  199. data/lib/arel/visitors/mysql.rb +9 -4
  200. data/lib/arel/visitors/postgresql.rb +1 -12
  201. data/lib/arel/visitors/sqlite.rb +25 -0
  202. data/lib/arel/visitors/to_sql.rb +29 -16
  203. data/lib/arel.rb +7 -3
  204. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  205. metadata +18 -16
  206. 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 posts_and_replies ON posts.in_reply_to_id = posts_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
 
@@ -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
@@ -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
 
@@ -1453,6 +1554,9 @@ module ActiveRecord
1453
1554
  # Post.excluding(post_one, post_two)
1454
1555
  # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1455
1556
  #
1557
+ # Post.excluding(Post.drafts)
1558
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
1559
+ #
1456
1560
  # This can also be called on associations. As with the above example, either
1457
1561
  # a single record of collection thereof may be specified:
1458
1562
  #
@@ -1468,14 +1572,15 @@ module ActiveRecord
1468
1572
  # is passed in) are not instances of the same model that the relation is
1469
1573
  # scoping.
1470
1574
  def excluding(*records)
1575
+ relations = records.extract! { |element| element.is_a?(Relation) }
1471
1576
  records.flatten!(1)
1472
1577
  records.compact!
1473
1578
 
1474
- unless records.all?(klass)
1475
- raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1579
+ unless records.all?(model) && relations.all? { |relation| relation.model == model }
1580
+ raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}."
1476
1581
  end
1477
1582
 
1478
- spawn.excluding!(records)
1583
+ spawn.excluding!(records + relations.flat_map(&:ids))
1479
1584
  end
1480
1585
  alias :without :excluding
1481
1586
 
@@ -1487,12 +1592,12 @@ module ActiveRecord
1487
1592
 
1488
1593
  # Returns the Arel object associated with the relation.
1489
1594
  def arel(aliases = nil) # :nodoc:
1490
- @arel ||= build_arel(aliases)
1595
+ @arel ||= with_connection { |c| build_arel(c, aliases) }
1491
1596
  end
1492
1597
 
1493
1598
  def construct_join_dependency(associations, join_type) # :nodoc:
1494
1599
  ActiveRecord::Associations::JoinDependency.new(
1495
- klass, table, associations, join_type
1600
+ model, table, associations, join_type
1496
1601
  )
1497
1602
  end
1498
1603
 
@@ -1508,16 +1613,28 @@ module ActiveRecord
1508
1613
  def build_where_clause(opts, rest = []) # :nodoc:
1509
1614
  opts = sanitize_forbidden_attributes(opts)
1510
1615
 
1616
+ if opts.is_a?(Array)
1617
+ opts, *rest = opts
1618
+ end
1619
+
1511
1620
  case opts
1512
- when String, Array
1513
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1621
+ when String
1622
+ if rest.empty?
1623
+ parts = [Arel.sql(opts)]
1624
+ elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
1625
+ parts = [build_named_bound_sql_literal(opts, rest.first)]
1626
+ elsif opts.include?("?")
1627
+ parts = [build_bound_sql_literal(opts, rest)]
1628
+ else
1629
+ parts = [model.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1630
+ end
1514
1631
  when Hash
1515
1632
  opts = opts.transform_keys do |key|
1516
1633
  if key.is_a?(Array)
1517
- key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
1634
+ key.map { |k| model.attribute_aliases[k.to_s] || k.to_s }
1518
1635
  else
1519
1636
  key = key.to_s
1520
- klass.attribute_aliases[key] || key
1637
+ model.attribute_aliases[key] || key
1521
1638
  end
1522
1639
  end
1523
1640
  references = PredicateBuilder.references(opts)
@@ -1541,11 +1658,67 @@ module ActiveRecord
1541
1658
  self
1542
1659
  end
1543
1660
 
1661
+ protected
1662
+ def arel_columns(columns)
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,12 +1743,11 @@ 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(connection, aliases = nil)
1579
1751
  arel = Arel::SelectManager.new(table)
1580
1752
 
1581
1753
  build_joins(arel.join_sources, aliases)
@@ -1674,7 +1846,7 @@ module ActiveRecord
1674
1846
 
1675
1847
  joins = joins_values.dup
1676
1848
  if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1677
- stashed_eager_load = joins.pop if joins.last.base_klass == klass
1849
+ stashed_eager_load = joins.pop if joins.last.base_klass == model
1678
1850
  end
1679
1851
 
1680
1852
  joins.each_with_index do |join, i|
@@ -1731,8 +1903,8 @@ module ActiveRecord
1731
1903
  def build_select(arel)
1732
1904
  if select_values.any?
1733
1905
  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] })
1906
+ elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements
1907
+ arel.project(*model.column_names.map { |field| table[field] })
1736
1908
  else
1737
1909
  arel.project(table[Arel.star])
1738
1910
  end
@@ -1742,25 +1914,40 @@ module ActiveRecord
1742
1914
  return if with_values.empty?
1743
1915
 
1744
1916
  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
1917
  build_with_value_from_hash(with_value)
1748
1918
  end
1749
1919
 
1750
- arel.with(with_statements)
1920
+ @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
1751
1921
  end
1752
1922
 
1753
1923
  def build_with_value_from_hash(hash)
1754
1924
  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)
1925
+ Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
1926
+ end
1927
+ end
1928
+
1929
+ def build_with_expression_from_value(value, nested = false)
1930
+ case value
1931
+ when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1932
+ when ActiveRecord::Relation
1933
+ if nested
1934
+ value.arel.ast
1935
+ else
1936
+ value.arel
1937
+ end
1938
+ when Arel::SelectManager then value
1939
+ when Array
1940
+ return build_with_expression_from_value(value.first, false) if value.size == 1
1941
+
1942
+ parts = value.map do |query|
1943
+ build_with_expression_from_value(query, true)
1944
+ end
1945
+
1946
+ parts.reduce do |result, value|
1947
+ Arel::Nodes::UnionAll.new(result, value)
1948
+ end
1949
+ else
1950
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1764
1951
  end
1765
1952
  end
1766
1953
 
@@ -1768,46 +1955,60 @@ module ActiveRecord
1768
1955
  with_table = Arel::Table.new(name)
1769
1956
 
1770
1957
  table.join(with_table, kind).on(
1771
- with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1958
+ with_table[model.model_name.to_s.foreign_key].eq(table[model.primary_key])
1772
1959
  ).join_sources.first
1773
1960
  end
1774
1961
 
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)
1962
+ def arel_columns_from_hash(fields)
1963
+ fields.flat_map do |table_name, columns|
1964
+ table_name = table_name.name if table_name.is_a?(Symbol)
1965
+ case columns
1966
+ when Symbol, String
1967
+ arel_column_with_table(table_name, columns)
1968
+ when Array
1969
+ columns.map do |column|
1970
+ arel_column_with_table(table_name, column)
1781
1971
  end
1782
- when String
1783
- arel_column(field, &:itself)
1784
- when Proc
1785
- field.call
1786
1972
  else
1787
- field
1973
+ raise TypeError, "Expected Symbol, String or Array, got: #{columns.class}"
1788
1974
  end
1789
1975
  end
1790
1976
  end
1791
1977
 
1978
+ def arel_column_with_table(table_name, column_name)
1979
+ self.references_values |= [Arel.sql(table_name, retryable: true)]
1980
+
1981
+ if column_name.is_a?(Symbol) || !column_name.match?(/\W/)
1982
+ predicate_builder.resolve_arel_attribute(table_name, column_name) do
1983
+ lookup_table_klass_from_join_dependencies(table_name)
1984
+ end
1985
+ else
1986
+ Arel.sql("#{model.adapter_class.quote_table_name(table_name)}.#{column_name}")
1987
+ end
1988
+ end
1989
+
1792
1990
  def arel_column(field)
1793
- field = klass.attribute_aliases[field] || field
1991
+ field = field.name if is_symbol = field.is_a?(Symbol)
1992
+
1993
+ field = model.attribute_aliases[field] || field.to_s
1794
1994
  from = from_clause.name || from_clause.value
1795
1995
 
1796
- if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1996
+ if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
1797
1997
  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
1998
+ elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
1999
+ arel_column_with_table(table, column)
2000
+ elsif block_given?
1804
2001
  yield field
2002
+ elsif Arel.arel_node?(field)
2003
+ field
2004
+ else
2005
+ Arel.sql(is_symbol ? model.adapter_class.quote_table_name(field) : field)
1805
2006
  end
1806
2007
  end
1807
2008
 
1808
2009
  def table_name_matches?(from)
1809
2010
  table_name = Regexp.escape(table.name)
1810
- quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
2011
+ quoted_table_name = Regexp.escape(model.adapter_class.quote_table_name(table.name))
1811
2012
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1812
2013
  end
1813
2014
 
@@ -1863,7 +2064,9 @@ module ActiveRecord
1863
2064
  args.each do |arg|
1864
2065
  next unless arg.is_a?(Hash)
1865
2066
  arg.each do |_key, value|
1866
- unless VALID_DIRECTIONS.include?(value)
2067
+ if value.is_a?(Hash)
2068
+ validate_order_args([value])
2069
+ elsif VALID_DIRECTIONS.exclude?(value)
1867
2070
  raise ArgumentError,
1868
2071
  "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1869
2072
  end
@@ -1871,10 +2074,14 @@ module ActiveRecord
1871
2074
  end
1872
2075
  end
1873
2076
 
2077
+ def flattened_args(args)
2078
+ args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
2079
+ end
2080
+
1874
2081
  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
2082
+ model.disallow_raw_sql!(
2083
+ flattened_args(order_args),
2084
+ permit: model.adapter_class.column_name_with_order_matcher
1878
2085
  )
1879
2086
 
1880
2087
  validate_order_args(order_args)
@@ -1888,14 +2095,20 @@ module ActiveRecord
1888
2095
  when Symbol
1889
2096
  order_column(arg.to_s).asc
1890
2097
  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)
2098
+ arg.map do |key, value|
2099
+ if value.is_a?(Hash)
2100
+ value.map do |field, dir|
2101
+ order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
2102
+ end
1895
2103
  else
1896
- order_column(field.to_s).public_send(dir.downcase)
2104
+ case key
2105
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
2106
+ key.public_send(value.downcase)
2107
+ else
2108
+ order_column(key.to_s).public_send(value.downcase)
2109
+ end
1897
2110
  end
1898
- }
2111
+ end
1899
2112
  else
1900
2113
  arg
1901
2114
  end
@@ -1904,7 +2117,7 @@ module ActiveRecord
1904
2117
 
1905
2118
  def sanitize_order_arguments(order_args)
1906
2119
  order_args.map! do |arg|
1907
- klass.sanitize_sql_for_order(arg)
2120
+ model.sanitize_sql_for_order(arg)
1908
2121
  end
1909
2122
  end
1910
2123
 
@@ -1930,7 +2143,7 @@ module ActiveRecord
1930
2143
  arg.expr.relation.name
1931
2144
  end
1932
2145
  end
1933
- end.compact
2146
+ end.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
1934
2147
  end
1935
2148
 
1936
2149
  def extract_table_name_from(string)
@@ -1942,17 +2155,18 @@ module ActiveRecord
1942
2155
  if attr_name == "count" && !group_values.empty?
1943
2156
  table[attr_name]
1944
2157
  else
1945
- Arel.sql(connection.quote_table_name(attr_name))
2158
+ Arel.sql(model.adapter_class.quote_table_name(attr_name), retryable: true)
1946
2159
  end
1947
2160
  end
1948
2161
  end
1949
2162
 
1950
- def build_case_for_value_position(column, values)
2163
+ def build_case_for_value_position(column, values, filter: true)
1951
2164
  node = Arel::Nodes::Case.new
1952
2165
  values.each.with_index(1) do |value, order|
1953
2166
  node.when(column.eq(value)).then(order)
1954
2167
  end
1955
2168
 
2169
+ node = node.else(values.length + 1) unless filter
1956
2170
  Arel::Nodes::Ascending.new(node)
1957
2171
  end
1958
2172
 
@@ -2010,38 +2224,40 @@ module ActiveRecord
2010
2224
  def process_select_args(fields)
2011
2225
  fields.flat_map do |field|
2012
2226
  if field.is_a?(Hash)
2013
- transform_select_hash_values(field)
2227
+ arel_column_aliases_from_hash(field)
2014
2228
  else
2015
2229
  field
2016
2230
  end
2017
2231
  end
2018
2232
  end
2019
2233
 
2020
- def transform_select_hash_values(fields)
2234
+ def arel_column_aliases_from_hash(fields)
2021
2235
  fields.flat_map do |key, columns_aliases|
2236
+ table_name = key.is_a?(Symbol) ? key.name : key
2022
2237
  case columns_aliases
2023
2238
  when Hash
2024
2239
  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)
2240
+ arel_column_with_table(table_name, column)
2241
+ .as(model.adapter_class.quote_column_name(column_alias.to_s))
2032
2242
  end
2033
2243
  when Array
2034
2244
  columns_aliases.map do |column|
2035
- arel_column("#{key}.#{column}", &:itself)
2245
+ arel_column_with_table(table_name, column)
2036
2246
  end
2037
2247
  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)
2248
+ arel_column(key)
2249
+ .as(model.adapter_class.quote_column_name(columns_aliases.to_s))
2041
2250
  end
2042
2251
  end
2043
2252
  end
2044
2253
 
2254
+ def process_with_args(args)
2255
+ args.flat_map do |arg|
2256
+ raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
2257
+ arg.map { |k, v| { k => v } }
2258
+ end
2259
+ end
2260
+
2045
2261
  STRUCTURAL_VALUE_METHODS = (
2046
2262
  Relation::VALUE_METHODS -
2047
2263
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]