activerecord 6.1.7 → 7.1.5

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 (311) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2030 -1020
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +51 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +39 -35
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  27. data/lib/active_record/associations/join_dependency.rb +28 -20
  28. data/lib/active_record/associations/preloader/association.rb +210 -52
  29. data/lib/active_record/associations/preloader/batch.rb +48 -0
  30. data/lib/active_record/associations/preloader/branch.rb +147 -0
  31. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  32. data/lib/active_record/associations/preloader.rb +50 -121
  33. data/lib/active_record/associations/singular_association.rb +9 -3
  34. data/lib/active_record/associations/through_association.rb +25 -14
  35. data/lib/active_record/associations.rb +446 -306
  36. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  37. data/lib/active_record/attribute_assignment.rb +1 -3
  38. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  39. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  40. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  41. data/lib/active_record/attribute_methods/query.rb +31 -19
  42. data/lib/active_record/attribute_methods/read.rb +27 -12
  43. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  45. data/lib/active_record/attribute_methods/write.rb +12 -15
  46. data/lib/active_record/attribute_methods.rb +161 -40
  47. data/lib/active_record/attributes.rb +27 -38
  48. data/lib/active_record/autosave_association.rb +65 -31
  49. data/lib/active_record/base.rb +25 -2
  50. data/lib/active_record/callbacks.rb +18 -34
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -46
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +113 -597
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -27
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +367 -141
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -150
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +317 -164
  70. data/lib/active_record/connection_adapters/column.rb +13 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +39 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +112 -55
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  89. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +397 -75
  101. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +508 -246
  103. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  104. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  105. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  106. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  107. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  108. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  109. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +296 -104
  110. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  111. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  112. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  113. data/lib/active_record/connection_adapters.rb +9 -6
  114. data/lib/active_record/connection_handling.rb +108 -137
  115. data/lib/active_record/core.rb +242 -233
  116. data/lib/active_record/counter_cache.rb +52 -27
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -2
  118. data/lib/active_record/database_configurations/database_config.rb +21 -12
  119. data/lib/active_record/database_configurations/hash_config.rb +88 -16
  120. data/lib/active_record/database_configurations/url_config.rb +18 -12
  121. data/lib/active_record/database_configurations.rb +95 -59
  122. data/lib/active_record/delegated_type.rb +66 -20
  123. data/lib/active_record/deprecator.rb +7 -0
  124. data/lib/active_record/destroy_association_async_job.rb +4 -2
  125. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  126. data/lib/active_record/dynamic_matchers.rb +1 -1
  127. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  128. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  129. data/lib/active_record/encryption/cipher.rb +53 -0
  130. data/lib/active_record/encryption/config.rb +68 -0
  131. data/lib/active_record/encryption/configurable.rb +60 -0
  132. data/lib/active_record/encryption/context.rb +42 -0
  133. data/lib/active_record/encryption/contexts.rb +76 -0
  134. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  135. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  136. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  137. data/lib/active_record/encryption/encrypted_attribute_type.rb +155 -0
  138. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  139. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  140. data/lib/active_record/encryption/encryptor.rb +155 -0
  141. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  142. data/lib/active_record/encryption/errors.rb +15 -0
  143. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  144. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  145. data/lib/active_record/encryption/key.rb +28 -0
  146. data/lib/active_record/encryption/key_generator.rb +53 -0
  147. data/lib/active_record/encryption/key_provider.rb +46 -0
  148. data/lib/active_record/encryption/message.rb +33 -0
  149. data/lib/active_record/encryption/message_serializer.rb +92 -0
  150. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  151. data/lib/active_record/encryption/properties.rb +76 -0
  152. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  153. data/lib/active_record/encryption/scheme.rb +100 -0
  154. data/lib/active_record/encryption.rb +58 -0
  155. data/lib/active_record/enum.rb +154 -63
  156. data/lib/active_record/errors.rb +172 -15
  157. data/lib/active_record/explain.rb +23 -3
  158. data/lib/active_record/explain_registry.rb +11 -6
  159. data/lib/active_record/explain_subscriber.rb +1 -1
  160. data/lib/active_record/fixture_set/file.rb +15 -1
  161. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  162. data/lib/active_record/fixture_set/render_context.rb +2 -0
  163. data/lib/active_record/fixture_set/table_row.rb +70 -14
  164. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  165. data/lib/active_record/fixtures.rb +147 -86
  166. data/lib/active_record/future_result.rb +174 -0
  167. data/lib/active_record/gem_version.rb +3 -3
  168. data/lib/active_record/inheritance.rb +81 -29
  169. data/lib/active_record/insert_all.rb +135 -22
  170. data/lib/active_record/integration.rb +11 -10
  171. data/lib/active_record/internal_metadata.rb +119 -33
  172. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  173. data/lib/active_record/locking/optimistic.rb +37 -22
  174. data/lib/active_record/locking/pessimistic.rb +15 -6
  175. data/lib/active_record/log_subscriber.rb +52 -19
  176. data/lib/active_record/marshalling.rb +59 -0
  177. data/lib/active_record/message_pack.rb +124 -0
  178. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  179. data/lib/active_record/middleware/database_selector.rb +23 -13
  180. data/lib/active_record/middleware/shard_selector.rb +62 -0
  181. data/lib/active_record/migration/command_recorder.rb +112 -14
  182. data/lib/active_record/migration/compatibility.rb +233 -46
  183. data/lib/active_record/migration/default_strategy.rb +23 -0
  184. data/lib/active_record/migration/execution_strategy.rb +19 -0
  185. data/lib/active_record/migration/join_table.rb +1 -1
  186. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  187. data/lib/active_record/migration.rb +361 -173
  188. data/lib/active_record/model_schema.rb +125 -101
  189. data/lib/active_record/nested_attributes.rb +50 -20
  190. data/lib/active_record/no_touching.rb +3 -3
  191. data/lib/active_record/normalization.rb +167 -0
  192. data/lib/active_record/persistence.rb +409 -88
  193. data/lib/active_record/promise.rb +84 -0
  194. data/lib/active_record/query_cache.rb +4 -22
  195. data/lib/active_record/query_logs.rb +174 -0
  196. data/lib/active_record/query_logs_formatter.rb +41 -0
  197. data/lib/active_record/querying.rb +29 -6
  198. data/lib/active_record/railtie.rb +220 -44
  199. data/lib/active_record/railties/controller_runtime.rb +15 -10
  200. data/lib/active_record/railties/databases.rake +188 -252
  201. data/lib/active_record/railties/job_runtime.rb +23 -0
  202. data/lib/active_record/readonly_attributes.rb +41 -3
  203. data/lib/active_record/reflection.rb +248 -81
  204. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  205. data/lib/active_record/relation/batches.rb +192 -63
  206. data/lib/active_record/relation/calculations.rb +246 -90
  207. data/lib/active_record/relation/delegation.rb +28 -14
  208. data/lib/active_record/relation/finder_methods.rb +108 -51
  209. data/lib/active_record/relation/merger.rb +22 -13
  210. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  211. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  212. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  213. data/lib/active_record/relation/predicate_builder.rb +27 -20
  214. data/lib/active_record/relation/query_attribute.rb +30 -12
  215. data/lib/active_record/relation/query_methods.rb +670 -129
  216. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  217. data/lib/active_record/relation/spawn_methods.rb +20 -3
  218. data/lib/active_record/relation/where_clause.rb +10 -19
  219. data/lib/active_record/relation.rb +287 -120
  220. data/lib/active_record/result.rb +37 -11
  221. data/lib/active_record/runtime_registry.rb +32 -13
  222. data/lib/active_record/sanitization.rb +65 -20
  223. data/lib/active_record/schema.rb +36 -22
  224. data/lib/active_record/schema_dumper.rb +73 -24
  225. data/lib/active_record/schema_migration.rb +68 -33
  226. data/lib/active_record/scoping/default.rb +72 -15
  227. data/lib/active_record/scoping/named.rb +5 -13
  228. data/lib/active_record/scoping.rb +65 -34
  229. data/lib/active_record/secure_password.rb +60 -0
  230. data/lib/active_record/secure_token.rb +21 -3
  231. data/lib/active_record/serialization.rb +6 -1
  232. data/lib/active_record/signed_id.rb +10 -8
  233. data/lib/active_record/store.rb +10 -10
  234. data/lib/active_record/suppressor.rb +13 -15
  235. data/lib/active_record/table_metadata.rb +16 -3
  236. data/lib/active_record/tasks/database_tasks.rb +251 -140
  237. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  238. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  239. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  240. data/lib/active_record/test_databases.rb +1 -1
  241. data/lib/active_record/test_fixtures.rb +117 -96
  242. data/lib/active_record/timestamp.rb +32 -19
  243. data/lib/active_record/token_for.rb +113 -0
  244. data/lib/active_record/touch_later.rb +11 -6
  245. data/lib/active_record/transactions.rb +48 -27
  246. data/lib/active_record/translation.rb +3 -3
  247. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  248. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  249. data/lib/active_record/type/internal/timezone.rb +7 -2
  250. data/lib/active_record/type/serialized.rb +9 -5
  251. data/lib/active_record/type/time.rb +4 -0
  252. data/lib/active_record/type/type_map.rb +17 -20
  253. data/lib/active_record/type.rb +1 -2
  254. data/lib/active_record/validations/absence.rb +1 -1
  255. data/lib/active_record/validations/associated.rb +4 -4
  256. data/lib/active_record/validations/numericality.rb +5 -4
  257. data/lib/active_record/validations/presence.rb +5 -28
  258. data/lib/active_record/validations/uniqueness.rb +51 -6
  259. data/lib/active_record/validations.rb +8 -4
  260. data/lib/active_record/version.rb +1 -1
  261. data/lib/active_record.rb +335 -32
  262. data/lib/arel/attributes/attribute.rb +0 -8
  263. data/lib/arel/crud.rb +28 -22
  264. data/lib/arel/delete_manager.rb +18 -4
  265. data/lib/arel/errors.rb +10 -0
  266. data/lib/arel/factory_methods.rb +4 -0
  267. data/lib/arel/filter_predications.rb +9 -0
  268. data/lib/arel/insert_manager.rb +2 -3
  269. data/lib/arel/nodes/and.rb +4 -0
  270. data/lib/arel/nodes/binary.rb +6 -1
  271. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  272. data/lib/arel/nodes/casted.rb +1 -1
  273. data/lib/arel/nodes/cte.rb +36 -0
  274. data/lib/arel/nodes/delete_statement.rb +12 -13
  275. data/lib/arel/nodes/filter.rb +10 -0
  276. data/lib/arel/nodes/fragments.rb +35 -0
  277. data/lib/arel/nodes/function.rb +1 -0
  278. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  279. data/lib/arel/nodes/insert_statement.rb +2 -2
  280. data/lib/arel/nodes/leading_join.rb +8 -0
  281. data/lib/arel/nodes/node.rb +111 -2
  282. data/lib/arel/nodes/select_core.rb +2 -2
  283. data/lib/arel/nodes/select_statement.rb +2 -2
  284. data/lib/arel/nodes/sql_literal.rb +6 -0
  285. data/lib/arel/nodes/table_alias.rb +4 -0
  286. data/lib/arel/nodes/update_statement.rb +8 -3
  287. data/lib/arel/nodes.rb +5 -0
  288. data/lib/arel/predications.rb +13 -3
  289. data/lib/arel/select_manager.rb +10 -4
  290. data/lib/arel/table.rb +9 -6
  291. data/lib/arel/tree_manager.rb +5 -13
  292. data/lib/arel/update_manager.rb +18 -4
  293. data/lib/arel/visitors/dot.rb +80 -90
  294. data/lib/arel/visitors/mysql.rb +16 -3
  295. data/lib/arel/visitors/postgresql.rb +0 -10
  296. data/lib/arel/visitors/to_sql.rb +141 -20
  297. data/lib/arel/visitors/visitor.rb +2 -2
  298. data/lib/arel.rb +18 -3
  299. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  300. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/migration.rb +3 -1
  302. data/lib/rails/generators/active_record/model/USAGE +113 -0
  303. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  304. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  305. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  306. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  307. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  308. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  309. metadata +96 -16
  310. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  311. data/lib/active_record/null_relation.rb +0 -67
