activerecord 6.1.7 → 7.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +616 -1290
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +19 -8
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  30. data/lib/active_record/associations/join_dependency.rb +28 -20
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +429 -522
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +1 -5
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +15 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +57 -54
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +19 -35
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +325 -604
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +230 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +378 -143
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +348 -165
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +403 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +310 -253
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +58 -0
  165. data/lib/active_record/enum.rb +170 -62
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +59 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +145 -158
  199. data/lib/active_record/nested_attributes.rb +61 -23
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +18 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +229 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +332 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +200 -65
  216. data/lib/active_record/relation/calculations.rb +301 -112
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +870 -163
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +6 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +288 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +65 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/sqlite.rb +25 -0
  317. data/lib/arel/visitors/to_sql.rb +170 -36
  318. data/lib/arel/visitors/visitor.rb +2 -2
  319. data/lib/arel.rb +23 -4
  320. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  321. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  322. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  323. data/lib/rails/generators/active_record/migration.rb +3 -1
  324. data/lib/rails/generators/active_record/model/USAGE +113 -0
  325. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  326. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  328. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  329. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  330. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  331. metadata +103 -17
  332. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  333. 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,54 @@ 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
+ #
76
+ # You can define join type in the scope and +associated+ will not use `JOIN` by default.
77
+ #
78
+ # Post.left_joins(:author).where.associated(:author)
79
+ # # SELECT "posts".* FROM "posts"
80
+ # # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
81
+ # # WHERE "authors"."id" IS NOT NULL
82
+ #
83
+ # Post.left_joins(:comments).where.associated(:author)
84
+ # # SELECT "posts".* FROM "posts"
85
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
86
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
87
+ # # WHERE "author"."id" IS NOT NULL
88
+ def associated(*associations)
89
+ associations.each do |association|
90
+ reflection = scope_association_reflection(association)
91
+ unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
92
+ @scope.joins!(association)
93
+ end
94
+
95
+ if reflection.options[:class_name]
96
+ self.not(association => { reflection.association_primary_key => nil })
97
+ else
98
+ self.not(reflection.table_name => { reflection.association_primary_key => nil })
99
+ end
100
+ end
101
+
102
+ @scope
103
+ end
104
+
53
105
  # Returns a new relation with left outer joins and where clause to identify
54
106
  # missing relations.
55
107
  #
@@ -68,16 +120,37 @@ module ActiveRecord
68
120
  # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
69
121
  # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
70
122
  # # 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)
123
+ def missing(*associations)
124
+ associations.each do |association|
125
+ reflection = scope_association_reflection(association)
126
+ @scope.left_outer_joins!(association)
127
+ if reflection.options[:class_name]
128
+ @scope.where!(association => { reflection.association_primary_key => nil })
129
+ else
130
+ @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
131
+ end
77
132
  end
78
133
 
79
134
  @scope
80
135
  end
136
+
137
+ private
138
+ def scope_association_reflection(association)
139
+ reflection = @scope.klass._reflect_on_association(association)
140
+ unless reflection
141
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
142
+ end
143
+ reflection
144
+ end
145
+ end
146
+
147
+ # A wrapper to distinguish CTE joins from other nodes.
148
+ class CTEJoin # :nodoc:
149
+ attr_reader :name
150
+
151
+ def initialize(name)
152
+ @name = name
153
+ end
81
154
  end
82
155
 
83
156
  FROZEN_EMPTY_ARRAY = [].freeze
@@ -100,7 +173,7 @@ module ActiveRecord
100
173
  end # end
101
174
 
102
175
  def #{method_name}=(value) # def includes_values=(value)
