activerecord 5.2.8 → 7.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (364) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1393 -587
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +10 -9
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +122 -47
  10. data/lib/active_record/associations/association_scope.rb +24 -24
  11. data/lib/active_record/associations/belongs_to_association.rb +67 -49
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -7
  13. data/lib/active_record/associations/builder/association.rb +52 -23
  14. data/lib/active_record/associations/builder/belongs_to.rb +44 -61
  15. data/lib/active_record/associations/builder/collection_association.rb +17 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +10 -3
  18. data/lib/active_record/associations/builder/has_one.rb +35 -3
  19. data/lib/active_record/associations/builder/singular_association.rb +5 -3
  20. data/lib/active_record/associations/collection_association.rb +59 -50
  21. data/lib/active_record/associations/collection_proxy.rb +32 -23
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +27 -14
  25. data/lib/active_record/associations/has_many_through_association.rb +26 -19
  26. data/lib/active_record/associations/has_one_association.rb +52 -37
  27. data/lib/active_record/associations/has_one_through_association.rb +6 -6
  28. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  30. data/lib/active_record/associations/join_dependency.rb +97 -62
  31. data/lib/active_record/associations/preloader/association.rb +220 -60
  32. data/lib/active_record/associations/preloader/batch.rb +48 -0
  33. data/lib/active_record/associations/preloader/branch.rb +147 -0
  34. data/lib/active_record/associations/preloader/through_association.rb +85 -40
  35. data/lib/active_record/associations/preloader.rb +44 -105
  36. data/lib/active_record/associations/singular_association.rb +9 -17
  37. data/lib/active_record/associations/through_association.rb +4 -4
  38. data/lib/active_record/associations.rb +207 -66
  39. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  40. data/lib/active_record/attribute_assignment.rb +17 -19
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +19 -8
  42. data/lib/active_record/attribute_methods/dirty.rb +141 -47
  43. data/lib/active_record/attribute_methods/primary_key.rb +22 -27
  44. data/lib/active_record/attribute_methods/query.rb +6 -10
  45. data/lib/active_record/attribute_methods/read.rb +15 -55
  46. data/lib/active_record/attribute_methods/serialization.rb +77 -18
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +16 -18
  48. data/lib/active_record/attribute_methods/write.rb +18 -37
  49. data/lib/active_record/attribute_methods.rb +90 -153
  50. data/lib/active_record/attributes.rb +38 -12
  51. data/lib/active_record/autosave_association.rb +50 -50
  52. data/lib/active_record/base.rb +23 -18
  53. data/lib/active_record/callbacks.rb +159 -44
  54. data/lib/active_record/coders/yaml_column.rb +12 -3
  55. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +92 -464
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -51
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +209 -164
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +38 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +103 -82
  63. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +140 -110
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -94
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +16 -5
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +456 -159
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -162
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +311 -327
  71. data/lib/active_record/connection_adapters/column.rb +33 -11
  72. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  73. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  74. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  75. data/lib/active_record/connection_adapters/mysql/database_statements.rb +113 -45
  76. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  77. data/lib/active_record/connection_adapters/mysql/quoting.rb +71 -5
  78. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  80. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +25 -8
  81. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +143 -19
  82. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  83. data/lib/active_record/connection_adapters/mysql2_adapter.rb +63 -22
  84. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  85. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +53 -28
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +56 -63
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +54 -16
  95. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +26 -12
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -52
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -2
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +39 -4
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +128 -91
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -1
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +149 -113
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +386 -182
  116. data/lib/active_record/connection_adapters/schema_cache.rb +161 -22
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +152 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +65 -18
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +92 -26
  122. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +251 -204
  123. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  124. data/lib/active_record/connection_adapters.rb +53 -0
  125. data/lib/active_record/connection_handling.rb +292 -38
  126. data/lib/active_record/core.rb +385 -158
  127. data/lib/active_record/counter_cache.rb +8 -30
  128. data/lib/active_record/database_configurations/connection_url_resolver.rb +100 -0
  129. data/lib/active_record/database_configurations/database_config.rb +83 -0
  130. data/lib/active_record/database_configurations/hash_config.rb +154 -0
  131. data/lib/active_record/database_configurations/url_config.rb +53 -0
  132. data/lib/active_record/database_configurations.rb +256 -0
  133. data/lib/active_record/delegated_type.rb +250 -0
  134. data/lib/active_record/destroy_association_async_job.rb +36 -0
  135. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  136. data/lib/active_record/dynamic_matchers.rb +4 -5
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +44 -0
  140. data/lib/active_record/encryption/configurable.rb +61 -0
  141. data/lib/active_record/encryption/context.rb +35 -0
  142. data/lib/active_record/encryption/contexts.rb +72 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -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 +155 -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 +160 -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 +42 -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_serializer.rb +90 -0
  159. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  160. data/lib/active_record/encryption/properties.rb +76 -0
  161. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  162. data/lib/active_record/encryption/scheme.rb +99 -0
  163. data/lib/active_record/encryption.rb +55 -0
  164. data/lib/active_record/enum.rb +130 -51
  165. data/lib/active_record/errors.rb +129 -23
  166. data/lib/active_record/explain.rb +10 -6
  167. data/lib/active_record/explain_registry.rb +11 -6
  168. data/lib/active_record/explain_subscriber.rb +1 -1
  169. data/lib/active_record/fixture_set/file.rb +22 -15
  170. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  171. data/lib/active_record/fixture_set/render_context.rb +17 -0
  172. data/lib/active_record/fixture_set/table_row.rb +187 -0
  173. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  174. data/lib/active_record/fixtures.rb +206 -490
  175. data/lib/active_record/future_result.rb +139 -0
  176. data/lib/active_record/gem_version.rb +3 -3
  177. data/lib/active_record/inheritance.rb +104 -37
  178. data/lib/active_record/insert_all.rb +278 -0
  179. data/lib/active_record/integration.rb +69 -18
  180. data/lib/active_record/internal_metadata.rb +24 -9
  181. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  182. data/lib/active_record/locking/optimistic.rb +41 -26
  183. data/lib/active_record/locking/pessimistic.rb +18 -8
  184. data/lib/active_record/log_subscriber.rb +46 -35
  185. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  186. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  187. data/lib/active_record/middleware/database_selector.rb +82 -0
  188. data/lib/active_record/middleware/shard_selector.rb +60 -0
  189. data/lib/active_record/migration/command_recorder.rb +96 -44
  190. data/lib/active_record/migration/compatibility.rb +246 -64
  191. data/lib/active_record/migration/join_table.rb +1 -2
  192. data/lib/active_record/migration.rb +266 -187
  193. data/lib/active_record/model_schema.rb +165 -52
  194. data/lib/active_record/nested_attributes.rb +17 -19
  195. data/lib/active_record/no_touching.rb +11 -4
  196. data/lib/active_record/null_relation.rb +2 -7
  197. data/lib/active_record/persistence.rb +467 -92
  198. data/lib/active_record/query_cache.rb +21 -4
  199. data/lib/active_record/query_logs.rb +138 -0
  200. data/lib/active_record/querying.rb +51 -24
  201. data/lib/active_record/railtie.rb +224 -57
  202. data/lib/active_record/railties/console_sandbox.rb +2 -4
  203. data/lib/active_record/railties/controller_runtime.rb +31 -36
  204. data/lib/active_record/railties/databases.rake +369 -101
  205. data/lib/active_record/readonly_attributes.rb +15 -0
  206. data/lib/active_record/reflection.rb +170 -137
  207. data/lib/active_record/relation/batches/batch_enumerator.rb +44 -14
  208. data/lib/active_record/relation/batches.rb +46 -37
  209. data/lib/active_record/relation/calculations.rb +168 -96
  210. data/lib/active_record/relation/delegation.rb +37 -52
  211. data/lib/active_record/relation/finder_methods.rb +79 -58
  212. data/lib/active_record/relation/from_clause.rb +5 -1
  213. data/lib/active_record/relation/merger.rb +50 -51
  214. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  215. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  216. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  217. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  218. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  219. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  220. data/lib/active_record/relation/predicate_builder.rb +58 -46
  221. data/lib/active_record/relation/query_attribute.rb +9 -10
  222. data/lib/active_record/relation/query_methods.rb +685 -208
  223. data/lib/active_record/relation/record_fetch_warning.rb +9 -11
  224. data/lib/active_record/relation/spawn_methods.rb +10 -10
  225. data/lib/active_record/relation/where_clause.rb +108 -64
  226. data/lib/active_record/relation.rb +515 -151
  227. data/lib/active_record/result.rb +78 -42
  228. data/lib/active_record/runtime_registry.rb +9 -13
  229. data/lib/active_record/sanitization.rb +29 -44
  230. data/lib/active_record/schema.rb +37 -31
  231. data/lib/active_record/schema_dumper.rb +74 -23
  232. data/lib/active_record/schema_migration.rb +7 -9
  233. data/lib/active_record/scoping/default.rb +62 -17
  234. data/lib/active_record/scoping/named.rb +17 -32
  235. data/lib/active_record/scoping.rb +70 -41
  236. data/lib/active_record/secure_token.rb +16 -8
  237. data/lib/active_record/serialization.rb +6 -4
  238. data/lib/active_record/signed_id.rb +116 -0
  239. data/lib/active_record/statement_cache.rb +49 -6
  240. data/lib/active_record/store.rb +88 -9
  241. data/lib/active_record/suppressor.rb +13 -17
  242. data/lib/active_record/table_metadata.rb +42 -43
  243. data/lib/active_record/tasks/database_tasks.rb +352 -94
  244. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  245. data/lib/active_record/tasks/postgresql_database_tasks.rb +41 -39
  246. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  247. data/lib/active_record/test_databases.rb +24 -0
  248. data/lib/active_record/test_fixtures.rb +287 -0
  249. data/lib/active_record/timestamp.rb +44 -34
  250. data/lib/active_record/touch_later.rb +23 -22
  251. data/lib/active_record/transactions.rb +67 -128
  252. data/lib/active_record/translation.rb +3 -3
  253. data/lib/active_record/type/adapter_specific_registry.rb +34 -19
  254. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  255. data/lib/active_record/type/internal/timezone.rb +2 -2
  256. data/lib/active_record/type/serialized.rb +7 -4
  257. data/lib/active_record/type/time.rb +10 -0
  258. data/lib/active_record/type/type_map.rb +17 -21
  259. data/lib/active_record/type/unsigned_integer.rb +0 -1
  260. data/lib/active_record/type.rb +9 -5
  261. data/lib/active_record/type_caster/connection.rb +15 -15
  262. data/lib/active_record/type_caster/map.rb +8 -8
  263. data/lib/active_record/validations/associated.rb +2 -3
  264. data/lib/active_record/validations/numericality.rb +35 -0
  265. data/lib/active_record/validations/uniqueness.rb +39 -31
  266. data/lib/active_record/validations.rb +4 -3
  267. data/lib/active_record.rb +209 -32
  268. data/lib/arel/alias_predication.rb +9 -0
  269. data/lib/arel/attributes/attribute.rb +33 -0
  270. data/lib/arel/collectors/bind.rb +29 -0
  271. data/lib/arel/collectors/composite.rb +39 -0
  272. data/lib/arel/collectors/plain_string.rb +20 -0
  273. data/lib/arel/collectors/sql_string.rb +27 -0
  274. data/lib/arel/collectors/substitute_binds.rb +35 -0
  275. data/lib/arel/crud.rb +48 -0
  276. data/lib/arel/delete_manager.rb +32 -0
  277. data/lib/arel/errors.rb +9 -0
  278. data/lib/arel/expressions.rb +29 -0
  279. data/lib/arel/factory_methods.rb +49 -0
  280. data/lib/arel/filter_predications.rb +9 -0
  281. data/lib/arel/insert_manager.rb +48 -0
  282. data/lib/arel/math.rb +45 -0
  283. data/lib/arel/nodes/and.rb +32 -0
  284. data/lib/arel/nodes/ascending.rb +23 -0
  285. data/lib/arel/nodes/binary.rb +126 -0
  286. data/lib/arel/nodes/bind_param.rb +44 -0
  287. data/lib/arel/nodes/case.rb +55 -0
  288. data/lib/arel/nodes/casted.rb +62 -0
  289. data/lib/arel/nodes/comment.rb +29 -0
  290. data/lib/arel/nodes/count.rb +12 -0
  291. data/lib/arel/nodes/delete_statement.rb +44 -0
  292. data/lib/arel/nodes/descending.rb +23 -0
  293. data/lib/arel/nodes/equality.rb +15 -0
  294. data/lib/arel/nodes/extract.rb +24 -0
  295. data/lib/arel/nodes/false.rb +16 -0
  296. data/lib/arel/nodes/filter.rb +10 -0
  297. data/lib/arel/nodes/full_outer_join.rb +8 -0
  298. data/lib/arel/nodes/function.rb +45 -0
  299. data/lib/arel/nodes/grouping.rb +11 -0
  300. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  301. data/lib/arel/nodes/in.rb +15 -0
  302. data/lib/arel/nodes/infix_operation.rb +92 -0
  303. data/lib/arel/nodes/inner_join.rb +8 -0
  304. data/lib/arel/nodes/insert_statement.rb +37 -0
  305. data/lib/arel/nodes/join_source.rb +20 -0
  306. data/lib/arel/nodes/matches.rb +18 -0
  307. data/lib/arel/nodes/named_function.rb +23 -0
  308. data/lib/arel/nodes/node.rb +51 -0
  309. data/lib/arel/nodes/node_expression.rb +13 -0
  310. data/lib/arel/nodes/ordering.rb +27 -0
  311. data/lib/arel/nodes/outer_join.rb +8 -0
  312. data/lib/arel/nodes/over.rb +15 -0
  313. data/lib/arel/nodes/regexp.rb +16 -0
  314. data/lib/arel/nodes/right_outer_join.rb +8 -0
  315. data/lib/arel/nodes/select_core.rb +67 -0
  316. data/lib/arel/nodes/select_statement.rb +41 -0
  317. data/lib/arel/nodes/sql_literal.rb +19 -0
  318. data/lib/arel/nodes/string_join.rb +11 -0
  319. data/lib/arel/nodes/table_alias.rb +31 -0
  320. data/lib/arel/nodes/terminal.rb +16 -0
  321. data/lib/arel/nodes/true.rb +16 -0
  322. data/lib/arel/nodes/unary.rb +44 -0
  323. data/lib/arel/nodes/unary_operation.rb +20 -0
  324. data/lib/arel/nodes/unqualified_column.rb +22 -0
  325. data/lib/arel/nodes/update_statement.rb +46 -0
  326. data/lib/arel/nodes/values_list.rb +9 -0
  327. data/lib/arel/nodes/window.rb +126 -0
  328. data/lib/arel/nodes/with.rb +11 -0
  329. data/lib/arel/nodes.rb +71 -0
  330. data/lib/arel/order_predications.rb +13 -0
  331. data/lib/arel/predications.rb +258 -0
  332. data/lib/arel/select_manager.rb +276 -0
  333. data/lib/arel/table.rb +117 -0
  334. data/lib/arel/tree_manager.rb +60 -0
  335. data/lib/arel/update_manager.rb +48 -0
  336. data/lib/arel/visitors/dot.rb +298 -0
  337. data/lib/arel/visitors/mysql.rb +99 -0
  338. data/lib/arel/visitors/postgresql.rb +110 -0
  339. data/lib/arel/visitors/sqlite.rb +38 -0
  340. data/lib/arel/visitors/to_sql.rb +955 -0
  341. data/lib/arel/visitors/visitor.rb +45 -0
  342. data/lib/arel/visitors.rb +13 -0
  343. data/lib/arel/window_predications.rb +9 -0
  344. data/lib/arel.rb +55 -0
  345. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  346. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  347. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  348. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  349. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  350. data/lib/rails/generators/active_record/migration.rb +19 -2
  351. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  352. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  353. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  354. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  355. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  356. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  357. metadata +162 -32
  358. data/lib/active_record/attribute_decorators.rb +0 -90
  359. data/lib/active_record/collection_cache_key.rb +0 -53
  360. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  361. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  362. data/lib/active_record/define_callbacks.rb +0 -22
  363. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  364. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -5,12 +5,13 @@ module ActiveRecord