@@ -3,19 +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
- extend ActiveSupport::Concern
12
-
13
10
  include ActiveModel::ForbiddenAttributesProtection
14
11
 
15
- # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
16
- # 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.
17
14
  class WhereChain
18
- def initialize(scope)
15
+ def initialize(scope) # :nodoc:
19
16
  @scope = scope
20
17
  end
21
18
 
@@ -41,7 +38,14 @@ module ActiveRecord
41
38
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
42
39
  #
43
40
  # User.where.not(name: "Jon", role: "admin")
44
- # # 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
+ # # => []
45
49
  def not(opts, *rest)
46
50
  where_clause = @scope.send(:build_where_clause, opts, rest)
47
51
 
@@ -50,6 +54,38 @@ module ActiveRecord
50
54
  @scope
51
55
  end
52
56
 
57
+ # Returns a new relation with joins and where clause to identify
58
+ # associated relations.
59
+ #
60
+ # For example, posts that are associated to a related author:
61
+ #
62
+ # Post.where.associated(:author)
63
+ # # SELECT "posts".* FROM "posts"
64
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
65
+ # # WHERE "authors"."id" IS NOT NULL
66
+ #
67
+ # Additionally, multiple relations can be combined. This will return posts
68
+ # associated to both an author and any comments:
69
+ #
70
+ # Post.where.associated(:author, :comments)
71
+ # # SELECT "posts".* FROM "posts"
72
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
73
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
74
+ # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
75
+ def associated(*associations)
76
+ associations.each do |association|
77
+ reflection = scope_association_reflection(association)
78
+ @scope.joins!(association)
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
84
+ end
85
+
86
+ @scope
87
+ end
88
+
53
89
  # Returns a new relation with left outer joins and where clause to identify