103
- assert_mutability! # assert_mutability!
176
+ assert_modifiable! # assert_modifiable!
104
177
  @values[:#{name}] = value # @values[:includes] = value
105
178
  end # end
106
179
  CODE
@@ -108,47 +181,71 @@ module ActiveRecord
108
181
 
109
182
  alias extensions extending_values
110
183
 
111
- # Specify relationships to be included in the result set. For
112
- # example:
184
+ # Specify associations +args+ to be eager loaded to prevent N + 1 queries.
185
+ # A separate query is performed for each association, unless a join is
186
+ # required by conditions.
187
+ #
188
+ # For example:
113
189
  #
114
- # users = User.includes(:address)
190
+ # users = User.includes(:address).limit(5)
115
191
  # users.each do |user|
116
192
  # user.address.city
117
193
  # end
118
194
  #
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.
195
+ # # SELECT "users".* FROM "users" LIMIT 5
196
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
197
+ #
198
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
199
+ # are loaded with a single query.
122
200
  #
123
- # You can also specify multiple relationships, like this:
201
+ # Loading the associations in a separate query will often result in a
202
+ # performance improvement over a simple join, as a join can result in many
203
+ # rows that contain redundant data and it performs poorly at scale.
124
204
  #
125
- # users = User.includes(:address, :friends)
205
+ # You can also specify multiple associations. Each association will result
206
+ # in an additional query:
126
207
  #
127
- # Loading nested relationships is possible using a Hash:
208
+ # User.includes(:address, :friends).to_a
209
+ # # SELECT "users".* FROM "users"
210
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
211
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
128
212
  #
129
- # users = User.includes(:address, friends: [:address, :followers])
213
+ # Loading nested associations is possible using a Hash:
130
214
  #
131
- # === conditions
215
+ # User.includes(:address, friends: [:address, :followers])
216
+ #
217
+ # === Conditions
132
218
  #
133
219
  # If you want to add string conditions to your included models, you'll have
134
220
  # to explicitly reference them. For example:
135
221
  #
136
- # User.includes(:posts).where('posts.name = ?', 'example')
222
+ # User.includes(:posts).where('posts.name = ?', 'example').to_a
137
223
  #
138
224
  # Will throw an error, but this will work:
139
225
  #
140
- # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
226
+ # User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
227
+ # # SELECT "users"."id" AS t0_r0, ... FROM "users"
228
+ # # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
229
+ # # WHERE "posts"."name" = ? [["name", "example"]]
230
+ #
231
+ # As the <tt>LEFT OUTER JOIN</tt> already contains the posts, the second query for
232
+ # the posts is no longer performed.
141
233
  #
142
234
  # Note that #includes works with association names while #references needs
143
235
  # the actual table name.
144
236
  #
145
- # If you pass the conditions via hash, you don't need to call #references
237
+ # If you pass the conditions via a Hash, you don't need to call #references
146
238
  # explicitly, as #where references the tables for you. For example, this
147
239
  # will work correctly:
148
240
  #
149
241
  # User.includes(:posts).where(posts: { name: 'example' })
242
+ #
243
+ # NOTE: Conditions affect both sides of an association. For example, the
244
+ # above code will return only users that have a post named "example",
245
+ # <em>and will only include posts named "example"</em>, even when a
246
+ # matching user has other additional posts.
150
247
  def includes(*args)
151
- check_if_method_has_arguments!(:includes, args)
248
+ check_if_method_has_arguments!(__callee__, args)
152
249
  spawn.includes!(*args)
153
250
  end
154
251
 
@@ -157,14 +254,34 @@ module ActiveRecord
157
254
  self
158
255
  end
159
256
 
160
- # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
257
+ # Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
258
+ # Performs a single query joining all specified associations. For example:
259
+ #
260
+ # users = User.eager_load(:address).limit(5)
261
+ # users.each do |user|
262
+ # user.address.city
263
+ # end
264
+ #
265
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
266
+ # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
267
+ # # LIMIT 5
268
+ #
269
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
270
+ # are loaded with a single joined query.
161
271
  #
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"
272
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
273
+ # similar to #includes:
274
+ #
275
+ # User.eager_load(:address, friends: [:address, :followers])
276
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
277
+ # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
278
+ # # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
279
+ # # ...
280
+ #
281
+ # NOTE: Loading the associations in a join can result in many rows that
282
+ # contain redundant data and it performs poorly at scale.
166
283
  def eager_load(*args)
167
- check_if_method_has_arguments!(:eager_load, args)
284
+ check_if_method_has_arguments!(__callee__, args)
168
285
  spawn.eager_load!(*args)
169
286
  end
170
287
 
@@ -173,12 +290,30 @@ module ActiveRecord
173
290
  self
174
291
  end
175
292
 
176
- # Allows preloading of +args+, in the same way that #includes does:
293
+ # Specify associations +args+ to be eager loaded using separate queries.
294
+ # A separate query is performed for each association.
295
+ #
296
+ # users = User.preload(:address).limit(5)
297
+ # users.each do |user|
298
+ # user.address.city
299
+ # end
300
+ #
301
+ # # SELECT "users".* FROM "users" LIMIT 5
302
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
303
+ #
304
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
305
+ # are loaded with a separate query.
177
306
  #
178
- # User.preload(:posts)
179
- # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
307
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
308
+ # similar to #includes:
309
+ #
310
+ # User.preload(:address, friends: [:address, :followers])
311
+ # # SELECT "users".* FROM "users"
312
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
313
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
314
+ # # SELECT ...
180
315
  def preload(*args)
181
- check_if_method_has_arguments!(:preload, args)
316
+ check_if_method_has_arguments!(__callee__, args)
182
317
  spawn.preload!(*args)
183
318
  end
184
319
 
@@ -201,7 +336,7 @@ module ActiveRecord
201
336
  end
202
337
 
203
338
  # 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.
339
+ # and should therefore be +JOIN+ed in any query rather than loaded separately.
205
340
  # This method only works in conjunction with #includes.
206
341
  # See #includes for more details.
207
342
  #
@@ -211,7 +346,7 @@ module ActiveRecord
211
346
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
212
347
  # # Query now knows the string references posts, so adds a JOIN
213
348
  def references(*table_names)
214
- check_if_method_has_arguments!(:references, table_names)
349
+ check_if_method_has_arguments!(__callee__, table_names)
215
350
  spawn.references!(*table_names)
216
351
  end
217
352
 
@@ -245,10 +380,18 @@ module ActiveRecord
245
380
  # Model.select(:field, :other_field, :and_one_more)
246
381
  # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
247
382
  #
383
+ # The argument also can be a hash of fields and aliases.
384
+ #
385
+ # Model.select(models: { field: :alias, other_field: :other_alias })
386
+ # # => [#<Model id: nil, alias: "value", other_alias: "value">]
387
+ #
388
+ # Model.select(models: [:field, :other_field])
389
+ # # => [#<Model id: nil, field: "value", other_field: "value">]
390
+ #
248
391
  # You can also use one or more strings, which will be used unchanged as SELECT fields.
249
392
  #
250
393
  # Model.select('field AS field_one', 'other_field AS field_two')
251
- # # => [#<Model id: nil, field: "value", other_field: "value">]
394
+ # # => [#<Model id: nil, field_one: "value", field_two: "value">]
252
395
  #
253
396
  # If an alias was specified, it will be accessible from the resulting objects:
254
397
  #
@@ -259,7 +402,7 @@ module ActiveRecord
259
402
  # except +id+ will throw ActiveModel::MissingAttributeError:
260
403
  #
261
404
  # Model.select(:field).first.other_field
262
- # # => ActiveModel::MissingAttributeError: missing attribute: other_field
405
+ # # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
263
406
  def select(*fields)
264
407
  if block_given?
265
408
  if fields.any?
@@ -269,7 +412,9 @@ module ActiveRecord
269
412
  return super()
270
413
  end
271
414
 
272
- check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
415
+ check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
416
+
417
+ fields = process_select_args(fields)
273
418
  spawn._select!(*fields)
274
419
  end
275
420
 
@@ -278,6 +423,102 @@ module ActiveRecord
278
423
  self
279
424
  end
280
425
 
426
+ # Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
427
+ #
428
+ # Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to
429
+ # use CTE's with MySQL 5.7.
430
+ #
431
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
432
+ # # => ActiveRecord::Relation
433
+ # # WITH posts_with_tags AS (
434
+ # # SELECT * FROM posts WHERE (tags_count > 0)
435
+ # # )
436
+ # # SELECT * FROM posts
437
+ #
438
+ # You can also pass an array of sub-queries to be joined in a +UNION ALL+.
439
+ #
440
+ # Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
441
+ # # => ActiveRecord::Relation
442
+ # # WITH posts_with_tags_or_comments AS (
443
+ # # (SELECT * FROM posts WHERE (tags_count > 0))
444
+ # # UNION ALL
445
+ # # (SELECT * FROM posts WHERE (comments_count > 0))
446
+ # # )
447
+ # # SELECT * FROM posts
448
+ #
449
+ # Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
450
+ #
451
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
452
+ # # => ActiveRecord::Relation
453
+ # # WITH posts_with_tags AS (
454
+ # # SELECT * FROM posts WHERE (tags_count > 0)
455
+ # # )
456
+ # # SELECT * FROM posts_with_tags AS posts
457
+ #
458
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
459
+ # # => ActiveRecord::Relation
460
+ # # WITH posts_with_tags AS (
461
+ # # SELECT * FROM posts WHERE (tags_count > 0)
462
+ # # )
463
+ # # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
464
+ #
465
+ # It is recommended to pass a query as ActiveRecord::Relation. If that is not possible
466
+ # and you have verified it is safe for the database, you can pass it as SQL literal
467
+ # using +Arel+.
468
+ #
469
+ # Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
470
+ #
471
+ # Great caution should be taken to avoid SQL injection vulnerabilities. This method should not
472
+ # be used with unsafe values that include unsanitized input.
473
+ #
474
+ # To add multiple CTEs just pass multiple key-value pairs
475
+ #
476
+ # Post.with(
477
+ # posts_with_comments: Post.where("comments_count > ?", 0),
478
+ # posts_with_tags: Post.where("tags_count > ?", 0)
479
+ # )
480
+ #
481
+ # or chain multiple +.with+ calls
482
+ #
483
+ # Post
484
+ # .with(posts_with_comments: Post.where("comments_count > ?", 0))
485
+ # .with(posts_with_tags: Post.where("tags_count > ?", 0))
486
+ def with(*args)
487
+ raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
488
+ check_if_method_has_arguments!(__callee__, args)
489
+ spawn.with!(*args)
490
+ end
491
+
492
+ # Like #with, but modifies relation in place.
493
+ def with!(*args) # :nodoc:
494
+ self.with_values += args
495
+ self
496
+ end
497
+
498
+ # Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
499
+ #
500
+ # Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
501
+ # # => ActiveRecord::Relation
502
+ # # WITH post_and_replies AS (
503
+ # # (SELECT * FROM posts WHERE id = 42)
504
+ # # UNION ALL
505
+ # # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
506
+ # # )
507
+ # # SELECT * FROM posts
508
+ #
509
+ # See `#with` for more information.
510
+ def with_recursive(*args)
511
+ check_if_method_has_arguments!(__callee__, args)
512
+ spawn.with_recursive!(*args)
513
+ end
514
+
515
+ # Like #with_recursive but modifies the relation in place.
516
+ def with_recursive!(*args) # :nodoc:
517
+ self.with_values += args
518
+ @with_is_recursive = true
519
+ self
520
+ end
521
+
281
522
  # Allows you to change a previously set select statement.
282
523
  #
283
524
  # Post.select(:title, :body)
@@ -289,7 +530,8 @@ module ActiveRecord
289
530
  # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
290
531
  # Note that we're unscoping the entire select statement.
291
532
  def reselect(*args)
292
- check_if_method_has_arguments!(:reselect, args)
533
+ check_if_method_has_arguments!(__callee__, args)
534
+ args = process_select_args(args)
293
535
  spawn.reselect!(*args)
294
536
  end
295
537
 
@@ -320,7 +562,7 @@ module ActiveRecord
320
562
  # User.select([:id, :first_name]).group(:id, :first_name).first(3)
321
563
  # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
322
564
  def group(*args)
323
- check_if_method_has_arguments!(:group, args)
565
+ check_if_method_has_arguments!(__callee__, args)
324
566
  spawn.group!(*args)
325
567
  end
326
568
 
@@ -329,17 +571,58 @@ module ActiveRecord
329
571
  self
330
572
  end
331
573
 
332
- # Allows to specify an order attribute:
574
+ # Allows you to change a previously set group statement.
575
+ #
576
+ # Post.group(:title, :body)
577
+ # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
578
+ #
579
+ # Post.group(:title, :body).regroup(:title)
580
+ # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
581
+ #
582
+ # This is short-hand for <tt>unscope(:group).group(fields)</tt>.
583
+ # Note that we're unscoping the entire group statement.
584
+ def regroup(*args)
585
+ check_if_method_has_arguments!(__callee__, args)
586
+ spawn.regroup!(*args)
587
+ end
588
+
589
+ # Same as #regroup but operates on relation in-place instead of copying.
590
+ def regroup!(*args) # :nodoc:
591
+ self.group_values = args
592
+ self
593
+ end
594
+
595
+ # Applies an <code>ORDER BY</code> clause to a query.
596
+ #
597
+ # #order accepts arguments in one of several formats.
598
+ #
599
+ # === symbols
600
+ #
601
+ # The symbol represents the name of the column you want to order the results by.
333
602
  #
334
603
  # User.order(:name)
335
604
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
336
605
  #
606
+ # By default, the order is ascending. If you want descending order, you can
607
+ # map the column name symbol to +:desc+.
608
+ #
337
609
  # User.order(email: :desc)
338
610
  # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
339
611
  #
612
+ # Multiple columns can be passed this way, and they will be applied in the order specified.
613
+ #
340
614
  # User.order(:name, email: :desc)
341
615
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
342
616
  #
617
+ # === strings
618
+ #
619
+ # Strings are passed directly to the database, allowing you to specify
620
+ # simple SQL expressions.
621
+ #
622
+ # This could be a source of SQL injection, so only strings composed of plain
623
+ # column names and simple <code>function(column_name)</code> expressions
624
+ # with optional +ASC+/+DESC+ modifiers are allowed.
625
+ #
343
626
  # User.order('name')
344
627
  # # SELECT "users".* FROM "users" ORDER BY name
345
628
  #
@@ -348,8 +631,21 @@ module ActiveRecord
348
631
  #
349
632
  # User.order('name DESC, email')
350
633
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
634
+ #
635
+ # === Arel
636
+ #
637
+ # If you need to pass in complicated expressions that you have verified
638
+ # are safe for the database, you can use Arel.
639
+ #
640
+ # User.order(Arel.sql('end_date - start_date'))
641
+ # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
642
+ #
643
+ # Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
644
+ #
645
+ # User.order(Arel.sql("payload->>'kind'"))
646
+ # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
351
647
  def order(*args)
352
- check_if_method_has_arguments!(:order, args) do
648
+ check_if_method_has_arguments!(__callee__, args) do
353
649
  sanitize_order_arguments(args)
354
650
  end
355
651
  spawn.order!(*args)
@@ -362,6 +658,66 @@ module ActiveRecord
362
658
  self
363
659
  end
364
660
 
661
+ # Applies an <tt>ORDER BY</tt> clause based on a given +column+,
662
+ # ordered and filtered by a specific set of +values+.
663
+ #
664
+ # User.in_order_of(:id, [1, 5, 3])
665
+ # # SELECT "users".* FROM "users"
666
+ # # WHERE "users"."id" IN (1, 5, 3)
667
+ # # ORDER BY CASE
668
+ # # WHEN "users"."id" = 1 THEN 1
669
+ # # WHEN "users"."id" = 5 THEN 2
670
+ # # WHEN "users"."id" = 3 THEN 3
671
+ # # END ASC
672
+ #
673
+ # +column+ can point to an enum column; the actual query generated may be different depending
674
+ # on the database adapter and the column definition.
675
+ #
676
+ # class Conversation < ActiveRecord::Base
677
+ # enum :status, [ :active, :archived ]
678
+ # end
679
+ #
680
+ # Conversation.in_order_of(:status, [:archived, :active])
681
+ # # SELECT "conversations".* FROM "conversations"
682
+ # # WHERE "conversations"."status" IN (1, 0)
683
+ # # ORDER BY CASE
684
+ # # WHEN "conversations"."status" = 1 THEN 1
685
+ # # WHEN "conversations"."status" = 0 THEN 2
686
+ # # END ASC
687
+ #
688
+ # +values+ can also include +nil+.
689
+ #
690
+ # Conversation.in_order_of(:status, [nil, :archived, :active])
691
+ # # SELECT "conversations".* FROM "conversations"
692
+ # # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
693
+ # # ORDER BY CASE
694
+ # # WHEN "conversations"."status" IS NULL THEN 1
695
+ # # WHEN "conversations"."status" = 1 THEN 2
696
+ # # WHEN "conversations"."status" = 0 THEN 3
697
+ # # END ASC
698
+ #
699
+ def in_order_of(column, values)
700
+ klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
701
+ return spawn.none! if values.empty?
702
+
703
+ references = column_references([column])
704
+ self.references_values |= references unless references.empty?
705
+
706
+ values = values.map { |value| type_caster.type_cast_for_database(column, value) }
707
+ arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
708
+
709
+ where_clause =
710
+ if values.include?(nil)
711
+ arel_column.in(values.compact).or(arel_column.eq(nil))
712
+ else
713
+ arel_column.in(values)
714
+ end
715
+
716
+ spawn
717
+ .order!(build_case_for_value_position(arel_column, values))
718
+ .where!(where_clause)
719
+ end
720
+
365
721
  # Replaces any existing order defined on the relation with the specified order.
366
722
  #
367
723
  # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
@@ -370,17 +726,17 @@ module ActiveRecord
370
726
  #
371
727
  # User.order('email DESC').reorder('id ASC').order('name ASC')
372
728
  #
373
- # generates a query with 'ORDER BY id ASC, name ASC'.
729
+ # generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
374
730
  def reorder(*args)
375
- check_if_method_has_arguments!(:reorder, args) do
376
- sanitize_order_arguments(args) unless args.all?(&:blank?)
731
+ check_if_method_has_arguments!(__callee__, args) do
732
+ sanitize_order_arguments(args)
377
733
  end
378
734
  spawn.reorder!(*args)
379
735
  end
380
736
 
381
737
  # Same as #reorder but operates on relation in-place instead of copying.
382
738
  def reorder!(*args) # :nodoc:
383
- preprocess_order_args(args) unless args.all?(&:blank?)
739
+ preprocess_order_args(args)
384
740
  args.uniq!
385
741
  self.reordering_value = true
386
742
  self.order_values = args
@@ -389,7 +745,8 @@ module ActiveRecord
389
745
 
390
746
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
391
747
  :limit, :offset, :joins, :left_outer_joins, :annotate,
392
- :includes, :from, :readonly, :having, :optimizer_hints])
748
+ :includes, :eager_load, :preload, :from, :readonly,
749
+ :having, :optimizer_hints, :with])
393
750
 