5
5
  class Relation
6
6
  MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
7
7
  :order, :joins, :left_outer_joins, :references,
8
- :extending, :unscope]
8
+ :extending, :unscope, :optimizer_hints, :annotate]
9
9
 
10
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
10
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :strict_loading,
11
11
  :reverse_order, :distinct, :create_with, :skip_query_cache]
12
+
12
13
  CLAUSE_METHODS = [:where, :having, :from]
13
- INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having]
14
+ INVALID_METHODS_FOR_DELETE_ALL = [:distinct]
14
15
 
15
16
  VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
16
17
 
@@ -18,6 +19,7 @@ module ActiveRecord
18
19
  include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
19
20
 
20
21
  attr_reader :table, :klass, :loaded, :predicate_builder
22
+ attr_accessor :skip_preloading_value
21
23
  alias :model :klass
22
24
  alias :loaded? :loaded
23
25
  alias :locked? :lock_value
@@ -26,10 +28,11 @@ module ActiveRecord
26
28
  @klass = klass
27
29
  @table = table
28
30
  @values = values
29
- @offsets = {}
30
31
  @loaded = false
31
32
  @predicate_builder = predicate_builder
32
33
  @delegate_to_klass = false
34
+ @future_result = nil
35
+ @records = nil
33
36
  end