54
90
  # missing relations.
55
91
  #
@@ -68,16 +104,37 @@ module ActiveRecord
68
104
  # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
69
105
  # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
70
106
  # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
71
- def missing(*args)
72
- args.each do |arg|
73
- reflection = @scope.klass._reflect_on_association(arg)
74
- opts = { reflection.table_name => { reflection.association_primary_key => nil } }
75
- @scope.left_outer_joins!(arg)
76
- @scope.where!(opts)
107
+ def missing(*associations)
108
+ associations.each do |association|
109
+ reflection = scope_association_reflection(association)
110
+ @scope.left_outer_joins!(association)
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
77
116
  end
78
117
 
79
118
  @scope
80
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
81
138
  end
82
139
 
83
140
  FROZEN_EMPTY_ARRAY = [].freeze
@@ -108,47 +165,71 @@ module ActiveRecord
108
165
 
109
166
  alias extensions extending_values
110
167
 
111
- # Specify relationships to be included in the result set. For
112
- # 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:
113
173
  #
114
- # users = User.includes(:address)
174
+ # users = User.includes(:address).limit(5)
115
175
  # users.each do |user|
116
176
  # user.address.city
117
177
  # end
118
178
  #
119
- # allows you to access the +address+ attribute of the +User+ model without
120
- # firing an additional query. This will often result in a
121
- # 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)
122
181
  #
123
- # You can also specify multiple relationships, like this:
182
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
183
+ # are loaded with a single query.
124
184
  #
125
- # users = User.includes(:address, :friends)
185
+ # Loading the associations in a separate query will often result in a
186
+ # performance improvement over a simple join, as a join can result in many
187
+ # rows that contain redundant data and it performs poorly at scale.
126
188
  #
127
- # Loading nested relationships is possible using a Hash:
189
+ # You can also specify multiple associations. Each association will result
190
+ # in an additional query:
128
191
  #
129
- # users = User.includes(:address, friends: [:address, :followers])
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)
130
196
  #
131
- # === conditions
197
+ # Loading nested associations is possible using a Hash:
198
+ #
199
+ # User.includes(:address, friends: [:address, :followers])
200
+ #
201
+ # === Conditions
132
202
  #
133
203
  # If you want to add string conditions to your included models, you'll have
134
204
  # to explicitly reference them. For example:
135
205
  #
136
- # User.includes(:posts).where('posts.name = ?', 'example')
206
+ # User.includes(:posts).where('posts.name = ?', 'example').to_a
137
207
  #
138
208
  # Will throw an error, but this will work:
139
209
  #
140
- # 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.
141
217
  #
142
218
  # Note that #includes works with association names while #references needs
143
219
  # the actual table name.
144
220
  #
145
- # 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
146
222
  # explicitly, as #where references the tables for you. For example, this
147
223
  # will work correctly:
148
224
  #
149
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.
150
231
  def includes(*args)
151
- check_if_method_has_arguments!(:includes, args)
232
+ check_if_method_has_arguments!(__callee__, args)
152
233
  spawn.includes!(*args)
153
234
  end
154
235
 
@@ -157,14 +238,34 @@ module ActiveRecord
157
238
  self
158
239
  end
159
240
 
160
- # 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:
161
243
  #
162
- # User.eager_load(:posts)
163
- # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
164
- # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
165
- # # "users"."id"
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.
255
+ #
256
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
257
+ # similar to #includes:
258
+ #
259
+ # User.eager_load(:address, friends: [:address, :followers])
260
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
261
+ # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
262
+ # # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
263
+ # # ...
264
+ #
265
+ # NOTE: Loading the associations in a join can result in many rows that
266
+ # contain redundant data and it performs poorly at scale.
166
267
  def eager_load(*args)
167
- check_if_method_has_arguments!(:eager_load, args)
268
+ check_if_method_has_arguments!(__callee__, args)
168
269
  spawn.eager_load!(*args)
169
270
  end
170
271
 
@@ -173,12 +274,30 @@ module ActiveRecord
173
274
  self
174
275
  end
175
276
 
176
- # 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.
177
279
  #
178
- # User.preload(:posts)
179
- # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
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.
290
+ #
291
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
292
+ # similar to #includes:
293
+ #
294
+ # User.preload(:address, friends: [:address, :followers])
295
+ # # SELECT "users".* FROM "users"
296
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
297
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
298
+ # # SELECT ...
180
299
  def preload(*args)