394
751
  # Removes an unwanted relation that is already defined on a chain of relations.
395
752
  # This is useful when passing around chains of relations and would like to
@@ -425,7 +782,7 @@ module ActiveRecord
425
782
  # has_many :comments, -> { unscope(where: :trashed) }
426
783
  #
427
784
  def unscope(*args)
428
- check_if_method_has_arguments!(:unscope, args)
785
+ check_if_method_has_arguments!(__callee__, args)
429
786
  spawn.unscope!(*args)
430
787
  end
431
788
 
@@ -439,7 +796,7 @@ module ActiveRecord
439
796
  if !VALID_UNSCOPING_VALUES.include?(scope)
440
797
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
441
798
  end
442
- assert_mutability!
799
+ assert_modifiable!
443
800
  @values.delete(scope)
444
801
  when Hash
445
802
  scope.each do |key, target_value|
@@ -458,7 +815,7 @@ module ActiveRecord
458
815
  self
459
816
  end
460
817
 
461
- # Performs a joins on +args+. The given symbol(s) should match the name of
818
+ # Performs JOINs on +args+. The given symbol(s) should match the name of
462
819
  # the association(s).
463
820
  #
464
821
  # User.joins(:posts)
@@ -487,7 +844,7 @@ module ActiveRecord
487
844
  # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