34
37
 
35
38
  def initialize_copy(other)
@@ -37,8 +40,15 @@ module ActiveRecord
37
40
  reset
38
41
  end
39
42
 
40
- def arel_attribute(name) # :nodoc:
41
- klass.arel_attribute(name, table)
43
+ def bind_attribute(name, value) # :nodoc:
44
+ if reflection = klass._reflect_on_association(name)
45
+ name = reflection.foreign_key
46
+ value = value.read_attribute(reflection.klass.primary_key) unless value.nil?
47
+ end
48
+
49
+ attr = table[name]
50
+ bind = predicate_builder.build_bind_attribute(attr.name, value)
51
+ yield attr, bind
42
52
  end
43
53
 
44
54
  # Initializes new record from relation while maintaining the current
@@ -54,9 +64,13 @@ module ActiveRecord
54
64
  # user = users.new { |user| user.name = 'Oscar' }
55
65
  # user.name # => Oscar
56
66
  def new(attributes = nil, &block)
57
- scoping { klass.new(values_for_create(attributes), &block) }
67
+ if attributes.is_a?(Array)
68
+ attributes.collect { |attr| new(attr, &block) }
69
+ else
70
+ block = current_scope_restoring_block(&block)
71
+ scoping { _new(attributes, &block) }
72
+ end
58
73
  end
59
-
60
74
  alias build new
61
75
 
62
76
  # Tries to create a new record with the same scoped attributes