181
- check_if_method_has_arguments!(:preload, args)
300
+ check_if_method_has_arguments!(__callee__, args)
182
301
  spawn.preload!(*args)
183
302
  end
184
303
 
@@ -201,7 +320,7 @@ module ActiveRecord
201
320
  end
202
321
 
203
322
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
204
- # 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.
205
324
  # This method only works in conjunction with #includes.
206
325
  # See #includes for more details.
207
326
  #
@@ -211,7 +330,7 @@ module ActiveRecord
211
330
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
212
331
  # # Query now knows the string references posts, so adds a JOIN
213
332
  def references(*table_names)
214
- check_if_method_has_arguments!(:references, table_names)
333
+ check_if_method_has_arguments!(__callee__, table_names)
215
334
  spawn.references!(*table_names)
216
335
  end
217
336
 
@@ -245,10 +364,18 @@ module ActiveRecord
245
364
  # Model.select(:field, :other_field, :and_one_more)
246
365
  # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
247
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
+ #
248
375
  # You can also use one or more strings, which will be used unchanged as SELECT fields.
249
376
  #
250
377
  # Model.select('field AS field_one', 'other_field AS field_two')
251
- # # => [#<Model id: nil, field: "value", other_field: "value">]
378
+ # # => [#<Model id: nil, field_one: "value", field_two: "value">]
252
379
  #
253
380
  # If an alias was specified, it will be accessible from the resulting objects:
254
381
  #
@@ -259,7 +386,7 @@ module ActiveRecord
259
386
  # except +id+ will throw ActiveModel::MissingAttributeError:
260
387
  #
261
388
  # Model.select(:field).first.other_field
262
- # # => ActiveModel::MissingAttributeError: missing attribute: other_field
389
+ # # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
263
390
  def select(*fields)
264
391
  if block_given?
265
392
  if fields.any?
@@ -269,7 +396,9 @@ module ActiveRecord
269
396
  return super()
270
397
  end
271
398
 
272
- check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
399
+ check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
400
+
401
+ fields = process_select_args(fields)
273
402
  spawn._select!(*fields)
274
403
  end
275
404
 
@@ -278,6 +407,66 @@ module ActiveRecord
278
407
  self
279
408
  end
280
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
+
281
470
  # Allows you to change a previously set select statement.
282
471
  #
283
472
  # Post.select(:title, :body)
@@ -289,7 +478,8 @@ module ActiveRecord
289
478
  # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
290
479
  # Note that we're unscoping the entire select statement.
291
480
  def reselect(*args)
292
- check_if_method_has_arguments!(:reselect, args)
481
+ check_if_method_has_arguments!(__callee__, args)
482
+ args = process_select_args(args)
293
483
  spawn.reselect!(*args)
294
484
  end
295
485
 
@@ -320,7 +510,7 @@ module ActiveRecord
320
510
  # User.select([:id, :first_name]).group(:id, :first_name).first(3)
321
511
  # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
322
512
  def group(*args)
323
- check_if_method_has_arguments!(:group, args)
513
+ check_if_method_has_arguments!(__callee__, args)
324
514
  spawn.group!(*args)
325
515
  end
326
516
 
@@ -329,17 +519,58 @@ module ActiveRecord
329
519
  self
330
520
  end
331
521
 
332
- # Allows to specify an order attribute:
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
+
543
+ # Applies an <code>ORDER BY</code> clause to a query.
544
+ #
545
+ # #order accepts arguments in one of several formats.
546
+ #
547
+ # === symbols
548
+ #
549
+ # The symbol represents the name of the column you want to order the results by.
333
550
  #
334
551
  # User.order(:name)
335
552
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
336
553
  #
554
+ # By default, the order is ascending. If you want descending order, you can
555
+ # map the column name symbol to +:desc+.
556
+ #
337
557
  # User.order(email: :desc)
338
558
  # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
339
559
  #
560
+ # Multiple columns can be passed this way, and they will be applied in the order specified.
561
+ #
340
562
  # User.order(:name, email: :desc)
341
563
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
342
564
  #
565
+ # === strings
566
+ #
567
+ # Strings are passed directly to the database, allowing you to specify
568
+ # simple SQL expressions.
569
+ #
570
+ # This could be a source of SQL injection, so only strings composed of plain
571
+ # column names and simple <code>function(column_name)</code> expressions
572
+ # with optional +ASC+/+DESC+ modifiers are allowed.
573
+ #
343
574
  # User.order('name')
344
575
  # # SELECT "users".* FROM "users" ORDER BY name
345
576
  #
@@ -348,8 +579,21 @@ module ActiveRecord
348
579
  #
349
580
  # User.order('name DESC, email')
350
581
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
582
+ #
583
+ # === Arel
584
+ #
585
+ # If you need to pass in complicated expressions that you have verified
586
+ # are safe for the database, you can use Arel.
587
+ #
588
+ # User.order(Arel.sql('end_date - start_date'))
589
+ # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
590
+ #
591
+ # Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
592
+ #
593
+ # User.order(Arel.sql("payload->>'kind'"))
594
+ # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
351
595
  def order(*args)
352
- check_if_method_has_arguments!(:order, args) do
596
+ check_if_method_has_arguments!(__callee__, args) do
353
597
  sanitize_order_arguments(args)
354
598
  end
355
599
  spawn.order!(*args)
@@ -362,6 +606,39 @@ module ActiveRecord
362
606
  self
363
607
  end
364
608
 
609
+ # Allows to specify an order by a specific set of values.
610
+ #
611
+ # User.in_order_of(: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
619
+ #
620
+ def in_order_of(column, values)
621
+ klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
622
+ return spawn.none! if values.empty?
623
+
624
+ references = column_references([column])
625
+ self.references_values |= references unless references.empty?
626
+
627
+ values = values.map { |value| type_caster.type_cast_for_database(column, value) }
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
636
+
637
+ spawn
638
+ .order!(build_case_for_value_position(arel_column, values))
639
+ .where!(where_clause)
640
+ end
641
+
365
642
  # Replaces any existing order defined on the relation with the specified order.
366
643
  #
367
644
  # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