488
845
  # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
489
846
  def joins(*args)
490
- check_if_method_has_arguments!(:joins, args)
847
+ check_if_method_has_arguments!(__callee__, args)
491
848
  spawn.joins!(*args)
492
849
  end
493
850
 
@@ -496,10 +853,10 @@ module ActiveRecord
496
853
  self
497
854
  end
498
855
 
499
- # Performs a left outer joins on +args+:
856
+ # Performs LEFT OUTER JOINs on +args+:
500
857
  #
501
858
  # User.left_outer_joins(:posts)
502
- # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
859
+ # # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
503
860
  #
504
861
  def left_outer_joins(*args)
505
862
  check_if_method_has_arguments!(__callee__, args)
@@ -519,7 +876,7 @@ module ActiveRecord
519
876
  # SQL is given as an illustration; the actual query generated may be different depending
520
877
  # on the database adapter.
521
878
  #
522
- # === string
879
+ # === \String
523
880
  #
524
881
  # A single string, without additional arguments, is passed to the query
525
882
  # constructor as an SQL fragment, and used in the where clause of the query.
@@ -531,7 +888,7 @@ module ActiveRecord
531
888
  # to injection attacks if not done properly. As an alternative, it is recommended
532
889
  # to use one of the following methods.
533
890
  #
534
- # === array
891
+ # === \Array
535
892
  #
536
893
  # If an array is passed, then the first element of the array is treated as a template, and
537
894
  # the remaining elements are inserted into the template to generate the condition.
@@ -571,20 +928,20 @@ module ActiveRecord
571
928
  # dependencies on the underlying database. If your code is intended for general consumption,
572
929
  # test with multiple database backends.
573
930
  #
574
- # === hash
931
+ # === \Hash
575
932
  #
576
933
  # #where will also accept a hash condition, in which the keys are fields and the values
577
934
  # are values to be searched for.
578
935
  #
579
936
  # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
580
937
  #
581
- # User.where({ name: "Joe", email: "joe@example.com" })
938
+ # User.where(name: "Joe", email: "joe@example.com")
582
939
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
583
940
  #
584
- # User.where({ name: ["Alice", "Bob"]})
941
+ # User.where(name: ["Alice", "Bob"])
585
942
  # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
586
943
  #
587
- # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
944
+ # User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
588
945
  # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
589
946
  #
590
947
  # In the case of a belongs_to relationship, an association key can be used
@@ -605,6 +962,12 @@ module ActiveRecord
605
962
  # PriceEstimate.where(estimate_of: treasure)
606
963
  # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
607
964
  #
965
+ # Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
966
+ # an array of columns with an array of tuples as values.
967
+ #
968
+ # Article.where([:author_id, :id] => [[15, 1], [15, 2]])
969
+ # # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
970
+ #
608
971
  # === Joins
609
972
  #
610
973
  # If the relation is the result of a join, you may create a condition which uses any of the
@@ -614,20 +977,34 @@ module ActiveRecord
614
977
  #
615
978
  # For hash conditions, you can either use the table name in the key, or use a sub-hash.
616
979
  #
617
- # User.joins(:posts).where({ "posts.published" => true })
618
- # User.joins(:posts).where({ posts: { published: true } })
980
+ # User.joins(:posts).where("posts.published" => true)
981
+ # User.joins(:posts).where(posts: { published: true })
619
982
  #
620
- # === no argument
983
+ # === No Argument
621
984
  #
622
985
  # 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.
986
+ # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
987
+ #
988
+ # Chaining with WhereChain#not:
624
989
  #
625
990
  # User.where.not(name: "Jon")
626
991
  # # SELECT * FROM users WHERE name != 'Jon'
627
992
  #
628
- # See WhereChain for more details on #not.
993
+ # Chaining with WhereChain#associated:
994
+ #
995
+ # Post.where.associated(:author)
996
+ # # SELECT "posts".* FROM "posts"
997
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
998
+ # # WHERE "authors"."id" IS NOT NULL
629
999
  #