@@ -82,7 +96,8 @@ module ActiveRecord
82
96
  if attributes.is_a?(Array)
83
97
  attributes.collect { |attr| create(attr, &block) }
84
98
  else
85
- scoping { klass.create(values_for_create(attributes), &block) }
99
+ block = current_scope_restoring_block(&block)
100
+ scoping { _create(attributes, &block) }
86
101
  end
87
102
  end
88
103
 
@@ -96,7 +111,8 @@ module ActiveRecord
96
111
  if attributes.is_a?(Array)
97
112
  attributes.collect { |attr| create!(attr, &block) }
98
113
  else
99
- scoping { klass.create!(values_for_create(attributes), &block) }
114
+ block = current_scope_restoring_block(&block)
115
+ scoping { _create!(attributes, &block) }
100
116
  end
101
117
  end
102
118
 
@@ -133,7 +149,7 @@ module ActiveRecord
133
149
  # above can be alternatively written this way:
134
150
  #
135
151
  # # Find the first user named "Scarlett" or create a new one with a
136
- # # different last name.
152
+ # # particular last name.
137
153
  # User.find_or_create_by(first_name: 'Scarlett') do |user|
138
154
  # user.last_name = 'Johansson'
139
155
  # end
@@ -143,23 +159,12 @@ module ActiveRecord
143
159
  # failed due to validation errors it won't be persisted, you get what
144
160
  # #create returns in such situation.
145
161
  #
146
- # Please note *this method is not atomic*, it runs first a SELECT, and if
162
+ # Please note <b>this method is not atomic</b>, it runs first a SELECT, and if
147
163
  # there are no results an INSERT is attempted. If there are other threads
148
164
  # or processes there is a race condition between both calls and it could
149
165
  # be the case that you end up with two similar records.
150
166
  #
151
- # Whether that is a problem or not depends on the logic of the
152
- # application, but in the particular case in which rows have a UNIQUE
153
- # constraint an exception may be raised, just retry:
154
- #
155
- # begin
156
- # CreditAccount.transaction(requires_new: true) do
157
- # CreditAccount.find_or_create_by(user_id: user.id)
158
- # end
159
- # rescue ActiveRecord::RecordNotUnique
160
- # retry
161
- # end
162
- #
167
+ # If this might be a problem for your application, please see #create_or_find_by.
163
168
  def find_or_create_by(attributes, &block)
164
169
  find_by(attributes) || create(attributes, &block)
165
170
  end
@@ -171,6 +176,51 @@ module ActiveRecord
171
176
  find_by(attributes) || create!(attributes, &block)
172
177
  end
173
178
 
179
+ # Attempts to create a record with the given attributes in a table that has a unique database constraint
180
+ # on one or several of its columns. If a row already exists with one or several of these
181
+ # unique constraints, the exception such an insertion would normally raise is caught,
182
+ # and the existing record with those attributes is found using #find_by!.
183
+ #
184
+ # This is similar to #find_or_create_by, but avoids the problem of stale reads between the SELECT
185
+ # and the INSERT, as that method needs to first query the table, then attempt to insert a row
186
+ # if none is found.
187
+ #
188
+ # There are several drawbacks to #create_or_find_by, though:
189
+ #
190
+ # * The underlying table must have the relevant columns defined with unique database constraints.
191
+ # * A unique constraint violation may be triggered by only one, or at least less than all,
192
+ # of the given attributes. This means that the subsequent #find_by! may fail to find a
193
+ # matching record, which will then raise an <tt>ActiveRecord::RecordNotFound</tt> exception,
194
+ # rather than a record with the given attributes.
195
+ # * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by,
196
+ # we actually have another race condition between INSERT -> SELECT, which can be triggered
197
+ # if a DELETE between those two statements is run by another client. But for most applications,
198
+ # that's a significantly less likely condition to hit.
199
+ # * It relies on exception handling to handle control flow, which may be marginally slower.
200
+ # * The primary key may auto-increment on each create, even if it fails. This can accelerate
201
+ # the problem of running out of integers, if the underlying table is still stuck on a primary
202
+ # key of type int (note: All Rails apps since 5.1+ have defaulted to bigint, which is not liable
203
+ # to this problem).
204
+ #
205
+ # This method will return a record if all given attributes are covered by unique constraints
206
+ # (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted
207
+ # and failed due to validation errors it won't be persisted, you get what #create returns in
208
+ # such situation.
209
+ def create_or_find_by(attributes, &block)
210
+ transaction(requires_new: true) { create(attributes, &block) }
211
+ rescue ActiveRecord::RecordNotUnique
212
+ find_by!(attributes)
213
+ end
214
+
215
+ # Like #create_or_find_by, but calls
216
+ # {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
217
+ # is raised if the created record is invalid.
218
+ def create_or_find_by!(attributes, &block)
219
+ transaction(requires_new: true) { create!(attributes, &block) }
220
+ rescue ActiveRecord::RecordNotUnique
221
+ find_by!(attributes)
222
+ end
223
+
174
224
  # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
175
225
  # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
176
226
  def find_or_initialize_by(attributes, &block)
@@ -185,7 +235,7 @@ module ActiveRecord
185
235
  # are needed by the next ones when eager loading is going on.
186
236
  #
187
237
  # Please see further details in the
188
- # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
238
+ # {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
189
239
  def explain
190
240
  exec_explain(collecting_queries_for_explain { exec_queries })
191
241
  end
@@ -208,13 +258,20 @@ module ActiveRecord
208
258
 
209
259
  # Returns size of the records.
210
260
  def size
211
- loaded? ? @records.length : count(:all)
261
+ if loaded?
262
+ records.length
263
+ else
264
+ count(:all)
265
+ end
212
266
  end
213
267
 
214
268
  # Returns true if there are no records.
215
269
  def empty?
216
- return @records.empty? if loaded?
217
- !exists?
270
+ if loaded?
271
+ records.empty?
272
+ else
273
+ !exists?
274
+ end
218
275
  end
219
276
 
220
277
  # Returns true if there are no records.
@@ -232,39 +289,119 @@ module ActiveRecord
232
289
  # Returns true if there is exactly one record.
233
290
  def one?
234
291
  return super if block_given?
235
- limit_value ? records.one? : size == 1
292
+ return records.one? if loaded?
293
+ limited_count == 1
236
294
  end
237
295
 
238
296
  # Returns true if there is more than one record.
