activerecord 7.0.0 → 7.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1701 -1039
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +362 -236
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
  84. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  85. data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
  86. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  87. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  93. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  94. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  95. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  96. data/lib/active_record/connection_adapters.rb +3 -1
  97. data/lib/active_record/connection_handling.rb +73 -96
  98. data/lib/active_record/core.rb +142 -153
  99. data/lib/active_record/counter_cache.rb +46 -25
  100. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  101. data/lib/active_record/database_configurations/database_config.rb +9 -3
  102. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  103. data/lib/active_record/database_configurations/url_config.rb +17 -11
  104. data/lib/active_record/database_configurations.rb +87 -34
  105. data/lib/active_record/delegated_type.rb +9 -4
  106. data/lib/active_record/deprecator.rb +7 -0
  107. data/lib/active_record/destroy_association_async_job.rb +2 -0
  108. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  109. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  110. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  111. data/lib/active_record/encryption/config.rb +25 -1
  112. data/lib/active_record/encryption/configurable.rb +13 -14
  113. data/lib/active_record/encryption/context.rb +10 -3
  114. data/lib/active_record/encryption/contexts.rb +8 -4
  115. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  116. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  117. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  119. data/lib/active_record/encryption/encryptor.rb +7 -7
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  121. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  122. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  123. data/lib/active_record/encryption/key_generator.rb +12 -1
  124. data/lib/active_record/encryption/message.rb +1 -1
  125. data/lib/active_record/encryption/message_serializer.rb +2 -0
  126. data/lib/active_record/encryption/properties.rb +4 -4
  127. data/lib/active_record/encryption/scheme.rb +20 -23
  128. data/lib/active_record/encryption.rb +1 -0
  129. data/lib/active_record/enum.rb +113 -29
  130. data/lib/active_record/errors.rb +108 -15
  131. data/lib/active_record/explain.rb +23 -3
  132. data/lib/active_record/explain_subscriber.rb +1 -1
  133. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  134. data/lib/active_record/fixture_set/render_context.rb +2 -0
  135. data/lib/active_record/fixture_set/table_row.rb +29 -8
  136. data/lib/active_record/fixtures.rb +121 -73
  137. data/lib/active_record/future_result.rb +30 -5
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +30 -16
  140. data/lib/active_record/insert_all.rb +57 -10
  141. data/lib/active_record/integration.rb +10 -10
  142. data/lib/active_record/internal_metadata.rb +120 -30
  143. data/lib/active_record/locking/optimistic.rb +32 -18
  144. data/lib/active_record/locking/pessimistic.rb +8 -5
  145. data/lib/active_record/log_subscriber.rb +39 -17
  146. data/lib/active_record/marshalling.rb +56 -0
  147. data/lib/active_record/message_pack.rb +124 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  149. data/lib/active_record/middleware/database_selector.rb +18 -13
  150. data/lib/active_record/middleware/shard_selector.rb +7 -5
  151. data/lib/active_record/migration/command_recorder.rb +108 -10
  152. data/lib/active_record/migration/compatibility.rb +158 -64
  153. data/lib/active_record/migration/default_strategy.rb +23 -0
  154. data/lib/active_record/migration/execution_strategy.rb +19 -0
  155. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  156. data/lib/active_record/migration.rb +274 -117
  157. data/lib/active_record/model_schema.rb +86 -54
  158. data/lib/active_record/nested_attributes.rb +24 -6
  159. data/lib/active_record/normalization.rb +167 -0
  160. data/lib/active_record/persistence.rb +200 -47
  161. data/lib/active_record/promise.rb +84 -0
  162. data/lib/active_record/query_cache.rb +3 -21
  163. data/lib/active_record/query_logs.rb +87 -51
  164. data/lib/active_record/query_logs_formatter.rb +41 -0
  165. data/lib/active_record/querying.rb +16 -3
  166. data/lib/active_record/railtie.rb +128 -62
  167. data/lib/active_record/railties/controller_runtime.rb +12 -8
  168. data/lib/active_record/railties/databases.rake +145 -146
  169. data/lib/active_record/railties/job_runtime.rb +23 -0
  170. data/lib/active_record/readonly_attributes.rb +32 -5
  171. data/lib/active_record/reflection.rb +189 -45
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  173. data/lib/active_record/relation/batches.rb +190 -61
  174. data/lib/active_record/relation/calculations.rb +208 -83
  175. data/lib/active_record/relation/delegation.rb +23 -9
  176. data/lib/active_record/relation/finder_methods.rb +77 -16
  177. data/lib/active_record/relation/merger.rb +2 -0
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  181. data/lib/active_record/relation/predicate_builder.rb +26 -14
  182. data/lib/active_record/relation/query_attribute.rb +25 -1
  183. data/lib/active_record/relation/query_methods.rb +430 -77
  184. data/lib/active_record/relation/spawn_methods.rb +18 -1
  185. data/lib/active_record/relation.rb +98 -41
  186. data/lib/active_record/result.rb +25 -9
  187. data/lib/active_record/runtime_registry.rb +10 -1
  188. data/lib/active_record/sanitization.rb +57 -16
  189. data/lib/active_record/schema.rb +36 -22
  190. data/lib/active_record/schema_dumper.rb +65 -23
  191. data/lib/active_record/schema_migration.rb +68 -33
  192. data/lib/active_record/scoping/default.rb +20 -12
  193. data/lib/active_record/scoping/named.rb +2 -2
  194. data/lib/active_record/scoping.rb +2 -1
  195. data/lib/active_record/secure_password.rb +60 -0
  196. data/lib/active_record/secure_token.rb +21 -3
  197. data/lib/active_record/serialization.rb +5 -0
  198. data/lib/active_record/signed_id.rb +9 -7
  199. data/lib/active_record/store.rb +16 -11
  200. data/lib/active_record/suppressor.rb +3 -1
  201. data/lib/active_record/table_metadata.rb +16 -3
  202. data/lib/active_record/tasks/database_tasks.rb +138 -107
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  206. data/lib/active_record/test_fixtures.rb +123 -99
  207. data/lib/active_record/timestamp.rb +27 -15
  208. data/lib/active_record/token_for.rb +113 -0
  209. data/lib/active_record/touch_later.rb +11 -6
  210. data/lib/active_record/transactions.rb +39 -13
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  213. data/lib/active_record/type/internal/timezone.rb +7 -2
  214. data/lib/active_record/type/serialized.rb +8 -4
  215. data/lib/active_record/type/time.rb +4 -0
  216. data/lib/active_record/validations/absence.rb +1 -1
  217. data/lib/active_record/validations/associated.rb +3 -3
  218. data/lib/active_record/validations/numericality.rb +5 -4
  219. data/lib/active_record/validations/presence.rb +5 -28
  220. data/lib/active_record/validations/uniqueness.rb +50 -5
  221. data/lib/active_record/validations.rb +8 -4
  222. data/lib/active_record/version.rb +1 -1
  223. data/lib/active_record.rb +143 -16
  224. data/lib/arel/errors.rb +10 -0
  225. data/lib/arel/factory_methods.rb +4 -0
  226. data/lib/arel/filter_predications.rb +1 -1
  227. data/lib/arel/nodes/and.rb +4 -0
  228. data/lib/arel/nodes/binary.rb +6 -1
  229. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  230. data/lib/arel/nodes/cte.rb +36 -0
  231. data/lib/arel/nodes/filter.rb +1 -1
  232. data/lib/arel/nodes/fragments.rb +35 -0
  233. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  234. data/lib/arel/nodes/leading_join.rb +8 -0
  235. data/lib/arel/nodes/node.rb +111 -2
  236. data/lib/arel/nodes/sql_literal.rb +6 -0
  237. data/lib/arel/nodes/table_alias.rb +4 -0
  238. data/lib/arel/nodes.rb +4 -0
  239. data/lib/arel/predications.rb +2 -0
  240. data/lib/arel/table.rb +9 -5
  241. data/lib/arel/visitors/mysql.rb +8 -1
  242. data/lib/arel/visitors/to_sql.rb +81 -17
  243. data/lib/arel/visitors/visitor.rb +2 -2
  244. data/lib/arel.rb +16 -2
  245. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  246. data/lib/rails/generators/active_record/migration.rb +3 -1
  247. data/lib/rails/generators/active_record/model/USAGE +113 -0
  248. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  249. metadata +51 -15
  250. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  251. data/lib/active_record/null_relation.rb +0 -63