630
- # === blank condition
1000
+ # Chaining with WhereChain#missing:
1001
+ #
1002
+ # Post.where.missing(:author)
1003
+ # # SELECT "posts".* FROM "posts"
1004
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
1005
+ # # WHERE "authors"."id" IS NULL
1006
+ #
1007
+ # === Blank Condition
631
1008
  #
632
1009
  # If the condition is any blank-ish object, then #where is a no-op and returns
633
1010
  # the current relation.
@@ -660,6 +1037,8 @@ module ActiveRecord
660
1037
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
661
1038
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
662
1039
  def rewhere(conditions)
1040
+ return unscope(:where) if conditions.nil?
1041
+
663
1042
  scope = spawn
664
1043
  where_clause = scope.build_where_clause(conditions)
665
1044
 
@@ -668,6 +1047,59 @@ module ActiveRecord
668
1047
  scope
669
1048
  end
670
1049
 
1050
+ # Allows you to invert an entire where clause instead of manually applying conditions.
1051
+ #
1052
+ # class User
1053
+ # scope :active, -> { where(accepted: true, locked: false) }
1054
+ # end
1055
+ #
1056
+ # User.where(accepted: true)
1057
+ # # WHERE `accepted` = 1
1058
+ #
1059
+ # User.where(accepted: true).invert_where
1060
+ # # WHERE `accepted` != 1
1061
+ #
1062
+ # User.active
1063
+ # # WHERE `accepted` = 1 AND `locked` = 0
1064
+ #
1065
+ # User.active.invert_where
1066
+ # # WHERE NOT (`accepted` = 1 AND `locked` = 0)
1067
+ #
1068
+ # Be careful because this inverts all conditions before +invert_where+ call.
1069
+ #
1070
+ # class User
1071
+ # scope :active, -> { where(accepted: true, locked: false) }
1072
+ # scope :inactive, -> { active.invert_where } # Do not attempt it
1073
+ # end
1074
+ #
1075
+ # # It also inverts `where(role: 'admin')` unexpectedly.
1076
+ # User.where(role: 'admin').inactive
1077
+ # # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
1078
+ #
1079
+ def invert_where
1080
+ spawn.invert_where!
1081
+ end
1082
+
1083
+ def invert_where! # :nodoc:
1084
+ self.where_clause = where_clause.invert
1085
+ self
1086
+ end
1087
+
1088
+ # Checks whether the given relation is structurally compatible with this relation, to determine
1089
+ # if it's possible to use the #and and #or methods without raising an error. Structurally
1090
+ # compatible is defined as: they must be scoping the same model, and they must differ only by
1091
+ # #where (if no #group has been defined) or #having (if a #group is present).
1092
+ #
1093
+ # Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
1094
+ # # => true
1095
+ #
1096
+ # Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
1097
+ # # => false
1098
+ #
1099
+ def structurally_compatible?(other)
1100
+ structurally_incompatible_values_for(other).empty?
1101
+ end
1102
+
671
1103
  # Returns a new relation, which is the logical intersection of this relation and the one passed
672
1104
  # as an argument.
673
1105
  #
@@ -712,7 +1144,11 @@ module ActiveRecord
712
1144
  #
713
1145
  def or(other)
714
1146
  if other.is_a?(Relation)
715
- spawn.or!(other)
1147
+ if @none
1148
+ other.spawn
1149
+ else
1150
+ spawn.or!(other)
1151
+ end
716
1152
  else
717
1153
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
718
1154
  end
@@ -725,7 +1161,7 @@ module ActiveRecord
725
1161
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
726
1162
  end
727
1163
 
728
- self.where_clause = self.where_clause.or(other.where_clause)
1164
+ self.where_clause = where_clause.or(other.where_clause)
729
1165
  self.having_clause = having_clause.or(other.having_clause)
730
1166
  self.references_values |= other.references_values
731
1167
 
@@ -825,15 +1261,29 @@ module ActiveRecord
825
1261
  end
826
1262
 
827
1263
  def none! # :nodoc:
828
- where!("1=0").extending!(NullRelation)
1264
+ unless @none
1265
+ where!("1=0")
1266
+ @none = true
1267
+ end
1268
+ self
1269
+ end
1270
+
1271
+ def null_relation? # :nodoc:
1272
+ @none
829
1273
  end
830
1274
 
831
- # Sets readonly attributes for the returned relation. If value is
832
- # true (default), attempting to update a record will result in an error.
1275
+ # Mark a relation as readonly. Attempting to update a record will result in
1276
+ # an error.
833
1277
  #
834
1278
  # users = User.readonly
835
1279
  # users.first.save
836
1280
  # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1281
+ #
1282
+ # To make a readonly relation writable, pass +false+.
1283
+ #
1284
+ # users.readonly(false)
1285
+ # users.first.save
1286
+ # => true
837
1287
  def readonly(value = true)
838
1288
  spawn.readonly!(value)
839
1289
  end
@@ -886,7 +1336,7 @@ module ActiveRecord
886
1336
  self
887
1337
  end
888
1338
 
889
- # Specifies table from which the records will be fetched. For example:
1339
+ # Specifies the table from which the records will be fetched. For example:
890
1340
  #
891
1341
  # Topic.select('title').from('posts')
892
1342
  # # SELECT title FROM posts
@@ -896,9 +1346,26 @@ module ActiveRecord
896
1346
  # Topic.select('title').from(Topic.approved)
897
1347
  # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
898
1348
  #
1349
+ # Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
1350
+ #
899
1351
  # Topic.select('a.title').from(Topic.approved, :a)
900
1352
  # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
901
1353
  #
1354
+ # It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
1355
+ #
1356
+ # Topic.select('title').from(Topic.approved).from(Topic.inactive)
1357
+ # # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
1358
+ #
1359
+ # For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
1360
+ #
1361
+ # color = "red"
1362
+ # Color
1363
+ # .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
1364
+ # .where("colorvalue->>'color' = ?", color)
1365
+ # .select("c.*").to_a
1366
+ # # SELECT c.*
1367
+ # # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
1368
+ # # WHERE (colorvalue->>'color' = 'red')
902
1369
  def from(value, subquery_name = nil)
903
1370
  spawn.from!(value, subquery_name)
904
1371
  end
@@ -933,7 +1400,7 @@ module ActiveRecord
933
1400
  #
934
1401
  # The object returned is a relation, which can be further extended.
935
1402
  #
936
- # === Using a module
1403
+ # === Using a \Module
937
1404
  #
938
1405
  # module Pagination
939
1406
  # def page(number)
@@ -948,7 +1415,7 @@ module ActiveRecord
948
1415
  #
949
1416
  # scope = Model.all.extending(Pagination, SomethingElse)
950
1417
  #
951
- # === Using a block
1418
+ # === Using a Block
952
1419
  #
953
1420
  # scope = Model.all.extending do
954
1421
  # def page(number)
@@ -994,7 +1461,7 @@ module ActiveRecord
994
1461
  # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
995
1462
  # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
996
1463
  def optimizer_hints(*args)
997
- check_if_method_has_arguments!(:optimizer_hints, args)
1464
+ check_if_method_has_arguments!(__callee__, args)
998
1465
  spawn.optimizer_hints!(*args)
999
1466
  end
1000
1467
 