239
297
  def many?
240
298
  return super if block_given?
241
- limit_value ? records.many? : size > 1
299
+ return records.many? if loaded?
300
+ limited_count > 1
242
301
  end
243
302
 
244
- # Returns a cache key that can be used to identify the records fetched by
245
- # this query. The cache key is built with a fingerprint of the sql query,
246
- # the number of records matched by the query and a timestamp of the last
247
- # updated record. When a new record comes to match the query, or any of
248
- # the existing records is updated or deleted, the cache key changes.
303
+ # Returns a stable cache key that can be used to identify this query.
304
+ # The cache key is built with a fingerprint of the SQL query.
249
305
  #
250
- # Product.where("name like ?", "%Cosmic Encounter%").cache_key
251
- # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
306
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
307
+ # # => "products/query-1850ab3d302391b85b8693e941286659"
252
308
  #
253
- # If the collection is loaded, the method will iterate through the records
254
- # to generate the timestamp, otherwise it will trigger one SQL query like:
309
+ # If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
310
+ # in Rails 6.0 and earlier, the cache key will also include a version.
255
311
  #
256
- # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
312
+ # ActiveRecord::Base.collection_cache_versioning = false
313
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
314
+ # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
257
315
  #
258
316
  # You can also pass a custom timestamp column to fetch the timestamp of the
259
317
  # last updated record.
260
318
  #
261
319
  # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
262
- #
263
- # You can customize the strategy to generate the key on a per model basis
264
- # overriding ActiveRecord::Base#collection_cache_key.
265
- def cache_key(timestamp_column = :updated_at)
320
+ def cache_key(timestamp_column = "updated_at")
266
321
  @cache_keys ||= {}
267
- @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column)
322
+ @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
323
+ end
324
+
325
+ def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
326
+ query_signature = ActiveSupport::Digest.hexdigest(to_sql)
327
+ key = "#{klass.model_name.cache_key}/query-#{query_signature}"
328
+
329
+ if collection_cache_versioning
330
+ key
331
+ else
332
+ "#{key}-#{compute_cache_version(timestamp_column)}"
333
+ end
334
+ end
335
+ private :compute_cache_key
336
+
337
+ # Returns a cache version that can be used together with the cache key to form
338
+ # a recyclable caching scheme. The cache version is built with the number of records
339
+ # matching the query, and the timestamp of the last updated record. When a new record
340
+ # comes to match the query, or any of the existing records is updated or deleted,
341
+ # the cache version changes.
342
+ #
343
+ # If the collection is loaded, the method will iterate through the records
344
+ # to generate the timestamp, otherwise it will trigger one SQL query like:
345
+ #
346
+ # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
347
+ def cache_version(timestamp_column = :updated_at)
348
+ if collection_cache_versioning
349
+ @cache_versions ||= {}
350
+ @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
351
+ end
352
+ end
353
+
354
+ def compute_cache_version(timestamp_column) # :nodoc:
355
+ timestamp_column = timestamp_column.to_s
356
+
357
+ if loaded?
358
+ size = records.size
359
+ if size > 0
360
+ timestamp = records.map { |record| record.read_attribute(timestamp_column) }.max
361
+ end
362
+ else
363
+ collection = eager_loading? ? apply_join_dependency : self
364
+
365
+ column = connection.visitor.compile(table[timestamp_column])
366
+ select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
367
+
368
+ if collection.has_limit_or_offset?
369
+ query = collection.select("#{column} AS collection_cache_key_timestamp")
370
+ query._select!(table[Arel.star]) if distinct_value && collection.select_values.empty?
371
+ subquery_alias = "subquery_for_cache_key"
372
+ subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
373
+ arel = query.build_subquery(subquery_alias, select_values % subquery_column)
374
+ else
375
+ query = collection.unscope(:order)
376
+ query.select_values = [select_values % column]
377
+ arel = query.arel
378
+ end
379
+
380
+ size, timestamp = connection.select_rows(arel, nil).first
381
+
382
+ if size
383
+ column_type = klass.type_for_attribute(timestamp_column)
384
+ timestamp = column_type.deserialize(timestamp)
385
+ else
386
+ size = 0
387
+ end
388
+ end
389
+
390
+ if timestamp
391
+ "#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
392
+ else
393
+ "#{size}"
394
+ end
395
+ end
396
+ private :compute_cache_version
397
+
398
+ # Returns a cache key along with the version.
399
+ def cache_key_with_version
400
+ if version = cache_version
401
+ "#{cache_key}-#{version}"
402
+ else
403
+ cache_key
404
+ end
268
405
  end
269
406
 
270
407
  # Scope all queries to the current scope.
@@ -274,18 +411,28 @@ module ActiveRecord
274
411
  # end
275
412
  # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
276
413
  #
414
+ # If <tt>all_queries: true</tt> is passed, scoping will apply to all queries
415
+ # for the relation including +update+ and +delete+ on instances.
416
+ # Once +all_queries+ is set to true it cannot be set to false in a
417
+ # nested block.
418
+ #
277
419
  # Please check unscoped if you want to remove all previous scopes (including
278
420
  # the default_scope) during the execution of a block.
279
- def scoping
280
- previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass
281
- yield
282
- ensure
283
- klass.current_scope = previous unless @delegate_to_klass
421
+ def scoping(all_queries: nil, &block)
422
+ registry = klass.scope_registry
423
+ if global_scope?(registry) && all_queries == false
424
+ raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
425
+ elsif already_in_scope?(registry)
426
+ yield
427
+ else
428
+ _scoping(self, registry, all_queries, &block)
429
+ end
284
430
  end
285
431
 
286
432
  def _exec_scope(*args, &block) # :nodoc:
287
433
  @delegate_to_klass = true
288
- instance_exec(*args, &block) || self
434
+ registry = klass.scope_registry
435
+ _scoping(nil, registry) { instance_exec(*args, &block) || self }
289
436
  ensure
290
437
  @delegate_to_klass = false
291
438
  end
@@ -293,7 +440,9 @@ module ActiveRecord
293
440
  # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
294
441
  # statement and sends it straight to the database. It does not instantiate the involved models and it does not
295
442
  # trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
296
- # Active Record's normal type casting and serialization.
443
+ # Active Record's normal type casting and serialization. Returns the number of rows affected.
444
+ #
445
+ # Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns.
297
446
  #
298
447
  # ==== Parameters
299
448
  #
