activerecord 6.1.6 → 7.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (309) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1627 -983
  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 +50 -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 +35 -31
  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.rb +26 -16
  27. data/lib/active_record/associations/preloader/association.rb +207 -52
  28. data/lib/active_record/associations/preloader/batch.rb +48 -0
  29. data/lib/active_record/associations/preloader/branch.rb +147 -0
  30. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  31. data/lib/active_record/associations/preloader.rb +50 -121
  32. data/lib/active_record/associations/singular_association.rb +9 -3
  33. data/lib/active_record/associations/through_association.rb +25 -14
  34. data/lib/active_record/associations.rb +439 -305
  35. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  36. data/lib/active_record/attribute_assignment.rb +1 -3
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  39. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  40. data/lib/active_record/attribute_methods/query.rb +31 -19
  41. data/lib/active_record/attribute_methods/read.rb +25 -10
  42. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  44. data/lib/active_record/attribute_methods/write.rb +10 -13
  45. data/lib/active_record/attribute_methods.rb +121 -40
  46. data/lib/active_record/attributes.rb +27 -38
  47. data/lib/active_record/autosave_association.rb +61 -30
  48. data/lib/active_record/base.rb +25 -2
  49. data/lib/active_record/callbacks.rb +18 -34
  50. data/lib/active_record/coders/column_serializer.rb +61 -0
  51. data/lib/active_record/coders/json.rb +1 -1
  52. data/lib/active_record/coders/yaml_column.rb +70 -34
  53. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  54. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -138
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -149
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
  69. data/lib/active_record/connection_adapters/column.rb +13 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  83. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  87. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  89. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  95. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  97. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +394 -74
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +509 -247
  101. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  102. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  103. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  104. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  107. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
  108. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  109. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  110. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  111. data/lib/active_record/connection_adapters.rb +9 -6
  112. data/lib/active_record/connection_handling.rb +107 -136
  113. data/lib/active_record/core.rb +202 -223
  114. data/lib/active_record/counter_cache.rb +46 -25
  115. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  116. data/lib/active_record/database_configurations/database_config.rb +21 -12
  117. data/lib/active_record/database_configurations/hash_config.rb +84 -16
  118. data/lib/active_record/database_configurations/url_config.rb +18 -12
  119. data/lib/active_record/database_configurations.rb +95 -59
  120. data/lib/active_record/delegated_type.rb +61 -15
  121. data/lib/active_record/deprecator.rb +7 -0
  122. data/lib/active_record/destroy_association_async_job.rb +3 -1
  123. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  124. data/lib/active_record/dynamic_matchers.rb +1 -1
  125. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  126. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  127. data/lib/active_record/encryption/cipher.rb +53 -0
  128. data/lib/active_record/encryption/config.rb +68 -0
  129. data/lib/active_record/encryption/configurable.rb +60 -0
  130. data/lib/active_record/encryption/context.rb +42 -0
  131. data/lib/active_record/encryption/contexts.rb +76 -0
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  134. data/lib/active_record/encryption/encryptable_record.rb +224 -0
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
  136. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  137. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  138. data/lib/active_record/encryption/encryptor.rb +155 -0
  139. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  140. data/lib/active_record/encryption/errors.rb +15 -0
  141. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  142. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  143. data/lib/active_record/encryption/key.rb +28 -0
  144. data/lib/active_record/encryption/key_generator.rb +53 -0
  145. data/lib/active_record/encryption/key_provider.rb +46 -0
  146. data/lib/active_record/encryption/message.rb +33 -0
  147. data/lib/active_record/encryption/message_serializer.rb +92 -0
  148. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  149. data/lib/active_record/encryption/properties.rb +76 -0
  150. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  151. data/lib/active_record/encryption/scheme.rb +96 -0
  152. data/lib/active_record/encryption.rb +56 -0
  153. data/lib/active_record/enum.rb +154 -63
  154. data/lib/active_record/errors.rb +171 -15
  155. data/lib/active_record/explain.rb +23 -3
  156. data/lib/active_record/explain_registry.rb +11 -6
  157. data/lib/active_record/explain_subscriber.rb +1 -1
  158. data/lib/active_record/fixture_set/file.rb +15 -1
  159. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  160. data/lib/active_record/fixture_set/render_context.rb +2 -0
  161. data/lib/active_record/fixture_set/table_row.rb +70 -14
  162. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  163. data/lib/active_record/fixtures.rb +131 -86
  164. data/lib/active_record/future_result.rb +164 -0
  165. data/lib/active_record/gem_version.rb +3 -3
  166. data/lib/active_record/inheritance.rb +81 -29
  167. data/lib/active_record/insert_all.rb +135 -22
  168. data/lib/active_record/integration.rb +11 -10
  169. data/lib/active_record/internal_metadata.rb +119 -33
  170. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  171. data/lib/active_record/locking/optimistic.rb +36 -21
  172. data/lib/active_record/locking/pessimistic.rb +15 -6
  173. data/lib/active_record/log_subscriber.rb +52 -19
  174. data/lib/active_record/marshalling.rb +56 -0
  175. data/lib/active_record/message_pack.rb +124 -0
  176. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  177. data/lib/active_record/middleware/database_selector.rb +23 -13
  178. data/lib/active_record/middleware/shard_selector.rb +62 -0
  179. data/lib/active_record/migration/command_recorder.rb +112 -14
  180. data/lib/active_record/migration/compatibility.rb +221 -48
  181. data/lib/active_record/migration/default_strategy.rb +23 -0
  182. data/lib/active_record/migration/execution_strategy.rb +19 -0
  183. data/lib/active_record/migration/join_table.rb +1 -1
  184. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  185. data/lib/active_record/migration.rb +358 -171
  186. data/lib/active_record/model_schema.rb +120 -101
  187. data/lib/active_record/nested_attributes.rb +37 -18
  188. data/lib/active_record/no_touching.rb +3 -3
  189. data/lib/active_record/normalization.rb +167 -0
  190. data/lib/active_record/persistence.rb +405 -85
  191. data/lib/active_record/promise.rb +84 -0
  192. data/lib/active_record/query_cache.rb +3 -21
  193. data/lib/active_record/query_logs.rb +174 -0
  194. data/lib/active_record/query_logs_formatter.rb +41 -0
  195. data/lib/active_record/querying.rb +29 -6
  196. data/lib/active_record/railtie.rb +219 -43
  197. data/lib/active_record/railties/controller_runtime.rb +13 -9
  198. data/lib/active_record/railties/databases.rake +188 -252
  199. data/lib/active_record/railties/job_runtime.rb +23 -0
  200. data/lib/active_record/readonly_attributes.rb +41 -3
  201. data/lib/active_record/reflection.rb +241 -80
  202. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  203. data/lib/active_record/relation/batches.rb +192 -63
  204. data/lib/active_record/relation/calculations.rb +219 -90
  205. data/lib/active_record/relation/delegation.rb +27 -13
  206. data/lib/active_record/relation/finder_methods.rb +108 -51
  207. data/lib/active_record/relation/merger.rb +22 -13
  208. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  209. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  210. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  211. data/lib/active_record/relation/predicate_builder.rb +27 -20
  212. data/lib/active_record/relation/query_attribute.rb +30 -12
  213. data/lib/active_record/relation/query_methods.rb +654 -127
  214. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  215. data/lib/active_record/relation/spawn_methods.rb +20 -3
  216. data/lib/active_record/relation/where_clause.rb +10 -19
  217. data/lib/active_record/relation.rb +262 -120
  218. data/lib/active_record/result.rb +37 -11
  219. data/lib/active_record/runtime_registry.rb +18 -13
  220. data/lib/active_record/sanitization.rb +65 -20
  221. data/lib/active_record/schema.rb +36 -22
  222. data/lib/active_record/schema_dumper.rb +73 -24
  223. data/lib/active_record/schema_migration.rb +68 -33
  224. data/lib/active_record/scoping/default.rb +72 -15
  225. data/lib/active_record/scoping/named.rb +5 -13
  226. data/lib/active_record/scoping.rb +65 -34
  227. data/lib/active_record/secure_password.rb +60 -0
  228. data/lib/active_record/secure_token.rb +21 -3
  229. data/lib/active_record/serialization.rb +6 -1
  230. data/lib/active_record/signed_id.rb +10 -8
  231. data/lib/active_record/store.rb +16 -11
  232. data/lib/active_record/suppressor.rb +13 -15
  233. data/lib/active_record/table_metadata.rb +16 -3
  234. data/lib/active_record/tasks/database_tasks.rb +225 -136
  235. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  236. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  237. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  238. data/lib/active_record/test_databases.rb +1 -1
  239. data/lib/active_record/test_fixtures.rb +123 -99
  240. data/lib/active_record/timestamp.rb +29 -18
  241. data/lib/active_record/token_for.rb +113 -0
  242. data/lib/active_record/touch_later.rb +11 -6
  243. data/lib/active_record/transactions.rb +48 -27
  244. data/lib/active_record/translation.rb +3 -3
  245. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  246. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  247. data/lib/active_record/type/internal/timezone.rb +7 -2
  248. data/lib/active_record/type/serialized.rb +9 -5
  249. data/lib/active_record/type/time.rb +4 -0
  250. data/lib/active_record/type/type_map.rb +17 -20
  251. data/lib/active_record/type.rb +1 -2
  252. data/lib/active_record/validations/absence.rb +1 -1
  253. data/lib/active_record/validations/associated.rb +4 -4
  254. data/lib/active_record/validations/numericality.rb +5 -4
  255. data/lib/active_record/validations/presence.rb +5 -28
  256. data/lib/active_record/validations/uniqueness.rb +51 -6
  257. data/lib/active_record/validations.rb +8 -4
  258. data/lib/active_record/version.rb +1 -1
  259. data/lib/active_record.rb +335 -32
  260. data/lib/arel/attributes/attribute.rb +0 -8
  261. data/lib/arel/crud.rb +28 -22
  262. data/lib/arel/delete_manager.rb +18 -4
  263. data/lib/arel/errors.rb +10 -0
  264. data/lib/arel/factory_methods.rb +4 -0
  265. data/lib/arel/filter_predications.rb +9 -0
  266. data/lib/arel/insert_manager.rb +2 -3
  267. data/lib/arel/nodes/and.rb +4 -0
  268. data/lib/arel/nodes/binary.rb +6 -1
  269. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  270. data/lib/arel/nodes/casted.rb +1 -1
  271. data/lib/arel/nodes/cte.rb +36 -0
  272. data/lib/arel/nodes/delete_statement.rb +12 -13
  273. data/lib/arel/nodes/filter.rb +10 -0
  274. data/lib/arel/nodes/fragments.rb +35 -0
  275. data/lib/arel/nodes/function.rb +1 -0
  276. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  277. data/lib/arel/nodes/insert_statement.rb +2 -2
  278. data/lib/arel/nodes/leading_join.rb +8 -0
  279. data/lib/arel/nodes/node.rb +111 -2
  280. data/lib/arel/nodes/select_core.rb +2 -2
  281. data/lib/arel/nodes/select_statement.rb +2 -2
  282. data/lib/arel/nodes/sql_literal.rb +6 -0
  283. data/lib/arel/nodes/table_alias.rb +4 -0
  284. data/lib/arel/nodes/update_statement.rb +8 -3
  285. data/lib/arel/nodes.rb +5 -0
  286. data/lib/arel/predications.rb +13 -3
  287. data/lib/arel/select_manager.rb +10 -4
  288. data/lib/arel/table.rb +9 -6
  289. data/lib/arel/tree_manager.rb +0 -12
  290. data/lib/arel/update_manager.rb +18 -4
  291. data/lib/arel/visitors/dot.rb +80 -90
  292. data/lib/arel/visitors/mysql.rb +16 -3
  293. data/lib/arel/visitors/postgresql.rb +0 -10
  294. data/lib/arel/visitors/to_sql.rb +139 -19
  295. data/lib/arel/visitors/visitor.rb +2 -2
  296. data/lib/arel.rb +18 -3
  297. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  298. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  299. data/lib/rails/generators/active_record/migration.rb +3 -1
  300. data/lib/rails/generators/active_record/model/USAGE +113 -0
  301. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  302. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  303. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  304. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  305. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  306. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  307. metadata +93 -13
  308. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  309. 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)