@@ -370,17 +647,17 @@ module ActiveRecord
370
647
  #
371
648
  # User.order('email DESC').reorder('id ASC').order('name ASC')
372
649
  #
373
- # generates a query with 'ORDER BY id ASC, name ASC'.
650
+ # generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
374
651
  def reorder(*args)
375
- check_if_method_has_arguments!(:reorder, args) do
376
- sanitize_order_arguments(args) unless args.all?(&:blank?)
652
+ check_if_method_has_arguments!(__callee__, args) do
653
+ sanitize_order_arguments(args)
377
654
  end
378
655
  spawn.reorder!(*args)
379
656
  end
380
657
 
381
658
  # Same as #reorder but operates on relation in-place instead of copying.
382
659
  def reorder!(*args) # :nodoc:
383
- preprocess_order_args(args) unless args.all?(&:blank?)
660
+ preprocess_order_args(args)
384
661
  args.uniq!
385
662
  self.reordering_value = true
386
663
  self.order_values = args
@@ -389,7 +666,8 @@ module ActiveRecord
389
666
 
390
667
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
391
668
  :limit, :offset, :joins, :left_outer_joins, :annotate,
392
- :includes, :from, :readonly, :having, :optimizer_hints])
669
+ :includes, :eager_load, :preload, :from, :readonly,
670
+ :having, :optimizer_hints])
393
671
 
394
672
  # Removes an unwanted relation that is already defined on a chain of relations.
395
673
  # This is useful when passing around chains of relations and would like to
@@ -425,7 +703,7 @@ module ActiveRecord
425
703
  # has_many :comments, -> { unscope(where: :trashed) }
426
704
  #
427
705
  def unscope(*args)
428
- check_if_method_has_arguments!(:unscope, args)
706
+ check_if_method_has_arguments!(__callee__, args)
429
707
  spawn.unscope!(*args)
430
708
  end
431
709
 
@@ -458,7 +736,7 @@ module ActiveRecord
458
736
  self
459
737
  end
460
738
 
461
- # Performs a joins on +args+. The given symbol(s) should match the name of
739
+ # Performs JOINs on +args+. The given symbol(s) should match the name of
462
740
  # the association(s).
463
741
  #
464
742
  # User.joins(:posts)
@@ -487,7 +765,7 @@ module ActiveRecord
487
765
  # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
488
766
  # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
489
767
  def joins(*args)
490
- check_if_method_has_arguments!(:joins, args)
768
+ check_if_method_has_arguments!(__callee__, args)
491
769
  spawn.joins!(*args)
492
770
  end
493
771
 
@@ -496,10 +774,10 @@ module ActiveRecord
496
774
  self
497
775
  end
498
776
 
499
- # Performs a left outer joins on +args+:
777
+ # Performs LEFT OUTER JOINs on +args+:
500
778
  #
501
779
  # User.left_outer_joins(:posts)
502
- # => 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"
503
781
  #
504
782
  def left_outer_joins(*args)
505
783
  check_if_method_has_arguments!(__callee__, args)
@@ -519,7 +797,7 @@ module ActiveRecord
519
797
  # SQL is given as an illustration; the actual query generated may be different depending
520
798
  # on the database adapter.
521
799
  #
522
- # === string
800
+ # === \String
523
801
  #
524
802
  # A single string, without additional arguments, is passed to the query
525
803
  # constructor as an SQL fragment, and used in the where clause of the query.
@@ -531,7 +809,7 @@ module ActiveRecord
531
809
  # to injection attacks if not done properly. As an alternative, it is recommended
532
810
  # to use one of the following methods.
533
811
  #
534
- # === array
812
+ # === \Array
535
813
  #
536
814
  # If an array is passed, then the first element of the array is treated as a template, and
537
815
  # the remaining elements are inserted into the template to generate the condition.
@@ -571,20 +849,20 @@ module ActiveRecord
571
849
  # dependencies on the underlying database. If your code is intended for general consumption,
572
850
  # test with multiple database backends.
573
851
  #
574
- # === hash
852
+ # === \Hash
575
853
  #
576
854
  # #where will also accept a hash condition, in which the keys are fields and the values
577
855
  # are values to be searched for.
578
856
  #
579
857
  # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
580
858
  #
581
- # User.where({ name: "Joe", email: "joe@example.com" })
859
+ # User.where(name: "Joe", email: "joe@example.com")
582
860
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
583
861
  #
584
- # User.where({ name: ["Alice", "Bob"]})
862
+ # User.where(name: ["Alice", "Bob"])
585
863
  # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
586
864
  #
587
- # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
865
+ # User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
588
866
  # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
589
867
  #
590
868
  # In the case of a belongs_to relationship, an association key can be used
@@ -605,6 +883,12 @@ module ActiveRecord
605
883
  # PriceEstimate.where(estimate_of: treasure)
606
884
  # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
607
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
+ #
608
892
  # === Joins
609
893
  #
610
894
  # If the relation is the result of a join, you may create a condition which uses any of the
@@ -614,20 +898,34 @@ module ActiveRecord
614
898
  #
615
899
  # For hash conditions, you can either use the table name in the key, or use a sub-hash.
616
900
  #
617
- # User.joins(:posts).where({ "posts.published" => true })
618
- # User.joins(:posts).where({ posts: { published: true } })
901
+ # User.joins(:posts).where("posts.published" => true)
902
+ # User.joins(:posts).where(posts: { published: true })
619
903
  #
620
- # === no argument
904
+ # === No Argument
621
905
  #
622
906
  # If no argument is passed, #where returns a new instance of WhereChain, that
623
- # 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:
624
910
  #
625
911
  # User.where.not(name: "Jon")
626
912
  # # SELECT * FROM users WHERE name != 'Jon'
627
913
  #
628
- # See WhereChain for more details on #not.
914
+ # Chaining with WhereChain#associated:
915
+ #
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:
629
922
  #
630
- # === blank condition
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
631
929
  #
632
930
  # If the condition is any blank-ish object, then #where is a no-op and returns
633
931
  # the current relation.
@@ -660,6 +958,8 @@ module ActiveRecord
660
958
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
661
959
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
662
960
  def rewhere(conditions)