@@ -3,17 +3,16 @@
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.
14
- # In this case, #where must be chained with #not to return a new relation.
12
+ # +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter.
13
+ # In this case, +where+ can be chained to return a new relation.
15
14
  class WhereChain
16
- def initialize(scope)
15
+ def initialize(scope) # :nodoc:
17
16
  @scope = scope
18
17
  end
19
18
 
@@ -39,7 +38,14 @@ 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')
42
+ #
43
+ # If there is a non-nil condition on a nullable column in the hash condition, the records that have
44
+ # nil values on the nullable column won't be returned.
45
+ # User.create!(nullable_country: nil)
46
+ # User.where.not(nullable_country: "UK")
47
+ # # SELECT * FROM users WHERE NOT (nullable_country = 'UK')
48
+ # # => []
43
49
  def not(opts, *rest)
44
50
  where_clause = @scope.send(:build_where_clause, opts, rest)
45
51
 
@@ -68,9 +74,13 @@ module ActiveRecord
68
74
  # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
69
75
  def associated(*associations)
70
76
  associations.each do |association|
71
- reflection = @scope.klass._reflect_on_association(association)
77
+ reflection = scope_association_reflection(association)
72
78
  @scope.joins!(association)
73
- self.not(reflection.table_name => { reflection.association_primary_key => nil })
79
+ if reflection.options[:class_name]
80
+ self.not(association => { reflection.association_primary_key => nil })
81
+ else
82
+ self.not(reflection.table_name => { reflection.association_primary_key => nil })
83
+ end
74
84
  end