@@ -1035,8 +1502,10 @@ module ActiveRecord
1035
1502
  # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1036
1503
  #
1037
1504
  # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1505
+ #
1506
+ # Some escaping is performed, however untrusted user input should not be used.
1038
1507
  def annotate(*args)
1039
- check_if_method_has_arguments!(:annotate, args)
1508
+ check_if_method_has_arguments!(__callee__, args)
1040
1509
  spawn.annotate!(*args)
1041
1510
  end
1042
1511
 
@@ -1054,9 +1523,54 @@ module ActiveRecord
1054
1523
  self
1055
1524
  end
1056
1525
 
1526
+ # Excludes the specified record (or collection of records) from the resulting
1527
+ # relation. For example:
1528
+ #
1529
+ # Post.excluding(post)
1530
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
1531
+ #
1532
+ # Post.excluding(post_one, post_two)
1533
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1534
+ #
1535
+ # Post.excluding(Post.drafts)
1536
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
1537
+ #
1538
+ # This can also be called on associations. As with the above example, either
1539
+ # a single record of collection thereof may be specified:
1540
+ #
1541
+ # post = Post.find(1)
1542
+ # comment = Comment.find(2)
1543
+ # post.comments.excluding(comment)
1544
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
1545
+ #
1546
+ # This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
1547
+ #
1548
+ # An <tt>ArgumentError</tt> will be raised if either no records are
1549
+ # specified, or if any of the records in the collection (if a collection
1550
+ # is passed in) are not instances of the same model that the relation is
1551
+ # scoping.
1552
+ def excluding(*records)
1553
+ relations = records.extract! { |element| element.is_a?(Relation) }
1554
+ records.flatten!(1)
1555
+ records.compact!
1556
+
1557
+ unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
1558
+ raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1559
+ end
1560
+
1561
+ spawn.excluding!(records + relations.flat_map(&:ids))
1562
+ end
1563
+ alias :without :excluding
1564
+
1565
+ def excluding!(records) # :nodoc:
1566
+ predicates = [ predicate_builder[primary_key, records].invert ]
1567
+ self.where_clause += Relation::WhereClause.new(predicates)
1568
+ self
1569
+ end
1570
+
1057
1571
  # Returns the Arel object associated with the relation.
1058
1572
  def arel(aliases = nil) # :nodoc:
1059
- @arel ||= build_arel(aliases)
1573
+ @arel ||= with_connection { |c| build_arel(c, aliases) }
1060
1574
  end
1061
1575
 
1062
1576
  def construct_join_dependency(associations, join_type) # :nodoc:
@@ -1077,13 +1591,29 @@ module ActiveRecord
1077
1591
  def build_where_clause(opts, rest = []) # :nodoc:
1078
1592
  opts = sanitize_forbidden_attributes(opts)
1079
1593
 
1594
+ if opts.is_a?(Array)
1595
+ opts, *rest = opts
1596
+ end
1597
+
1080
1598
  case opts
1081
- when String, Array
1082
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1599
+ when String
1600
+ if rest.empty?
1601
+ parts = [Arel.sql(opts)]
1602
+ elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
1603
+ parts = [build_named_bound_sql_literal(opts, rest.first)]
1604
+ elsif opts.include?("?")
1605
+ parts = [build_bound_sql_literal(opts, rest)]
1606
+ else
1607
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1608
+ end
1083
1609
  when Hash
1084
1610
  opts = opts.transform_keys do |key|
1085
- key = key.to_s
1086
- klass.attribute_aliases[key] || key
1611
+ if key.is_a?(Array)
1612
+ key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
1613
+ else
1614
+ key = key.to_s
1615
+ klass.attribute_aliases[key] || key
1616
+ end
1087
1617
  end
1088
1618
  references = PredicateBuilder.references(opts)
1089
1619
  self.references_values |= references unless references.empty?
@@ -1101,7 +1631,76 @@ module ActiveRecord
1101
1631
  end
1102
1632
  alias :build_having_clause :build_where_clause
1103
1633
 
1634
+ def async!
1635
+ @async = true
1636
+ self
1637
+ end
1638
+
1639
+ protected
1640
+ def arel_columns(columns)
1641
+ columns.flat_map do |field|
1642
+ case field
1643
+ when Symbol
1644
+ arel_column(field.to_s) do |attr_name|
1645
+ adapter_class.quote_table_name(attr_name)
1646
+ end
1647
+ when String
1648
+ arel_column(field, &:itself)
1649
+ when Proc
1650
+ field.call
1651
+ when Hash
1652
+ arel_columns_from_hash(field)
1653
+ else
1654
+ field
1655
+ end
1656
+ end
1657
+ end
1658
+
1104
1659
  private
1660
+ def async
1661
+ spawn.async!
1662
+ end
1663
+
1664
+ def build_named_bound_sql_literal(statement, values)
1665
+ bound_values = values.transform_values do |value|
1666
+ if ActiveRecord::Relation === value
1667
+ Arel.sql(value.to_sql)
1668
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1669
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1670
+ values.empty? ? nil : values
1671
+ else
1672
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1673
+ value
1674
+ end
1675
+ end
1676
+
1677
+ begin
1678
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
1679
+ rescue Arel::BindError => error
1680
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1681
+ end
1682
+ end
1683
+
1684
+ def build_bound_sql_literal(statement, values)
1685
+ bound_values = values.map do |value|
1686
+ if ActiveRecord::Relation === value
1687
+ Arel.sql(value.to_sql)
1688
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1689
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1690
+ values.empty? ? nil : values
1691
+ else
1692
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1693
+ value
1694
+ end
1695
+ end
1696
+
1697
+ begin
1698
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
1699
+ rescue Arel::BindError => error
1700
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1701
+ end
1702
+ end
1703
+
1105
1704
  def lookup_table_klass_from_join_dependencies(table_name)
1106
1705
  each_join_dependencies do |join|
1107
1706
  return join.base_klass if table_name == join.table_name
@@ -1109,31 +1708,28 @@ module ActiveRecord
1109
1708
  nil
1110
1709
  end
1111
1710
 
1112
- def each_join_dependencies(join_dependencies = build_join_dependencies)
1711
+ def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
1113
1712
  join_dependencies.each do |join_dependency|
1114
- join_dependency.each do |join|
1115
- yield join
1116
- end
1713
+ join_dependency.each(&block)
1117
1714
  end
1118
1715
  end
1119
1716
 
1120
1717
  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?
1718
+ joins = joins_values | left_outer_joins_values
1719
+ joins |= eager_load_values unless eager_load_values.empty?
1720
+ joins |= includes_values unless includes_values.empty?
1124
1721
 
1125
1722
  join_dependencies = []
1126
1723
  join_dependencies.unshift construct_join_dependency(
1127
- select_association_list(associations, join_dependencies), nil
1724
+ select_named_joins(joins, join_dependencies), nil
1128
1725
  )
1129
1726
  end
1130
1727
 
1131
- def assert_mutability!
1132
- raise ImmutableRelation if @loaded
1133
- raise ImmutableRelation if defined?(@arel) && @arel
1728
+ def assert_modifiable!
1729
+ raise UnmodifiableRelation if @loaded || @arel
1134
1730
  end