961
+ return unscope(:where) if conditions.nil?
962
+
663
963
  scope = spawn
664
964
  where_clause = scope.build_where_clause(conditions)
665
965
 
@@ -668,6 +968,59 @@ module ActiveRecord
668
968
  scope
669
969
  end
670
970
 
971
+ # Allows you to invert an entire where clause instead of manually applying conditions.
972
+ #
973
+ # class User
974
+ # scope :active, -> { where(accepted: true, locked: false) }
975
+ # end
976
+ #
977
+ # User.where(accepted: true)
978
+ # # WHERE `accepted` = 1
979
+ #
980
+ # User.where(accepted: true).invert_where
981
+ # # WHERE `accepted` != 1
982
+ #
983
+ # User.active
984
+ # # WHERE `accepted` = 1 AND `locked` = 0
985
+ #
986
+ # User.active.invert_where
987
+ # # WHERE NOT (`accepted` = 1 AND `locked` = 0)
988
+ #
989
+ # Be careful because this inverts all conditions before +invert_where+ call.
990
+ #
991
+ # class User
992
+ # scope :active, -> { where(accepted: true, locked: false) }
993
+ # scope :inactive, -> { active.invert_where } # Do not attempt it
994
+ # end
995
+ #
996
+ # # It also inverts `where(role: 'admin')` unexpectedly.
997
+ # User.where(role: 'admin').inactive
998
+ # # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
999
+ #
1000
+ def invert_where
1001
+ spawn.invert_where!
1002
+ end
1003
+
1004
+ def invert_where! # :nodoc:
1005
+ self.where_clause = where_clause.invert
1006
+ self
1007
+ end
1008
+
1009
+ # Checks whether the given relation is structurally compatible with this relation, to determine
1010
+ # if it's possible to use the #and and #or methods without raising an error. Structurally
1011
+ # compatible is defined as: they must be scoping the same model, and they must differ only by
1012
+ # #where (if no #group has been defined) or #having (if a #group is present).
1013
+ #
1014
+ # Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
1015
+ # # => true
1016
+ #
1017
+ # Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
1018
+ # # => false
1019
+ #
1020
+ def structurally_compatible?(other)
1021
+ structurally_incompatible_values_for(other).empty?
1022
+ end
1023
+
671
1024
  # Returns a new relation, which is the logical intersection of this relation and the one passed
672
1025
  # as an argument.
673
1026
  #
@@ -712,7 +1065,11 @@ module ActiveRecord
712
1065
  #
713
1066
  def or(other)
714
1067
  if other.is_a?(Relation)
715
- spawn.or!(other)
1068
+ if @none
1069
+ other.spawn
1070
+ else
1071
+ spawn.or!(other)
1072
+ end
716
1073
  else
717
1074
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
718
1075
  end
@@ -825,15 +1182,29 @@ module ActiveRecord
825
1182
  end
826
1183
 
827
1184
  def none! # :nodoc:
828
- where!("1=0").extending!(NullRelation)
1185
+ unless @none
1186
+ where!("1=0")
1187
+ @none = true
1188
+ end
1189
+ self
829
1190
  end
830
1191
 
831
- # Sets readonly attributes for the returned relation. If value is
832
- # 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.
833
1198
  #
834
1199
  # users = User.readonly
835
1200
  # users.first.save
836
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
837
1208
  def readonly(value = true)
838
1209
  spawn.readonly!(value)
839
1210
  end
@@ -886,7 +1257,7 @@ module ActiveRecord
886
1257
  self
887
1258
  end
888
1259
 
889
- # Specifies table from which the records will be fetched. For example:
1260
+ # Specifies the table from which the records will be fetched. For example:
890
1261
  #
891
1262
  # Topic.select('title').from('posts')
892
1263
  # # SELECT title FROM posts
@@ -896,9 +1267,26 @@ module ActiveRecord
896
1267
  # Topic.select('title').from(Topic.approved)
897
1268
  # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
898
1269
  #
1270
+ # Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
1271
+ #
899
1272
  # Topic.select('a.title').from(Topic.approved, :a)
900
1273
  # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
901
1274
  #
1275
+ # It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
1276
+ #
1277
+ # Topic.select('title').from(Topic.approved).from(Topic.inactive)
1278
+ # # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
1279
+ #
1280
+ # For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
1281
+ #
1282
+ # color = "red"
1283
+ # Color
1284
+ # .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
1285
+ # .where("colorvalue->>'color' = ?", color)
1286
+ # .select("c.*").to_a
1287
+ # # SELECT c.*
1288
+ # # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
1289
+ # # WHERE (colorvalue->>'color' = 'red')
902
1290
  def from(value, subquery_name = nil)
903
1291
  spawn.from!(value, subquery_name)
904
1292
  end
@@ -933,7 +1321,7 @@ module ActiveRecord
933
1321
  #
934
1322
  # The object returned is a relation, which can be further extended.
935
1323
  #
936
- # === Using a module
1324
+ # === Using a \Module
937
1325
  #
938
1326
  # module Pagination
939
1327
  # def page(number)
@@ -948,7 +1336,7 @@ module ActiveRecord
948
1336
  #
949
1337
  # scope = Model.all.extending(Pagination, SomethingElse)
950
1338
  #
951
- # === Using a block
1339
+ # === Using a Block
952
1340
  #
953
1341
  # scope = Model.all.extending do
954
1342
  # def page(number)
@@ -994,7 +1382,7 @@ module ActiveRecord
994
1382
  # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
995
1383
  # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
996
1384
  def optimizer_hints(*args)
997
- check_if_method_has_arguments!(:optimizer_hints, args)
1385
+ check_if_method_has_arguments!(__callee__, args)
998
1386
  spawn.optimizer_hints!(*args)
999
1387
  end
1000
1388
 
@@ -1035,8 +1423,10 @@ module ActiveRecord
1035
1423
  # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1036
1424
  #
1037
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.
1038
1428
  def annotate(*args)
1039
- check_if_method_has_arguments!(:annotate, args)
1429
+ check_if_method_has_arguments!(__callee__, args)
1040
1430
  spawn.annotate!(*args)
1041
1431
  end
1042
1432
 