75
85
 
76
86
  @scope
@@ -96,13 +106,35 @@ module ActiveRecord
96
106
  # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
97
107
  def missing(*associations)
98
108
  associations.each do |association|
99
- reflection = @scope.klass._reflect_on_association(association)
109
+ reflection = scope_association_reflection(association)
100
110
  @scope.left_outer_joins!(association)
101
- @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
111
+ if reflection.options[:class_name]
112
+ @scope.where!(association => { reflection.association_primary_key => nil })
113
+ else
114
+ @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
115
+ end
102
116
  end
103
117
 
104
118
  @scope
105
119
  end
120
+
121
+ private
122
+ def scope_association_reflection(association)
123
+ reflection = @scope.klass._reflect_on_association(association)
124
+ unless reflection
125
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
126
+ end
127
+ reflection
128
+ end
129
+ end
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
106
138
  end
107
139
 
108
140
  FROZEN_EMPTY_ARRAY = [].freeze
@@ -133,45 +165,69 @@ module ActiveRecord
133
165
 
134
166
  alias extensions extending_values
135
167
 
136
- # Specify relationships to be included in the result set. For
137
- # 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.
171
+ #
172
+ # For example:
138
173
  #
139
- # users = User.includes(:address)
174
+ # users = User.includes(:address).limit(5)
140
175
  # users.each do |user|
141
176
  # user.address.city
142
177
  # end
143
178
  #
144
- # allows you to access the +address+ attribute of the +User+ model without
145
- # firing an additional query. This will often result in a
146
- # 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.
147
184
  #
148
- # You can also specify multiple relationships, like this:
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.
149
188
  #
150
- # users = User.includes(:address, :friends)
189
+ # You can also specify multiple associations. Each association will result
190
+ # in an additional query:
151
191
  #
152
- # Loading nested relationships is possible using a Hash:
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)
153
196
  #
154
- # users = User.includes(:address, friends: [:address, :followers])
197
+ # Loading nested associations is possible using a Hash:
155
198
  #
156
- # === conditions
199
+ # User.includes(:address, friends: [:address, :followers])
200
+ #
201
+ # === Conditions
157
202
  #
158
203
  # If you want to add string conditions to your included models, you'll have
159
204
  # to explicitly reference them. For example:
160
205
  #
161
- # User.includes(:posts).where('posts.name = ?', 'example')
206
+ # User.includes(:posts).where('posts.name = ?', 'example').to_a
162
207
  #
163
208
  # Will throw an error, but this will work:
164
209
  #
165
- # 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.
166
217
  #
167
218
  # Note that #includes works with association names while #references needs
168
219
  # the actual table name.
169
220
  #
170
- # 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
171
222
  # explicitly, as #where references the tables for you. For example, this
172
223
  # will work correctly:
173
224
  #
174
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.
175
231
  def includes(*args)
176
232
  check_if_method_has_arguments!(__callee__, args)