@@ -315,26 +464,25 @@ module ActiveRecord
315
464
  def update_all(updates)
316
465
  raise ArgumentError, "Empty list of attributes to change" if updates.blank?
317
466
 
318
- if eager_loading?
319
- relation = apply_join_dependency
320
- return relation.update_all(updates)
321
- end
322
-
323
- stmt = Arel::UpdateManager.new
324
-
325
- stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates))
326
- stmt.table(table)
327
-
328
- if has_join_values? || offset_value
329
- @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
467
+ if updates.is_a?(Hash)
468
+ if klass.locking_enabled? &&
469
+ !updates.key?(klass.locking_column) &&
470
+ !updates.key?(klass.locking_column.to_sym)
471
+ attr = table[klass.locking_column]
472
+ updates[attr.name] = _increment_attribute(attr)
473
+ end
474
+ values = _substitute_values(updates)
330
475
  else
331
- stmt.key = arel_attribute(primary_key)
332
- stmt.take(arel.limit)
333
- stmt.order(*arel.orders)
334
- stmt.wheres = arel.constraints
476
+ values = Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
335
477
  end
336
478
 
337
- @klass.connection.update stmt, "#{@klass} Update All"
479
+ arel = eager_loading? ? apply_join_dependency.arel : build_arel
480
+ arel.source.left = table
481
+
482
+ group_values_arel_columns = arel_columns(group_values.uniq)
483
+ having_clause_ast = having_clause.ast unless having_clause.empty?
484
+ stmt = arel.compile_update(values, table[primary_key], having_clause_ast, group_values_arel_columns)
485
+ klass.connection.update(stmt, "#{klass} Update All").tap { reset }
338
486
  end
339
487
 
340
488
  def update(id = :all, attributes) # :nodoc:
@@ -345,6 +493,73 @@ module ActiveRecord
345
493
  end
346
494
  end
347
495
 
496
+ def update!(id = :all, attributes) # :nodoc:
497
+ if id == :all
498
+ each { |record| record.update!(attributes) }
499
+ else
500
+ klass.update!(id, attributes)
501
+ end
502
+ end
503
+
504
+ # Updates the counters of the records in the current relation.
505
+ #
506
+ # ==== Parameters
507
+ #
508
+ # * +counter+ - A Hash containing the names of the fields to update as keys and the amount to update as values.
509
+ # * <tt>:touch</tt> option - Touch the timestamp columns when updating.
510
+ # * If attributes names are passed, they are updated along with update_at/on attributes.
511
+ #
512
+ # ==== Examples
513
+ #
514
+ # # For Posts by a given author increment the comment_count by 1.
515
+ # Post.where(author_id: author.id).update_counters(comment_count: 1)
516
+ def update_counters(counters)
517
+ touch = counters.delete(:touch)
518
+
519
+ updates = {}
520
+ counters.each do |counter_name, value|
521
+ attr = table[counter_name]
522
+ updates[attr.name] = _increment_attribute(attr, value)
523
+ end
524
+
525
+ if touch
526
+ names = touch if touch != true
527
+ names = Array.wrap(names)
528
+ options = names.extract_options!
529
+ touch_updates = klass.touch_attributes_with_time(*names, **options)
530
+ updates.merge!(touch_updates) unless touch_updates.empty?
531
+ end
532
+
533
+ update_all updates
534
+ end
535
+
536
+ # Touches all records in the current relation, setting the +updated_at+/+updated_on+ attributes to the current time or the time specified.
537
+ # It does not instantiate the involved models, and it does not trigger Active Record callbacks or validations.
538
+ # This method can be passed attribute names and an optional time argument.
539
+ # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
540
+ # If no time argument is passed, the current time is used as default.
541
+ #
542
+ # === Examples
543
+ #
544
+ # # Touch all records
545
+ # Person.all.touch_all
546
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'"
547
+ #
548
+ # # Touch multiple records with a custom attribute
549
+ # Person.all.touch_all(:created_at)
550
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'"
551
+ #
552
+ # # Touch multiple records with a specified time
553
+ # Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
554
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'"
555
+ #
556
+ # # Touch records with scope
557
+ # Person.where(name: 'David').touch_all
558
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
559
+ def touch_all(*names, time: nil)
560
+ update_all klass.touch_attributes_with_time(*names, time: time)
561
+ end
562
+
348
563
  # Destroys the records by instantiating each
349
564
  # record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
350
565
  # Each object's callbacks are executed (including <tt>:dependent</tt> association options).
@@ -385,31 +600,88 @@ module ActiveRecord
385
600
  # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
386
601
  def delete_all
387
602
  invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
388
- value = get_value(method)
389
- SINGLE_VALUE_METHODS.include?(method) ? value : value.any?
603
+ value = @values[method]
604
+ method == :distinct ? value : value&.any?
390
605
  end
391
606
  if invalid_methods.any?
392
607
  raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
393
608
  end
394
609
 
395
- if eager_loading?
396
- relation = apply_join_dependency
397
- return relation.delete_all
398
- end
610
+ arel = eager_loading? ? apply_join_dependency.arel : build_arel
611
+ arel.source.left = table
399
612
 
400
- stmt = Arel::DeleteManager.new
401
- stmt.from(table)
613
+ group_values_arel_columns = arel_columns(group_values.uniq)
614
+ having_clause_ast = having_clause.ast unless having_clause.empty?
615
+ stmt = arel.compile_delete(table[primary_key], having_clause_ast, group_values_arel_columns)
402
616
 