@@ -1054,6 +1444,47 @@ module ActiveRecord
1054
1444
  self
1055
1445
  end
1056
1446
 
1447
+ # Excludes the specified record (or collection of records) from the resulting
1448
+ # relation. For example:
1449
+ #
1450
+ # Post.excluding(post)
1451
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
1452
+ #
1453
+ # Post.excluding(post_one, post_two)
1454
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1455
+ #
1456
+ # This can also be called on associations. As with the above example, either
1457
+ # a single record of collection thereof may be specified:
1458
+ #
1459
+ # post = Post.find(1)
1460
+ # comment = Comment.find(2)
1461
+ # post.comments.excluding(comment)
1462
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
1463
+ #
1464
+ # This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
1465
+ #
1466
+ # An <tt>ArgumentError</tt> will be raised if either no records are
1467
+ # specified, or if any of the records in the collection (if a collection
1468
+ # is passed in) are not instances of the same model that the relation is
1469
+ # scoping.
1470
+ def excluding(*records)
1471
+ records.flatten!(1)
1472
+ records.compact!
1473
+
1474
+ unless records.all?(klass)
1475
+ raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1476
+ end
1477
+
1478
+ spawn.excluding!(records)
1479
+ end
1480
+ alias :without :excluding
1481
+
1482
+ def excluding!(records) # :nodoc:
1483
+ predicates = [ predicate_builder[primary_key, records].invert ]
1484
+ self.where_clause += Relation::WhereClause.new(predicates)
1485
+ self
1486
+ end
1487
+
1057
1488
  # Returns the Arel object associated with the relation.
1058
1489
  def arel(aliases = nil) # :nodoc:
1059
1490
  @arel ||= build_arel(aliases)
@@ -1082,8 +1513,12 @@ module ActiveRecord
1082
1513
  parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1083
1514
  when Hash
1084
1515
  opts = opts.transform_keys do |key|
1085
- key = key.to_s
1086
- 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
1087
1522
  end
1088
1523
  references = PredicateBuilder.references(opts)
1089
1524
  self.references_values |= references unless references.empty?
@@ -1101,7 +1536,16 @@ module ActiveRecord
1101
1536
  end
1102
1537
  alias :build_having_clause :build_where_clause
1103
1538
 
1539
+ def async!
1540
+ @async = true
1541
+ self
1542
+ end
1543
+
1104
1544
  private
1545
+ def async
1546
+ spawn.async!
1547
+ end
1548
+
1105
1549
  def lookup_table_klass_from_join_dependencies(table_name)
1106
1550
  each_join_dependencies do |join|
1107
1551
  return join.base_klass if table_name == join.table_name
@@ -1109,22 +1553,20 @@ module ActiveRecord
1109
1553
  nil
1110
1554
  end
1111
1555
 
1112
- def each_join_dependencies(join_dependencies = build_join_dependencies)
1556
+ def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
1113
1557
  join_dependencies.each do |join_dependency|
1114
- join_dependency.each do |join|
1115
- yield join
1116
- end
1558
+ join_dependency.each(&block)
1117
1559
  end
1118
1560
  end
1119
1561
 
1120
1562
  def build_join_dependencies
1121
- associations = joins_values | left_outer_joins_values
1122
- associations |= eager_load_values unless eager_load_values.empty?
1123
- 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?
1124
1566
 
1125
1567
  join_dependencies = []
1126
1568
  join_dependencies.unshift construct_join_dependency(
1127
- select_association_list(associations, join_dependencies), nil
1569
+ select_named_joins(joins, join_dependencies), nil
1128
1570
  )
1129
1571
  end
1130
1572
 
@@ -1145,6 +1587,7 @@ module ActiveRecord
1145
1587
  arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1146
1588
 
1147
1589
  build_order(arel)
1590
+ build_with(arel)
1148
1591
  build_select(arel)
1149
1592
 
1150
1593
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
@@ -1155,14 +1598,6 @@ module ActiveRecord
1155
1598
  unless annotate_values.empty?
1156
1599
  annotates = annotate_values
1157
1600
  annotates = annotates.uniq if annotates.size > 1
1158
- unless annotates == annotate_values
1159
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
1160
- Duplicated query annotations are no longer shown in queries in Rails 7.0.
1161
- To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations
1162
- (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
1163
- MSG
1164
- annotates = annotate_values
1165
- end
1166
1601
  arel.comment(*annotates)
1167
1602
  end
1168
1603
 
@@ -1170,8 +1605,7 @@ module ActiveRecord
1170
1605
  end
1171
1606
 
1172
1607
  def build_cast_value(name, value)
1173
- cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1174
- Arel::Nodes::BindParam.new(cast_value)
1608
+ ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1175
1609
  end
1176
1610
 
1177
1611
  def build_from
@@ -1189,6 +1623,18 @@ module ActiveRecord
1189
1623
  end
1190
1624
  end
1191
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
+
1192
1638
  def select_association_list(associations, stashed_joins = nil)
1193
1639
  result = []
1194
1640
  associations.each do |association|
@@ -1204,20 +1650,21 @@ module ActiveRecord
1204
1650
  result
1205
1651
  end
1206
1652
 
1207
- class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1208
- end
1209
-
1210
1653
  def build_join_buckets
1211
1654
  buckets = Hash.new { |h, k| h[k] = [] }
1212
1655
 
1213
1656
  unless left_outer_joins_values.empty?
1214
1657
  stashed_left_joins = []
1215
- left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1216
- 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
1217
1664
  end
1218
1665
 
1219
1666
  if joins_values.empty?
1220
- buckets[:association_join] = left_joins
1667
+ buckets[:named_join] = left_joins
1221
1668
  buckets[:stashed_join] = stashed_left_joins
1222
1669
  return buckets, Arel::Nodes::OuterJoin
1223
1670
  else
@@ -1243,9 +1690,11 @@ module ActiveRecord
1243
1690
  end
1244
1691
  end
1245
1692
 
1246
- 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|
1247
1694
  if join.is_a?(Arel::Nodes::Join)
1248
1695
  buckets[:join_node] << join
1696
+ elsif join.is_a?(CTEJoin)
1697
+ buckets[:join_node] << build_with_join_node(join.name)
1249
1698
  else
1250
1699
  raise "unknown class: %s" % join.class.name
1251
1700
  end
@@ -1262,16 +1711,16 @@ module ActiveRecord
1262
1711
 
1263
1712
  buckets, join_type = build_join_buckets
1264
1713
 
1265
- association_joins = buckets[:association_join]
1266
- stashed_joins = buckets[:stashed_join]
1267
- leading_joins = buckets[:leading_join]
1268
- 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]
1269
1718
 