177
233
  spawn.includes!(*args)
@@ -182,12 +238,32 @@ module ActiveRecord
182
238
  self
183
239
  end
184
240
 
185
- # 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
252
+ #
253
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
254
+ # are loaded with a single joined query.
186
255
  #
187
- # User.eager_load(:posts)
188
- # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
189
- # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
190
- # # "users"."id"
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.
191
267
  def eager_load(*args)
192
268
  check_if_method_has_arguments!(__callee__, args)
193
269
  spawn.eager_load!(*args)
@@ -198,10 +274,28 @@ module ActiveRecord
198
274
  self
199
275
  end
200
276
 
201
- # 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)
287
+ #
288
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
289
+ # are loaded with a separate query.
202
290
  #
203
- # User.preload(:posts)
204
- # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
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 ...
205
299
  def preload(*args)
206
300
  check_if_method_has_arguments!(__callee__, args)
207
301
  spawn.preload!(*args)
@@ -226,7 +320,7 @@ module ActiveRecord
226
320
  end
227
321
 
228
322
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
229
- # 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.
230
324
  # This method only works in conjunction with #includes.
231
325
  # See #includes for more details.
232
326
  #
@@ -270,10 +364,18 @@ module ActiveRecord
270
364
  # Model.select(:field, :other_field, :and_one_more)
271
365
  # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
272
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
+ #
273
375
  # You can also use one or more strings, which will be used unchanged as SELECT fields.
274
376
  #
275
377
  # Model.select('field AS field_one', 'other_field AS field_two')
276
- # # => [#<Model id: nil, field: "value", other_field: "value">]
378
+ # # => [#<Model id: nil, field_one: "value", field_two: "value">]
277
379
  #
278
380
  # If an alias was specified, it will be accessible from the resulting objects:
279
381
  #
@@ -284,7 +386,7 @@ module ActiveRecord
284
386
  # except +id+ will throw ActiveModel::MissingAttributeError:
285
387
  #
286
388
  # Model.select(:field).first.other_field
287
- # # => ActiveModel::MissingAttributeError: missing attribute: other_field
389
+ # # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
288
390
  def select(*fields)
289
391
  if block_given?
290
392
  if fields.any?
@@ -295,6 +397,8 @@ module ActiveRecord
295
397
  end
296
398
 
297
399
  check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
400
+
401
+ fields = process_select_args(fields)
298
402
  spawn._select!(*fields)
299
403
  end
300
404
 
@@ -303,6 +407,66 @@ module ActiveRecord
303
407
  self
304
408
  end
305
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
+
306
470
  # Allows you to change a previously set select statement.
307
471
  #
308
472
  # Post.select(:title, :body)
@@ -315,6 +479,7 @@ module ActiveRecord
315
479
  # Note that we're unscoping the entire select statement.
316
480
  def reselect(*args)
317
481
  check_if_method_has_arguments!(__callee__, args)
482
+ args = process_select_args(args)
318
483
  spawn.reselect!(*args)
319
484
  end
320
485
 
@@ -354,6 +519,27 @@ module ActiveRecord
354
519
  self
355
520
  end
356
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
+
357
543
  # Applies an <code>ORDER BY</code> clause to a query.
358
544
  #
359
545
  # #order accepts arguments in one of several formats.
@@ -402,7 +588,7 @@ module ActiveRecord
402
588
  # User.order(Arel.sql('end_date - start_date'))
403
589
  # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
404
590
  #
405
- # 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.
406
592
  #
407
593
  # User.order(Arel.sql("payload->>'kind'"))
408
594
  # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
@@ -420,22 +606,37 @@ module ActiveRecord
420
606
  self
421
607
  end
422
608
 
423
- # Allows to specify an order by a specific set of values. Depending on your
424
- # 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.
425
610
  #
426
611
  # User.in_order_of(:id, [1, 5, 3])
427
- # # SELECT "users".* FROM "users" ORDER BY FIELD("users"."id", 1, 5, 3)
612
+ # # SELECT "users".* FROM "users"
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
428
619
  #
429
620
  def in_order_of(column, values)
430
621
  klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
622
+ return spawn.none! if values.empty?
431
623
 
432
624
  references = column_references([column])
433
625
  self.references_values |= references unless references.empty?
434
626
 
435
627
  values = values.map { |value| type_caster.type_cast_for_database(column, value) }