403
- if has_join_values? || has_limit_or_offset?
404
- @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
405
- else
406
- stmt.wheres = arel.constraints
617
+ klass.connection.delete(stmt, "#{klass} Delete All").tap { reset }
618
+ end
619
+
620
+ # Finds and destroys all records matching the specified conditions.
621
+ # This is short-hand for <tt>relation.where(condition).destroy_all</tt>.
622
+ # Returns the collection of objects that were destroyed.
623
+ #
624
+ # If no record is found, returns empty array.
625
+ #
626
+ # Person.destroy_by(id: 13)
627
+ # Person.destroy_by(name: 'Spartacus', rating: 4)
628
+ # Person.destroy_by("published_at < ?", 2.weeks.ago)
629
+ def destroy_by(*args)
630
+ where(*args).destroy_all
631
+ end
632
+
633
+ # Finds and deletes all records matching the specified conditions.
634
+ # This is short-hand for <tt>relation.where(condition).delete_all</tt>.
635
+ # Returns the number of rows affected.
636
+ #
637
+ # If no record is found, returns <tt>0</tt> as zero rows were affected.
638
+ #
639
+ # Person.delete_by(id: 13)
640
+ # Person.delete_by(name: 'Spartacus', rating: 4)
641
+ # Person.delete_by("published_at < ?", 2.weeks.ago)
642
+ def delete_by(*args)
643
+ where(*args).delete_all
644
+ end
645
+
646
+ # Schedule the query to be performed from a background thread pool.
647
+ #
648
+ # Post.where(published: true).load_async # => #<ActiveRecord::Relation>
649
+ #
650
+ # When the +Relation+ is iterated, if the background query wasn't executed yet,
651
+ # it will be performed by the foreground thread.
652
+ #
653
+ # Note that {config.active_record.async_query_executor}[https://guides.rubyonrails.org/configuring.html#config-active-record-async-query-executor] must be configured
654
+ # for queries to actually be executed concurrently. Otherwise it defaults to
655
+ # executing them in the foreground.
656
+ #
657
+ # +load_async+ will also fallback to executing in the foreground in the test environment when transactional
658
+ # fixtures are enabled.
659
+ #
660
+ # If the query was actually executed in the background, the Active Record logs will show
661
+ # it by prefixing the log line with <tt>ASYNC</tt>:
662
+ #
663
+ # ASYNC Post Load (0.0ms) (db time 2ms) SELECT "posts".* FROM "posts" LIMIT 100
664
+ def load_async
665
+ return load if !connection.async_enabled?
666
+
667
+ unless loaded?
668
+ result = exec_main_query(async: connection.current_transaction.closed?)
669
+
670
+ if result.is_a?(Array)
671
+ @records = result
672
+ else
673
+ @future_result = result
674
+ end
675
+ @loaded = true
407
676
  end
408
677
 
409
- affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
678
+ self
679
+ end
410
680
 
411
- reset
412
- affected
681
+ # Returns <tt>true</tt> if the relation was scheduled on the background
682
+ # thread pool.
683
+ def scheduled?
684
+ !!@future_result
413
685
  end
414
686
 
415
687
  # Causes the records to be loaded from the database if they have not
@@ -419,7 +691,10 @@ module ActiveRecord
419
691
  #
420
692
  # Post.where(published: true).load # => #<ActiveRecord::Relation>
421
693
  def load(&block)
422
- exec_queries(&block) unless loaded?
694
+ if !loaded? || scheduled?
695
+ @records = exec_queries(&block)
696
+ @loaded = true
697
+ end
423
698
 
424
699
  self
425
700
  end
@@ -431,10 +706,13 @@ module ActiveRecord
431
706
  end
432
707
 
433
708
  def reset
709
+ @future_result&.cancel
710
+ @future_result = nil
434
711
  @delegate_to_klass = false
435
712
  @to_sql = @arel = @loaded = @should_eager_load = nil
436
- @records = [].freeze
437
- @offsets = {}
713
+ @offsets = @take = nil
714
+ @cache_keys = nil
715
+ @records = nil
438
716
  self
439
717
  end
440
718
 
@@ -443,16 +721,14 @@ module ActiveRecord
443
721
  # User.where(name: 'Oscar').to_sql
444
722
  # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
445
723
  def to_sql
446
- @to_sql ||= begin
447
- if eager_loading?
448
- apply_join_dependency do |relation, join_dependency|
449
- relation = join_dependency.apply_column_aliases(relation)
450
- relation.to_sql
451
- end
452
- else
453
- conn = klass.connection
454
- conn.unprepared_statement { conn.to_sql(arel) }
724
+ @to_sql ||= if eager_loading?
725
+ apply_join_dependency do |relation, join_dependency|
726
+ relation = join_dependency.apply_column_aliases(relation)
727
+ relation.to_sql
455
728
  end
729
+ else
730
+ conn = klass.connection
731
+ conn.unprepared_statement { conn.to_sql(arel) }
456
732
  end
457
733
  end
458
734
 
@@ -465,7 +741,9 @@ module ActiveRecord
465
741
  end
466
742
 
467
743
  def scope_for_create
468
- where_values_hash.merge!(create_with_value.stringify_keys)
744
+ hash = where_clause.to_h(klass.table_name, equality_only: true)
745
+ create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
746
+ hash
469
747
  end
470
748
 
471
749
  # Returns true if relation needs eager loading.
@@ -508,8 +786,12 @@ module ActiveRecord
508
786
  @values.dup
509
787
  end
510
788
 
789
+ def values_for_queries # :nodoc:
790
+ @values.except(:extending, :skip_query_cache, :strict_loading)
791
+ end
792
+
511
793
  def inspect
512
- subject = loaded? ? records : self
794
+ subject = loaded? ? records : annotate("loading for inspect")
513
795
  entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)
514
796
 
515
797
  entries[10] = "..." if entries.size == 11
@@ -526,104 +808,186 @@ module ActiveRecord
526
808
  end
527
809
 
528
810
  def alias_tracker(joins = [], aliases = nil) # :nodoc:
529
- joins += [aliases] if aliases
530
- ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins)
811
+ ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins, aliases)
531
812
  end
532
813
 
533
- protected
814
+ class StrictLoadingScope # :nodoc:
815
+ def self.empty_scope?
816
+ true
817
+ end
818
+
819
+ def self.strict_loading_value
820
+ true
821
+ end
822
+ end
534
823
 
824
+ def preload_associations(records) # :nodoc:
825
+ preload = preload_values
826
+ preload += includes_values unless eager_loading?
827
+ scope = strict_loading_value ? StrictLoadingScope : nil
828
+ preload.each do |associations|
829
+ ActiveRecord::Associations::Preloader.new(records: records, associations: associations, scope: scope).call
830
+ end
831
+ end
832
+
833
+ protected
535
834
  def load_records(records)
536
835
  @records = records.freeze
537
836
  @loaded = true
538
837
  end
539
838
 
839
+ def null_relation? # :nodoc:
840
+ is_a?(NullRelation)
841
+ end
842
+
540
843
  private