1270
1719
  join_sources.concat(leading_joins) unless leading_joins.empty?
1271
1720
 
1272
- unless association_joins.empty? && stashed_joins.empty?
1721
+ unless named_joins.empty? && stashed_joins.empty?
1273
1722
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1274
- join_dependency = construct_join_dependency(association_joins, join_type)
1723
+ join_dependency = construct_join_dependency(named_joins, join_type)
1275
1724
  join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1276
1725
  end
1277
1726
 
@@ -1282,13 +1731,47 @@ module ActiveRecord
1282
1731
  def build_select(arel)
1283
1732
  if select_values.any?
1284
1733
  arel.project(*arel_columns(select_values))
1285
- elsif klass.ignored_columns.any?
1734
+ elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1286
1735
  arel.project(*klass.column_names.map { |field| table[field] })
1287
1736
  else
1288
1737
  arel.project(table[Arel.star])
1289
1738
  end
1290
1739
  end
1291
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
+
1292
1775
  def arel_columns(columns)
1293
1776
  columns.flat_map do |field|
1294
1777
  case field
@@ -1407,7 +1890,7 @@ module ActiveRecord
1407
1890
  when Hash
1408
1891
  arg.map { |field, dir|
1409
1892
  case field
1410
- when Arel::Nodes::SqlLiteral
1893
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
1411
1894
  field.public_send(dir.downcase)
1412
1895
  else
1413
1896
  order_column(field.to_s).public_send(dir.downcase)
@@ -1423,14 +1906,35 @@ module ActiveRecord
1423
1906
  order_args.map! do |arg|
1424
1907
  klass.sanitize_sql_for_order(arg)
1425
1908
  end
1426
- order_args.flatten!
1427
- order_args.compact_blank!
1428
1909
  end
1429
1910
 
1430
1911
  def column_references(order_args)
1431
- references = order_args.grep(String)
1432
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1433
- references
1912
+ order_args.flat_map do |arg|
1913
+ case arg
1914
+ when String, Symbol
1915
+ extract_table_name_from(arg)
1916
+ when Hash
1917
+ arg
1918
+ .map do |key, value|
1919
+ case value
1920
+ when Hash
1921
+ key.to_s
1922
+ else
1923
+ extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
1924
+ end
1925
+ end
1926
+ when Arel::Attribute
1927
+ arg.relation.name
1928
+ when Arel::Nodes::Ordering
1929
+ if arg.expr.is_a?(Arel::Attribute)
1930
+ arg.expr.relation.name
1931
+ end
1932
+ end
1933
+ end.compact
1934
+ end
1935
+
1936
+ def extract_table_name_from(string)
1937
+ string.match(/^\W?(\w+)\W?\./) && $1
1434
1938
  end
1435
1939
 
1436
1940
  def order_column(field)
@@ -1443,6 +1947,15 @@ module ActiveRecord
1443
1947
  end
1444
1948
  end
1445
1949
 
1950
+ def build_case_for_value_position(column, values)
1951
+ node = Arel::Nodes::Case.new
1952
+ values.each.with_index(1) do |value, order|
1953
+ node.when(column.eq(value)).then(order)
1954
+ end
1955
+
1956
+ Arel::Nodes::Ascending.new(node)
1957
+ end
1958
+
1446
1959
  def resolve_arel_attributes(attrs)
1447
1960
  attrs.flat_map do |attr|
1448
1961
  case attr
@@ -1476,24 +1989,59 @@ module ActiveRecord
1476
1989
  # Post.references() # raises an error
1477
1990
  # Post.references([]) # does not raise an error
1478
1991
  #
1479
- # This particular method should be called with a method_name and the args
1992
+ # This particular method should be called with a method_name (__callee__) and the args
1480
1993
  # passed into that method as an input. For example:
1481
1994
  #
1482
1995
  # def references(*args)
1483
- # check_if_method_has_arguments!("references", args)
1996
+ # check_if_method_has_arguments!(__callee__, args)
1484
1997
  # ...
1485
1998
  # end
1486
1999
  def check_if_method_has_arguments!(method_name, args, message = nil)
1487
2000
  if args.blank?
1488
2001
  raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1489
- elsif block_given?
1490
- yield args
1491
2002
  else
2003
+ yield args if block_given?
2004
+
1492
2005
  args.flatten!
1493
2006
  args.compact_blank!
1494
2007
  end
1495
2008
  end
1496
2009
 
2010
+ def process_select_args(fields)
2011
+ fields.flat_map do |field|
2012
+ if field.is_a?(Hash)
2013
+ transform_select_hash_values(field)
2014
+ else
2015
+ field
2016
+ end
2017
+ end
2018
+ end
2019
+
2020
+ def transform_select_hash_values(fields)
2021
+ fields.flat_map do |key, columns_aliases|
2022
+ case columns_aliases
2023
+ when Hash
2024
+ 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)
2032
+ end
2033
+ when Array
2034
+ columns_aliases.map do |column|
2035
+ arel_column("#{key}.#{column}", &:itself)
2036
+ end
2037
+ 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)
2041
+ end
2042
+ end
2043
+ end
2044
+
1497
2045
  STRUCTURAL_VALUE_METHODS = (
1498
2046
  Relation::VALUE_METHODS -
1499
2047
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
@@ -1512,11 +2060,4 @@ module ActiveRecord
1512
2060
  end
1513
2061
  end
1514
2062
  end
1515
-
1516
- class Relation # :nodoc:
1517
- # No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
1518
- # TODO: Remove the class once Rails 6.1 has released.
1519
- class WhereClauseFactory # :nodoc:
1520
- end
1521
- end
1522
2063
  end