436
- column = order_column(column.to_s) if column.is_a?(Symbol)
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
437
636
 
438
- spawn.order!(connection.field_ordered_value(column, values))
637
+ spawn
638
+ .order!(build_case_for_value_position(arel_column, values))
639
+ .where!(where_clause)
439
640
  end
440
641
 
441
642
  # Replaces any existing order defined on the relation with the specified order.
@@ -446,7 +647,7 @@ module ActiveRecord
446
647
  #
447
648
  # User.order('email DESC').reorder('id ASC').order('name ASC')
448
649
  #
449
- # generates a query with 'ORDER BY id ASC, name ASC'.
650
+ # generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
450
651
  def reorder(*args)
451
652
  check_if_method_has_arguments!(__callee__, args) do
452
653
  sanitize_order_arguments(args)
@@ -465,7 +666,8 @@ module ActiveRecord
465
666
 
466
667
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
467
668
  :limit, :offset, :joins, :left_outer_joins, :annotate,
468
- :includes, :from, :readonly, :having, :optimizer_hints])
669
+ :includes, :eager_load, :preload, :from, :readonly,
670
+ :having, :optimizer_hints])
469
671
 
470
672
  # Removes an unwanted relation that is already defined on a chain of relations.
471
673
  # This is useful when passing around chains of relations and would like to
@@ -575,7 +777,7 @@ module ActiveRecord
575
777
  # Performs LEFT OUTER JOINs on +args+:
576
778
  #
577
779
  # User.left_outer_joins(:posts)
578
- # => 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"
579
781
  #
580
782
  def left_outer_joins(*args)
581
783
  check_if_method_has_arguments!(__callee__, args)
@@ -595,7 +797,7 @@ module ActiveRecord
595
797
  # SQL is given as an illustration; the actual query generated may be different depending
596
798
  # on the database adapter.
597
799
  #
598
- # === string
800
+ # === \String
599
801
  #
600
802
  # A single string, without additional arguments, is passed to the query
601
803
  # constructor as an SQL fragment, and used in the where clause of the query.
@@ -607,7 +809,7 @@ module ActiveRecord
607
809
  # to injection attacks if not done properly. As an alternative, it is recommended
608
810
  # to use one of the following methods.
609
811
  #
610
- # === array
812
+ # === \Array
611
813
  #
612
814
  # If an array is passed, then the first element of the array is treated as a template, and
613
815
  # the remaining elements are inserted into the template to generate the condition.
@@ -647,7 +849,7 @@ module ActiveRecord
647
849
  # dependencies on the underlying database. If your code is intended for general consumption,
648
850
  # test with multiple database backends.
649
851
  #
650
- # === hash
852
+ # === \Hash
651
853
  #
652
854
  # #where will also accept a hash condition, in which the keys are fields and the values
653
855
  # are values to be searched for.
@@ -681,6 +883,12 @@ module ActiveRecord
681
883
  # PriceEstimate.where(estimate_of: treasure)
682
884
  # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
683
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
+ #
684
892
  # === Joins
685
893
  #
686
894
  # If the relation is the result of a join, you may create a condition which uses any of the
@@ -693,17 +901,31 @@ module ActiveRecord
693
901
  # User.joins(:posts).where("posts.published" => true)
694
902
  # User.joins(:posts).where(posts: { published: true })
695
903
  #
696
- # === no argument
904
+ # === No Argument
697
905
  #
698
906
  # If no argument is passed, #where returns a new instance of WhereChain, that
699
- # can be chained with #not to return a new relation that negates the where clause.
907
+ # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
908
+ #
909
+ # Chaining with WhereChain#not:
700
910
  #
701
911
  # User.where.not(name: "Jon")
702
912
  # # SELECT * FROM users WHERE name != 'Jon'
703
913
  #
704
- # See WhereChain for more details on #not.
914
+ # Chaining with WhereChain#associated:
705
915
  #
706
- # === blank condition
916
+ # Post.where.associated(:author)
917
+ # # SELECT "posts".* FROM "posts"
918
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
919
+ # # WHERE "authors"."id" IS NOT NULL
920
+ #
921
+ # Chaining with WhereChain#missing:
922
+ #
923
+ # Post.where.missing(:author)
924
+ # # SELECT "posts".* FROM "posts"
925
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
926
+ # # WHERE "authors"."id" IS NULL
927
+ #
928
+ # === Blank Condition
707
929
  #