844
+ def already_in_scope?(registry)
845
+ @delegate_to_klass && registry.current_scope(klass, true)
846
+ end
847
+
848
+ def global_scope?(registry)
849
+ registry.global_current_scope(klass, true)
850
+ end
851
+
852
+ def current_scope_restoring_block(&block)
853
+ current_scope = klass.current_scope(true)
854
+ -> record do
855
+ klass.current_scope = current_scope
856
+ yield record if block_given?
857
+ end
858
+ end
859
+
860
+ def _new(attributes, &block)
861
+ klass.new(attributes, &block)
862
+ end
863
+
864
+ def _create(attributes, &block)
865
+ klass.create(attributes, &block)
866
+ end
867
+
868
+ def _create!(attributes, &block)
869
+ klass.create!(attributes, &block)
870
+ end
871
+
872
+ def _scoping(scope, registry, all_queries = false)
873
+ previous = registry.current_scope(klass, true)
874
+ registry.set_current_scope(klass, scope)
875
+
876
+ if all_queries
877
+ previous_global = registry.global_current_scope(klass, true)
878
+ registry.set_global_current_scope(klass, scope)
879
+ end
880
+ yield
881
+ ensure
882
+ registry.set_current_scope(klass, previous)
883
+ if all_queries
884
+ registry.set_global_current_scope(klass, previous_global)
885
+ end
886
+ end
887
+
888
+ def _substitute_values(values)
889
+ values.map do |name, value|
890
+ attr = table[name]
891
+ unless Arel.arel_node?(value)
892
+ type = klass.type_for_attribute(attr.name)
893
+ value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
894
+ end
895
+ [attr, value]
896
+ end
897
+ end
541
898
 
542
- def has_join_values?
543
- joins_values.any? || left_outer_joins_values.any?
899
+ def _increment_attribute(attribute, value = 1)
900
+ bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
901
+ expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
902
+ expr = value < 0 ? expr - bind : expr + bind
903
+ expr.expr
544
904
  end
545
905
 
546
906
  def exec_queries(&block)
547
907
  skip_query_cache_if_necessary do
548
- @records =
549
- if eager_loading?
550
- apply_join_dependency do |relation, join_dependency|
551
- if ActiveRecord::NullRelation === relation
552
- []
553
- else
554
- relation = join_dependency.apply_column_aliases(relation)
555
- rows = connection.select_all(relation.arel, "SQL")
556
- join_dependency.instantiate(rows, &block)
557
- end.freeze
558
- end
559
- else
560
- klass.find_by_sql(arel, &block).freeze
561
- end
562
-
563
- preload = preload_values
564
- preload += includes_values unless eager_loading?
565
- preloader = nil
566
- preload.each do |associations|
567
- preloader ||= build_preloader
568
- preloader.preload @records, associations
908
+ rows = if scheduled?
909
+ future = @future_result
910
+ @future_result = nil
911
+ future.result
912
+ else
913
+ exec_main_query
569
914
  end
570
915
 
571
- @records.each(&:readonly!) if readonly_value
916
+ records = instantiate_records(rows, &block)
917
+ preload_associations(records) unless skip_preloading_value
918
+
919
+ records.each(&:readonly!) if readonly_value
920
+ records.each(&:strict_loading!) if strict_loading_value
572
921
 
573
- @loaded = true
574
- @records
922
+ records
575
923
  end
576
924
  end
577
925
 
578
- def skip_query_cache_if_necessary
579
- if skip_query_cache_value
580
- uncached do
581
- yield
926
+ def exec_main_query(async: false)
927
+ skip_query_cache_if_necessary do
928
+ if where_clause.contradiction?
929
+ [].freeze
930
+ elsif eager_loading?
931
+ apply_join_dependency do |relation, join_dependency|
932
+ if relation.null_relation?
933
+ [].freeze
934
+ else
935
+ relation = join_dependency.apply_column_aliases(relation)
936
+ @_join_dependency = join_dependency
937
+ connection.select_all(relation.arel, "SQL", async: async)
938
+ end
939
+ end
940
+ else
941
+ klass._query_by_sql(arel, async: async)
582
942
  end
943
+ end
944
+ end
945
+
946
+ def instantiate_records(rows, &block)
947
+ return [].freeze if rows.empty?
948
+ if eager_loading?
949
+ records = @_join_dependency.instantiate(rows, strict_loading_value, &block).freeze
950
+ @_join_dependency = nil
951
+ records
583
952
  else
584
- yield
953
+ klass._load_from_sql(rows, &block).freeze
585
954
  end
586
955
  end
587
956
 
588
- def build_preloader
589
- ActiveRecord::Associations::Preloader.new
957
+ def skip_query_cache_if_necessary(&block)
958
+ if skip_query_cache_value
959
+ uncached(&block)
960
+ else
961
+ yield
962
+ end
590
963
  end
591
964
 
592
965
  def references_eager_loaded_tables?
593
- joined_tables = arel.join_sources.map do |join|
966
+ joined_tables = build_joins([]).flat_map do |join|
594
967
  if join.is_a?(Arel::Nodes::StringJoin)
595
968
  tables_in_string(join.left)
596
969
  else
597
- [join.left.table_name, join.left.table_alias]
970
+ join.left.name
598
971
  end
599
972
  end
600
973
 
601
- joined_tables += [table.name, table.table_alias]
974
+ joined_tables << table.name
602
975
 
603
976
  # always convert table names to downcase as in Oracle quoted table names are in uppercase
604
- joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq
977
+ joined_tables.map!(&:downcase)
605
978
 
606
- (references_values - joined_tables).any?
979
+ !(references_values.map(&:to_s) - joined_tables).empty?
607
980
  end
608
981
 
609
982
  def tables_in_string(string)
610
983
  return [] if string.blank?
611
984
  # always convert table names to downcase as in Oracle quoted table names are in uppercase
612
985
  # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
613
- string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"]
986
+ string.scan(/[a-zA-Z_][.\w]+(?=.?\.)/).map!(&:downcase) - ["raw_sql_"]
614
987
  end
615
988
 
616
- def values_for_create(attributes = nil)
617
- result = attributes ? where_values_hash.merge!(attributes) : where_values_hash
618
-
619
- # NOTE: if there are same keys in both create_with and result, create_with should be used.
620
- # This is to make sure nested attributes don't get passed to the klass.new,
621
- # while keeping the precedence of the duplicate keys in create_with.
622
- create_with_value.stringify_keys.each do |k, v|
623
- result[k] = v if result.key?(k)
624
- end
625
-
626
- result
989
+ def limited_count
990
+ limit_value ? count : limit(2).count
627
991
  end
628
992
  end
629
993
  end