1135
1731
 
1136
- def build_arel(aliases = nil)
1732
+ def build_arel(connection, aliases = nil)
1137
1733
  arel = Arel::SelectManager.new(table)
1138
1734
 
1139
1735
  build_joins(arel.join_sources, aliases)
@@ -1145,6 +1741,7 @@ module ActiveRecord
1145
1741
  arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1146
1742
 
1147
1743
  build_order(arel)
1744
+ build_with(arel)
1148
1745
  build_select(arel)
1149
1746
 
1150
1747
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
@@ -1155,14 +1752,6 @@ module ActiveRecord
1155
1752
  unless annotate_values.empty?
1156
1753
  annotates = annotate_values
1157
1754
  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
1755
  arel.comment(*annotates)
1167
1756
  end
1168
1757
 
@@ -1170,8 +1759,7 @@ module ActiveRecord
1170
1759
  end
1171
1760
 
1172
1761
  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)
1762
+ ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1175
1763
  end
1176
1764
 
1177
1765
  def build_from
@@ -1189,6 +1777,18 @@ module ActiveRecord
1189
1777
  end
1190
1778
  end
1191
1779
 
1780
+ def select_named_joins(join_names, stashed_joins = nil, &block)
1781
+ cte_joins, associations = join_names.partition do |join_name|
1782
+ Symbol === join_name && with_values.any? { _1.key?(join_name) }
1783
+ end
1784
+
1785
+ cte_joins.each do |cte_name|
1786
+ block&.call(CTEJoin.new(cte_name))
1787
+ end
1788
+
1789
+ select_association_list(associations, stashed_joins, &block)
1790
+ end
1791
+
1192
1792
  def select_association_list(associations, stashed_joins = nil)
1193
1793
  result = []
1194
1794
  associations.each do |association|
@@ -1204,20 +1804,21 @@ module ActiveRecord
1204
1804
  result
1205
1805
  end
1206
1806
 
1207
- class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1208
- end
1209
-
1210
1807
  def build_join_buckets
1211
1808
  buckets = Hash.new { |h, k| h[k] = [] }
1212
1809
 
1213
1810
  unless left_outer_joins_values.empty?
1214
1811
  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"
1812
+ left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
1813
+ if left_join.is_a?(CTEJoin)
1814
+ buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
1815
+ else
1816
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1817
+ end
1217
1818
  end
1218
1819
 
1219
1820
  if joins_values.empty?
1220
- buckets[:association_join] = left_joins
1821
+ buckets[:named_join] = left_joins
1221
1822
  buckets[:stashed_join] = stashed_left_joins
1222
1823
  return buckets, Arel::Nodes::OuterJoin
1223
1824
  else
@@ -1243,9 +1844,11 @@ module ActiveRecord
1243
1844
  end
1244
1845
  end
1245
1846
 
1246
- buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1847
+ buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
1247
1848
  if join.is_a?(Arel::Nodes::Join)
1248
1849
  buckets[:join_node] << join
1850
+ elsif join.is_a?(CTEJoin)
1851
+ buckets[:join_node] << build_with_join_node(join.name)
1249
1852
  else
1250
1853
  raise "unknown class: %s" % join.class.name
1251
1854
  end
@@ -1262,16 +1865,16 @@ module ActiveRecord
1262
1865
 
1263
1866
  buckets, join_type = build_join_buckets
1264
1867
 
1265
- association_joins = buckets[:association_join]
1266
- stashed_joins = buckets[:stashed_join]
1267
- leading_joins = buckets[:leading_join]
1268
- join_nodes = buckets[:join_node]
1868
+ named_joins = buckets[:named_join]
1869
+ stashed_joins = buckets[:stashed_join]
1870
+ leading_joins = buckets[:leading_join]
1871
+ join_nodes = buckets[:join_node]
1269
1872
 
1270
1873
  join_sources.concat(leading_joins) unless leading_joins.empty?
1271
1874
 
1272
- unless association_joins.empty? && stashed_joins.empty?
1875
+ unless named_joins.empty? && stashed_joins.empty?
1273
1876
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1274
- join_dependency = construct_join_dependency(association_joins, join_type)
1877
+ join_dependency = construct_join_dependency(named_joins, join_type)
1275
1878
  join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1276
1879
  end
1277
1880
 
@@ -1282,38 +1885,72 @@ module ActiveRecord
1282
1885
  def build_select(arel)
1283
1886
  if select_values.any?
1284
1887
  arel.project(*arel_columns(select_values))
1285
- elsif klass.ignored_columns.any?
1888
+ elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1286
1889
  arel.project(*klass.column_names.map { |field| table[field] })
1287
1890
  else
1288
1891
  arel.project(table[Arel.star])
1289
1892
  end
1290
1893
  end
1291
1894
 
1292
- def arel_columns(columns)
1293
- columns.flat_map do |field|
1294
- case field
1295
- when Symbol
1296
- arel_column(field.to_s) do |attr_name|
1297
- connection.quote_table_name(attr_name)
1298
- end
1299
- when String
1300
- arel_column(field, &:itself)
1301
- when Proc
1302
- field.call
1895
+ def build_with(arel)
1896
+ return if with_values.empty?
1897
+
1898
+ with_statements = with_values.map do |with_value|
1899
+ raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
1900
+
1901
+ build_with_value_from_hash(with_value)
1902
+ end
1903
+
1904
+ @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
1905
+ end
1906
+
1907
+ def build_with_value_from_hash(hash)
1908
+ hash.map do |name, value|
1909
+ Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
1910
+ end
1911
+ end
1912
+
1913
+ def build_with_expression_from_value(value, nested = false)
1914
+ case value
1915
+ when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1916
+ when ActiveRecord::Relation
1917
+ if nested
1918
+ value.arel.ast
1303
1919
  else
1304
- field
1920
+ value.arel
1305
1921
  end
1922
+ when Arel::SelectManager then value
1923
+ when Array
1924
+ return build_with_expression_from_value(value.first, false) if value.size == 1
1925
+
1926
+ parts = value.map do |query|
1927
+ build_with_expression_from_value(query, true)
1928
+ end
1929
+
1930
+ parts.reduce do |result, value|
1931
+ Arel::Nodes::UnionAll.new(result, value)
1932
+ end
1933
+ else
1934
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1306
1935
  end
1307
1936
  end
1308
1937
 
1938
+ def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
1939
+ with_table = Arel::Table.new(name)
1940
+
1941
+ table.join(with_table, kind).on(
1942
+ with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1943
+ ).join_sources.first
1944
+ end
1945
+
1309
1946
  def arel_column(field)
1310
1947
  field = klass.attribute_aliases[field] || field
1311
1948
  from = from_clause.name || from_clause.value
1312
1949
 
1313
1950
  if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1314
1951
  table[field]
1315
- elsif field.match?(/\A\w+\.\w+\z/)
1316
- table, column = field.split(".")
1952
+ elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
1953
+ self.references_values |= [Arel.sql(table, retryable: true)]
1317
1954
  predicate_builder.resolve_arel_attribute(table, column) do