708
930
  # If the condition is any blank-ish object, then #where is a no-op and returns
709
931
  # the current relation.
@@ -736,6 +958,8 @@ module ActiveRecord
736
958
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
737
959
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
738
960
  def rewhere(conditions)
961
+ return unscope(:where) if conditions.nil?
962
+
739
963
  scope = spawn
740
964
  where_clause = scope.build_where_clause(conditions)
741
965
 
@@ -841,7 +1065,11 @@ module ActiveRecord
841
1065
  #
842
1066
  def or(other)
843
1067
  if other.is_a?(Relation)
844
- spawn.or!(other)
1068
+ if @none
1069
+ other.spawn
1070
+ else
1071
+ spawn.or!(other)
1072
+ end
845
1073
  else
846
1074
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
847
1075
  end
@@ -954,15 +1182,29 @@ module ActiveRecord
954
1182
  end
955
1183
 
956
1184
  def none! # :nodoc:
957
- where!("1=0").extending!(NullRelation)
1185
+ unless @none
1186
+ where!("1=0")
1187
+ @none = true
1188
+ end
1189
+ self
958
1190
  end
959
1191
 
960
- # Sets readonly attributes for the returned relation. If value is
961
- # true (default), attempting to update a record will result in an error.
1192
+ def null_relation? # :nodoc:
1193
+ @none
1194
+ end
1195
+
1196
+ # Mark a relation as readonly. Attempting to update a record will result in
1197
+ # an error.
962
1198
  #
963
1199
  # users = User.readonly
964
1200
  # users.first.save
965
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
966
1208
  def readonly(value = true)
967
1209
  spawn.readonly!(value)
968
1210
  end
@@ -1079,7 +1321,7 @@ module ActiveRecord
1079
1321
  #
1080
1322
  # The object returned is a relation, which can be further extended.
1081
1323
  #
1082
- # === Using a module
1324
+ # === Using a \Module
1083
1325
  #
1084
1326
  # module Pagination
1085
1327
  # def page(number)
@@ -1094,7 +1336,7 @@ module ActiveRecord
1094
1336
  #
1095
1337
  # scope = Model.all.extending(Pagination, SomethingElse)
1096
1338
  #
1097
- # === Using a block
1339
+ # === Using a Block
1098
1340
  #
1099
1341
  # scope = Model.all.extending do
1100
1342
  # def page(number)
@@ -1181,6 +1423,8 @@ module ActiveRecord
1181
1423
  # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1182
1424
  #
1183
1425
  # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1426
+ #
1427
+ # Some escaping is performed, however untrusted user input should not be used.
1184
1428
  def annotate(*args)
1185
1429
  check_if_method_has_arguments!(__callee__, args)
1186
1430
  spawn.annotate!(*args)
@@ -1269,8 +1513,12 @@ module ActiveRecord
1269
1513
  parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1270
1514
  when Hash
1271
1515
  opts = opts.transform_keys do |key|
1272
- key = key.to_s
1273
- 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
1274
1522
  end
1275
1523
  references = PredicateBuilder.references(opts)
1276
1524
  self.references_values |= references unless references.empty?
@@ -1288,7 +1536,16 @@ module ActiveRecord
1288
1536
  end
1289
1537
  alias :build_having_clause :build_where_clause
1290
1538
 
1539
+ def async!
1540
+ @async = true
1541
+ self
1542
+ end
1543
+
1291
1544
  private
1545
+ def async
1546
+ spawn.async!
1547
+ end
1548
+
1292
1549
  def lookup_table_klass_from_join_dependencies(table_name)
1293
1550
  each_join_dependencies do |join|
1294
1551
  return join.base_klass if table_name == join.table_name
@@ -1303,13 +1560,13 @@ module ActiveRecord
1303
1560
  end
1304
1561
 
1305
1562
  def build_join_dependencies
1306
- associations = joins_values | left_outer_joins_values
1307
- associations |= eager_load_values unless eager_load_values.empty?
1308
- 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?
1309
1566
 
1310
1567
  join_dependencies = []
1311
1568
  join_dependencies.unshift construct_join_dependency(
1312
- select_association_list(associations, join_dependencies), nil
1569
+ select_named_joins(joins, join_dependencies), nil
1313
1570
  )