181
+ #
182
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
183
+ # are loaded with a single query.
122
184
  #
123
- # 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.
124
188
  #
125
- # users = User.includes(:address, :friends)
189
+ # You can also specify multiple associations. Each association will result
190
+ # in an additional query:
126
191
  #
127
- # 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)
128
196
  #
129
- # users = User.includes(:address, friends: [:address, :followers])
197
+ # Loading nested associations is possible using a Hash:
130
198
  #
131
- # === conditions
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:
629
915
  #
630
- # === 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
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,12 +1906,19 @@ 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)
1912
+ references = order_args.flat_map do |arg|
1913
+ case arg
1914
+ when String, Symbol
1915
+ arg
1916
+ when Hash
1917
+ arg.keys.map do |key|
1918
+ key if key.is_a?(String) || key.is_a?(Symbol)
1919
+ end
1920
+ end
1921
+ end
1432
1922
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1433
1923
  references
1434
1924
  end
@@ -1443,6 +1933,15 @@ module ActiveRecord
1443
1933
  end
1444
1934
  end
1445
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
+
1446
1945
  def resolve_arel_attributes(attrs)
1447
1946
  attrs.flat_map do |attr|
1448
1947
  case attr
@@ -1476,24 +1975,59 @@ module ActiveRecord
1476
1975
  # Post.references() # raises an error
1477
1976
  # Post.references([]) # does not raise an error
1478
1977
  #
1479
- # This particular method should be called with a method_name and the args
1978
+ # This particular method should be called with a method_name (__callee__) and the args
1480
1979
  # passed into that method as an input. For example:
1481
1980
  #
1482
1981
  # def references(*args)
1483
- # check_if_method_has_arguments!("references", args)
1982
+ # check_if_method_has_arguments!(__callee__, args)
1484
1983
  # ...
1485
1984
  # end
1486
1985
  def check_if_method_has_arguments!(method_name, args, message = nil)
1487
1986
  if args.blank?
1488
1987
  raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1489
- elsif block_given?
1490
- yield args
1491
1988
  else
1989
+ yield args if block_given?
1990
+
1492
1991
  args.flatten!
1493
1992
  args.compact_blank!
1494
1993
  end
1495
1994
  end
1496
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
+
1497
2031
  STRUCTURAL_VALUE_METHODS = (
1498
2032
  Relation::VALUE_METHODS -
1499
2033
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
@@ -1512,11 +2046,4 @@ module ActiveRecord
1512
2046
  end
1513
2047
  end
1514
2048
  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
2049
  end