1318
1955
  lookup_table_klass_from_join_dependencies(table)
1319
1956
  end
@@ -1324,7 +1961,7 @@ module ActiveRecord
1324
1961
 
1325
1962
  def table_name_matches?(from)
1326
1963
  table_name = Regexp.escape(table.name)
1327
- quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
1964
+ quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
1328
1965
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1329
1966
  end
1330
1967
 
@@ -1380,7 +2017,9 @@ module ActiveRecord
1380
2017
  args.each do |arg|
1381
2018
  next unless arg.is_a?(Hash)
1382
2019
  arg.each do |_key, value|
1383
- unless VALID_DIRECTIONS.include?(value)
2020
+ if value.is_a?(Hash)
2021
+ validate_order_args([value])
2022
+ elsif VALID_DIRECTIONS.exclude?(value)
1384
2023
  raise ArgumentError,
1385
2024
  "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1386
2025
  end
@@ -1388,10 +2027,14 @@ module ActiveRecord
1388
2027
  end
1389
2028
  end
1390
2029
 
2030
+ def flattened_args(args)
2031
+ args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
2032
+ end
2033
+
1391
2034
  def preprocess_order_args(order_args)
1392
2035
  @klass.disallow_raw_sql!(
1393
- order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1394
- permit: connection.column_name_with_order_matcher
2036
+ flattened_args(order_args),
2037
+ permit: model.adapter_class.column_name_with_order_matcher
1395
2038
  )
1396
2039
 
1397
2040
  validate_order_args(order_args)
@@ -1405,14 +2048,20 @@ module ActiveRecord
1405
2048
  when Symbol
1406
2049
  order_column(arg.to_s).asc
1407
2050
  when Hash
1408
- arg.map { |field, dir|
1409
- case field
1410
- when Arel::Nodes::SqlLiteral
1411
- field.public_send(dir.downcase)
2051
+ arg.map do |key, value|
2052
+ if value.is_a?(Hash)
2053
+ value.map do |field, dir|
2054
+ order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
2055
+ end
1412
2056
  else
1413
- order_column(field.to_s).public_send(dir.downcase)
2057
+ case key
2058
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
2059
+ key.public_send(value.downcase)
2060
+ else
2061
+ order_column(key.to_s).public_send(value.downcase)
2062
+ end
1414
2063
  end
1415
- }
2064
+ end
1416
2065
  else
1417
2066
  arg
1418
2067
  end
@@ -1423,14 +2072,35 @@ module ActiveRecord
1423
2072
  order_args.map! do |arg|
1424
2073
  klass.sanitize_sql_for_order(arg)
1425
2074
  end
1426
- order_args.flatten!
1427
- order_args.compact_blank!
1428
2075
  end
1429
2076
 
1430
2077
  def column_references(order_args)
1431
- references = order_args.grep(String)
1432
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1433
- references
2078
+ order_args.flat_map do |arg|
2079
+ case arg
2080
+ when String, Symbol
2081
+ extract_table_name_from(arg)
2082
+ when Hash
2083
+ arg
2084
+ .map do |key, value|
2085
+ case value
2086
+ when Hash
2087
+ key.to_s
2088
+ else
2089
+ extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
2090
+ end
2091
+ end
2092
+ when Arel::Attribute
2093
+ arg.relation.name
2094
+ when Arel::Nodes::Ordering
2095
+ if arg.expr.is_a?(Arel::Attribute)
2096
+ arg.expr.relation.name
2097
+ end
2098
+ end
2099
+ end.compact
2100
+ end
2101
+
2102
+ def extract_table_name_from(string)
2103
+ string.match(/^\W?(\w+)\W?\./) && $1
1434
2104
  end
1435
2105
 
1436
2106
  def order_column(field)
@@ -1438,11 +2108,20 @@ module ActiveRecord
1438
2108
  if attr_name == "count" && !group_values.empty?
1439
2109
  table[attr_name]
1440
2110
  else
1441
- Arel.sql(connection.quote_table_name(attr_name))
2111
+ Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
1442
2112
  end
1443
2113
  end
1444
2114
  end
1445
2115
 
2116
+ def build_case_for_value_position(column, values)
2117
+ node = Arel::Nodes::Case.new
2118
+ values.each.with_index(1) do |value, order|
2119
+ node.when(column.eq(value)).then(order)
2120
+ end
2121
+
2122
+ Arel::Nodes::Ascending.new(node)
2123
+ end
2124
+
1446
2125
  def resolve_arel_attributes(attrs)
1447
2126
  attrs.flat_map do |attr|
1448
2127
  case attr
@@ -1476,24 +2155,59 @@ module ActiveRecord
1476
2155
  # Post.references() # raises an error
1477
2156
  # Post.references([]) # does not raise an error
1478
2157
  #
1479
- # This particular method should be called with a method_name and the args
2158
+ # This particular method should be called with a method_name (__callee__) and the args
1480
2159
  # passed into that method as an input. For example:
1481
2160
  #
1482
2161
  # def references(*args)
1483
- # check_if_method_has_arguments!("references", args)
2162
+ # check_if_method_has_arguments!(__callee__, args)
1484
2163
  # ...
1485
2164
  # end
1486
2165
  def check_if_method_has_arguments!(method_name, args, message = nil)
1487
2166
  if args.blank?
1488
2167
  raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1489
- elsif block_given?
1490
- yield args
1491
2168
  else
2169
+ yield args if block_given?
2170
+
1492
2171
  args.flatten!
1493
2172
  args.compact_blank!
1494
2173
  end
1495
2174
  end
1496
2175
 
2176
+ def process_select_args(fields)
2177
+ fields.flat_map do |field|
2178
+ if field.is_a?(Hash)
2179
+ arel_columns_from_hash(field)
2180
+ else
2181
+ field
2182
+ end
2183
+ end
2184
+ end
2185
+
2186
+ def arel_columns_from_hash(fields)
2187
+ fields.flat_map do |key, columns_aliases|
2188
+ case columns_aliases
2189
+ when Hash
2190
+ columns_aliases.map do |column, column_alias|
2191
+ if values[:joins]&.include?(key)
2192
+ references = PredicateBuilder.references({ key.to_s => fields[key] })
2193
+ self.references_values |= references unless references.empty?
2194
+ end
2195
+ arel_column("#{key}.#{column}") do
2196
+ predicate_builder.resolve_arel_attribute(key.to_s, column)
2197
+ end.as(column_alias.to_s)
2198
+ end
2199
+ when Array
2200
+ columns_aliases.map do |column|
2201
+ arel_column("#{key}.#{column}", &:itself)
2202
+ end
2203
+ when String, Symbol
2204
+ arel_column(key.to_s) do
2205
+ predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
2206
+ end.as(columns_aliases.to_s)
2207
+ end
2208
+ end
2209
+ end
2210
+
1497
2211
  STRUCTURAL_VALUE_METHODS = (
1498
2212
  Relation::VALUE_METHODS -
1499
2213
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
@@ -1512,11 +2226,4 @@ module ActiveRecord
1512
2226
  end
1513
2227
  end
1514
2228
  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
2229
  end