1314
1571
  end
1315
1572
 
@@ -1330,6 +1587,7 @@ module ActiveRecord
1330
1587
  arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1331
1588
 
1332
1589
  build_order(arel)
1590
+ build_with(arel)
1333
1591
  build_select(arel)
1334
1592
 
1335
1593
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
@@ -1365,6 +1623,18 @@ module ActiveRecord
1365
1623
  end
1366
1624
  end
1367
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
+
1368
1638
  def select_association_list(associations, stashed_joins = nil)
1369
1639
  result = []
1370
1640
  associations.each do |association|
@@ -1380,20 +1650,21 @@ module ActiveRecord
1380
1650
  result
1381
1651
  end
1382
1652
 
1383
- class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1384
- end
1385
-
1386
1653
  def build_join_buckets
1387
1654
  buckets = Hash.new { |h, k| h[k] = [] }
1388
1655
 
1389
1656
  unless left_outer_joins_values.empty?
1390
1657
  stashed_left_joins = []
1391
- left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1392
- 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
1393
1664
  end
1394
1665
 
1395
1666
  if joins_values.empty?
1396
- buckets[:association_join] = left_joins
1667
+ buckets[:named_join] = left_joins
1397
1668
  buckets[:stashed_join] = stashed_left_joins
1398
1669
  return buckets, Arel::Nodes::OuterJoin
1399
1670
  else
@@ -1419,9 +1690,11 @@ module ActiveRecord
1419
1690
  end
1420
1691
  end
1421
1692
 
1422
- 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|
1423
1694
  if join.is_a?(Arel::Nodes::Join)
1424
1695
  buckets[:join_node] << join
1696
+ elsif join.is_a?(CTEJoin)
1697
+ buckets[:join_node] << build_with_join_node(join.name)
1425
1698
  else
1426
1699
  raise "unknown class: %s" % join.class.name
1427
1700
  end
@@ -1438,16 +1711,16 @@ module ActiveRecord
1438
1711
 
1439
1712
  buckets, join_type = build_join_buckets
1440
1713
 
1441
- association_joins = buckets[:association_join]
1442
- stashed_joins = buckets[:stashed_join]
1443
- leading_joins = buckets[:leading_join]
1444
- 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]
1445
1718
 
1446
1719
  join_sources.concat(leading_joins) unless leading_joins.empty?
1447
1720
 
1448
- unless association_joins.empty? && stashed_joins.empty?
1721
+ unless named_joins.empty? && stashed_joins.empty?
1449
1722
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1450
- join_dependency = construct_join_dependency(association_joins, join_type)
1723
+ join_dependency = construct_join_dependency(named_joins, join_type)
1451
1724
  join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1452
1725
  end
1453
1726
 
@@ -1465,6 +1738,40 @@ module ActiveRecord
1465
1738
  end
1466
1739
  end
1467
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
+
1468
1775
  def arel_columns(columns)
1469
1776
  columns.flat_map do |field|
1470
1777
  case field
@@ -1583,7 +1890,7 @@ module ActiveRecord
1583
1890
  when Hash
1584
1891
  arg.map { |field, dir|
1585
1892
  case field
1586
- when Arel::Nodes::SqlLiteral
1893
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
1587
1894
  field.public_send(dir.downcase)
1588
1895
  else
1589
1896
  order_column(field.to_s).public_send(dir.downcase)
@@ -1607,7 +1914,9 @@ module ActiveRecord
1607
1914
  when String, Symbol
1608
1915
  arg
1609
1916
  when Hash
1610
- arg.keys
1917
+ arg.keys.map do |key|
1918
+ key if key.is_a?(String) || key.is_a?(Symbol)
1919
+ end
1611
1920
  end
1612
1921
  end
1613
1922
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
@@ -1624,6 +1933,15 @@ module ActiveRecord
1624
1933
  end
1625
1934
  end
1626
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
+
1627
1945
  def resolve_arel_attributes(attrs)
1628
1946
  attrs.flat_map do |attr|
1629
1947
  case attr
@@ -1675,6 +1993,41 @@ module ActiveRecord
1675
1993
  end
1676
1994
  end
1677
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
+
1678
2031
  STRUCTURAL_VALUE_METHODS = (
1679
2032
  Relation::VALUE_METHODS -
1680
2033
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]