activerecord 6.1.7 → 7.1.0

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 (307) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1516 -1019
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +50 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +35 -31
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency.rb +26 -16
  27. data/lib/active_record/associations/preloader/association.rb +207 -52
  28. data/lib/active_record/associations/preloader/batch.rb +48 -0
  29. data/lib/active_record/associations/preloader/branch.rb +147 -0
  30. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  31. data/lib/active_record/associations/preloader.rb +50 -121
  32. data/lib/active_record/associations/singular_association.rb +9 -3
  33. data/lib/active_record/associations/through_association.rb +25 -14
  34. data/lib/active_record/associations.rb +423 -289
  35. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  36. data/lib/active_record/attribute_assignment.rb +1 -3
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +61 -14
  39. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  40. data/lib/active_record/attribute_methods/query.rb +31 -19
  41. data/lib/active_record/attribute_methods/read.rb +25 -10
  42. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  44. data/lib/active_record/attribute_methods/write.rb +10 -13
  45. data/lib/active_record/attribute_methods.rb +121 -40
  46. data/lib/active_record/attributes.rb +27 -38
  47. data/lib/active_record/autosave_association.rb +61 -30
  48. data/lib/active_record/base.rb +25 -2
  49. data/lib/active_record/callbacks.rb +18 -34
  50. data/lib/active_record/coders/column_serializer.rb +61 -0
  51. data/lib/active_record/coders/json.rb +1 -1
  52. data/lib/active_record/coders/yaml_column.rb +70 -46
  53. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  54. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -51
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -136
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +622 -149
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
  69. data/lib/active_record/connection_adapters/column.rb +13 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +18 -1
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -52
  83. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +381 -69
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +492 -230
  100. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +65 -53
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  104. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
  107. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  108. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  109. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  110. data/lib/active_record/connection_adapters.rb +9 -6
  111. data/lib/active_record/connection_handling.rb +107 -136
  112. data/lib/active_record/core.rb +194 -224
  113. data/lib/active_record/counter_cache.rb +46 -25
  114. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  115. data/lib/active_record/database_configurations/database_config.rb +21 -12
  116. data/lib/active_record/database_configurations/hash_config.rb +84 -16
  117. data/lib/active_record/database_configurations/url_config.rb +18 -12
  118. data/lib/active_record/database_configurations.rb +95 -59
  119. data/lib/active_record/delegated_type.rb +61 -15
  120. data/lib/active_record/deprecator.rb +7 -0
  121. data/lib/active_record/destroy_association_async_job.rb +3 -1
  122. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  123. data/lib/active_record/dynamic_matchers.rb +1 -1
  124. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  125. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  126. data/lib/active_record/encryption/cipher.rb +53 -0
  127. data/lib/active_record/encryption/config.rb +68 -0
  128. data/lib/active_record/encryption/configurable.rb +60 -0
  129. data/lib/active_record/encryption/context.rb +42 -0
  130. data/lib/active_record/encryption/contexts.rb +76 -0
  131. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  132. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  133. data/lib/active_record/encryption/encryptable_record.rb +224 -0
  134. data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
  135. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  136. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  137. data/lib/active_record/encryption/encryptor.rb +155 -0
  138. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  139. data/lib/active_record/encryption/errors.rb +15 -0
  140. data/lib/active_record/encryption/extended_deterministic_queries.rb +172 -0
  141. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  142. data/lib/active_record/encryption/key.rb +28 -0
  143. data/lib/active_record/encryption/key_generator.rb +53 -0
  144. data/lib/active_record/encryption/key_provider.rb +46 -0
  145. data/lib/active_record/encryption/message.rb +33 -0
  146. data/lib/active_record/encryption/message_serializer.rb +92 -0
  147. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  148. data/lib/active_record/encryption/properties.rb +76 -0
  149. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  150. data/lib/active_record/encryption/scheme.rb +96 -0
  151. data/lib/active_record/encryption.rb +56 -0
  152. data/lib/active_record/enum.rb +156 -62
  153. data/lib/active_record/errors.rb +171 -15
  154. data/lib/active_record/explain.rb +23 -3
  155. data/lib/active_record/explain_registry.rb +11 -6
  156. data/lib/active_record/explain_subscriber.rb +1 -1
  157. data/lib/active_record/fixture_set/file.rb +15 -1
  158. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  159. data/lib/active_record/fixture_set/render_context.rb +2 -0
  160. data/lib/active_record/fixture_set/table_row.rb +70 -14
  161. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  162. data/lib/active_record/fixtures.rb +131 -86
  163. data/lib/active_record/future_result.rb +164 -0
  164. data/lib/active_record/gem_version.rb +3 -3
  165. data/lib/active_record/inheritance.rb +81 -29
  166. data/lib/active_record/insert_all.rb +133 -20
  167. data/lib/active_record/integration.rb +11 -10
  168. data/lib/active_record/internal_metadata.rb +117 -33
  169. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  170. data/lib/active_record/locking/optimistic.rb +36 -21
  171. data/lib/active_record/locking/pessimistic.rb +15 -6
  172. data/lib/active_record/log_subscriber.rb +52 -19
  173. data/lib/active_record/marshalling.rb +56 -0
  174. data/lib/active_record/message_pack.rb +124 -0
  175. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  176. data/lib/active_record/middleware/database_selector.rb +23 -13
  177. data/lib/active_record/middleware/shard_selector.rb +62 -0
  178. data/lib/active_record/migration/command_recorder.rb +108 -13
  179. data/lib/active_record/migration/compatibility.rb +221 -48
  180. data/lib/active_record/migration/default_strategy.rb +23 -0
  181. data/lib/active_record/migration/execution_strategy.rb +19 -0
  182. data/lib/active_record/migration/join_table.rb +1 -1
  183. data/lib/active_record/migration.rb +355 -171
  184. data/lib/active_record/model_schema.rb +116 -97
  185. data/lib/active_record/nested_attributes.rb +36 -15
  186. data/lib/active_record/no_touching.rb +3 -3
  187. data/lib/active_record/normalization.rb +159 -0
  188. data/lib/active_record/persistence.rb +405 -85
  189. data/lib/active_record/promise.rb +84 -0
  190. data/lib/active_record/query_cache.rb +3 -21
  191. data/lib/active_record/query_logs.rb +174 -0
  192. data/lib/active_record/query_logs_formatter.rb +41 -0
  193. data/lib/active_record/querying.rb +29 -6
  194. data/lib/active_record/railtie.rb +219 -43
  195. data/lib/active_record/railties/controller_runtime.rb +13 -9
  196. data/lib/active_record/railties/databases.rake +185 -249
  197. data/lib/active_record/railties/job_runtime.rb +23 -0
  198. data/lib/active_record/readonly_attributes.rb +41 -3
  199. data/lib/active_record/reflection.rb +229 -80
  200. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  201. data/lib/active_record/relation/batches.rb +192 -63
  202. data/lib/active_record/relation/calculations.rb +211 -90
  203. data/lib/active_record/relation/delegation.rb +27 -13
  204. data/lib/active_record/relation/finder_methods.rb +108 -51
  205. data/lib/active_record/relation/merger.rb +22 -13
  206. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  207. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  208. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  209. data/lib/active_record/relation/predicate_builder.rb +27 -20
  210. data/lib/active_record/relation/query_attribute.rb +30 -12
  211. data/lib/active_record/relation/query_methods.rb +654 -127
  212. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  213. data/lib/active_record/relation/spawn_methods.rb +20 -3
  214. data/lib/active_record/relation/where_clause.rb +10 -19
  215. data/lib/active_record/relation.rb +262 -120
  216. data/lib/active_record/result.rb +37 -11
  217. data/lib/active_record/runtime_registry.rb +18 -13
  218. data/lib/active_record/sanitization.rb +65 -20
  219. data/lib/active_record/schema.rb +36 -22
  220. data/lib/active_record/schema_dumper.rb +73 -24
  221. data/lib/active_record/schema_migration.rb +68 -33
  222. data/lib/active_record/scoping/default.rb +72 -15
  223. data/lib/active_record/scoping/named.rb +5 -13
  224. data/lib/active_record/scoping.rb +65 -34
  225. data/lib/active_record/secure_password.rb +60 -0
  226. data/lib/active_record/secure_token.rb +21 -3
  227. data/lib/active_record/serialization.rb +6 -1
  228. data/lib/active_record/signed_id.rb +10 -8
  229. data/lib/active_record/store.rb +10 -10
  230. data/lib/active_record/suppressor.rb +13 -15
  231. data/lib/active_record/table_metadata.rb +16 -3
  232. data/lib/active_record/tasks/database_tasks.rb +225 -136
  233. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  234. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  235. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  236. data/lib/active_record/test_databases.rb +1 -1
  237. data/lib/active_record/test_fixtures.rb +116 -96
  238. data/lib/active_record/timestamp.rb +28 -17
  239. data/lib/active_record/token_for.rb +113 -0
  240. data/lib/active_record/touch_later.rb +11 -6
  241. data/lib/active_record/transactions.rb +48 -27
  242. data/lib/active_record/translation.rb +3 -3
  243. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  244. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  245. data/lib/active_record/type/internal/timezone.rb +7 -2
  246. data/lib/active_record/type/serialized.rb +9 -5
  247. data/lib/active_record/type/time.rb +4 -0
  248. data/lib/active_record/type/type_map.rb +17 -20
  249. data/lib/active_record/type.rb +1 -2
  250. data/lib/active_record/validations/absence.rb +1 -1
  251. data/lib/active_record/validations/associated.rb +4 -4
  252. data/lib/active_record/validations/numericality.rb +5 -4
  253. data/lib/active_record/validations/presence.rb +5 -28
  254. data/lib/active_record/validations/uniqueness.rb +51 -6
  255. data/lib/active_record/validations.rb +8 -4
  256. data/lib/active_record/version.rb +1 -1
  257. data/lib/active_record.rb +335 -32
  258. data/lib/arel/attributes/attribute.rb +0 -8
  259. data/lib/arel/crud.rb +28 -22
  260. data/lib/arel/delete_manager.rb +18 -4
  261. data/lib/arel/errors.rb +10 -0
  262. data/lib/arel/factory_methods.rb +4 -0
  263. data/lib/arel/filter_predications.rb +9 -0
  264. data/lib/arel/insert_manager.rb +2 -3
  265. data/lib/arel/nodes/and.rb +4 -0
  266. data/lib/arel/nodes/binary.rb +6 -1
  267. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  268. data/lib/arel/nodes/casted.rb +1 -1
  269. data/lib/arel/nodes/cte.rb +36 -0
  270. data/lib/arel/nodes/delete_statement.rb +12 -13
  271. data/lib/arel/nodes/filter.rb +10 -0
  272. data/lib/arel/nodes/fragments.rb +35 -0
  273. data/lib/arel/nodes/function.rb +1 -0
  274. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  275. data/lib/arel/nodes/insert_statement.rb +2 -2
  276. data/lib/arel/nodes/leading_join.rb +8 -0
  277. data/lib/arel/nodes/node.rb +111 -2
  278. data/lib/arel/nodes/select_core.rb +2 -2
  279. data/lib/arel/nodes/select_statement.rb +2 -2
  280. data/lib/arel/nodes/sql_literal.rb +6 -0
  281. data/lib/arel/nodes/table_alias.rb +4 -0
  282. data/lib/arel/nodes/update_statement.rb +8 -3
  283. data/lib/arel/nodes.rb +5 -0
  284. data/lib/arel/predications.rb +13 -3
  285. data/lib/arel/select_manager.rb +10 -4
  286. data/lib/arel/table.rb +9 -6
  287. data/lib/arel/tree_manager.rb +0 -12
  288. data/lib/arel/update_manager.rb +18 -4
  289. data/lib/arel/visitors/dot.rb +80 -90
  290. data/lib/arel/visitors/mysql.rb +16 -3
  291. data/lib/arel/visitors/postgresql.rb +0 -10
  292. data/lib/arel/visitors/to_sql.rb +139 -19
  293. data/lib/arel/visitors/visitor.rb +2 -2
  294. data/lib/arel.rb +18 -3
  295. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  296. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  297. data/lib/rails/generators/active_record/migration.rb +3 -1
  298. data/lib/rails/generators/active_record/model/USAGE +113 -0
  299. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  300. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  302. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  303. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  304. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  305. metadata +92 -13
  306. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  307. data/lib/active_record/null_relation.rb +0 -67
@@ -3,7 +3,49 @@
3
3
  require "active_support/core_ext/enumerable"
4
4
 
5
5
  module ActiveRecord
6
+ # = Active Record \Calculations
6
7
  module Calculations
8
+ class ColumnAliasTracker # :nodoc:
9
+ def initialize(connection)
10
+ @connection = connection
11
+ @aliases = Hash.new(0)
12
+ end
13
+
14
+ def alias_for(field)
15
+ aliased_name = column_alias_for(field)
16
+
17
+ if @aliases[aliased_name] == 0
18
+ @aliases[aliased_name] = 1
19
+ aliased_name
20
+ else
21
+ # Update the count
22
+ count = @aliases[aliased_name] += 1
23
+ "#{truncate(aliased_name)}_#{count}"
24
+ end
25
+ end
26
+
27
+ private
28
+ # Converts the given field to the value that the database adapter returns as
29
+ # a usable column name:
30
+ #
31
+ # column_alias_for("users.id") # => "users_id"
32
+ # column_alias_for("sum(id)") # => "sum_id"
33
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
34
+ # column_alias_for("count(*)") # => "count_all"
35
+ def column_alias_for(field)
36
+ column_alias = +field
37
+ column_alias.gsub!(/\*/, "all")
38
+ column_alias.gsub!(/\W+/, " ")
39
+ column_alias.strip!
40
+ column_alias.gsub!(/ +/, "_")
41
+ @connection.table_alias_for(column_alias)
42
+ end
43
+
44
+ def truncate(name)
45
+ name.slice(0, @connection.table_alias_length - 2)
46
+ end
47
+ end
48
+
7
49
  # Count the records.
8
50
  #
9
51
  # Person.count
@@ -30,8 +72,7 @@ module ActiveRecord
30
72
  # of each key would be the #count.
31
73
  #
32
74
  # Article.group(:status, :category).count
33
- # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
34
- # ["published", "business"]=>0, ["published", "technology"]=>2}
75
+ # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
35
76
  #
36
77
  # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
37
78
  #
@@ -52,6 +93,11 @@ module ActiveRecord
52
93
  end
53
94
  end
54
95
 
96
+ # Same as <tt>#count</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
97
+ def async_count(column_name = nil)
98
+ async.count(column_name)
99
+ end
100
+
55
101
  # Calculates the average value on a given column. Returns +nil+ if there's
56
102
  # no row. See #calculate for examples with options.
57
103
  #
@@ -60,6 +106,11 @@ module ActiveRecord
60
106
  calculate(:average, column_name)
61
107
  end
62
108
 
109
+ # Same as <tt>#average</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
110
+ def async_average(column_name)
111
+ async.average(column_name)
112
+ end
113
+
63
114
  # Calculates the minimum value on a given column. The value is returned
64
115
  # with the same data type of the column, or +nil+ if there's no row. See
65
116
  # #calculate for examples with options.
@@ -69,6 +120,11 @@ module ActiveRecord
69
120
  calculate(:minimum, column_name)
70
121
  end
71
122
 
123
+ # Same as <tt>#minimum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
124
+ def async_minimum(column_name)
125
+ async.minimum(column_name)
126
+ end
127
+
72
128
  # Calculates the maximum value on a given column. The value is returned
73
129
  # with the same data type of the column, or +nil+ if there's no row. See
74
130
  # #calculate for examples with options.
@@ -78,23 +134,29 @@ module ActiveRecord
78
134
  calculate(:maximum, column_name)
79
135
  end
80
136
 
137
+ # Same as <tt>#maximum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
138
+ def async_maximum(column_name)
139
+ async.maximum(column_name)
140
+ end
141
+
81
142
  # Calculates the sum of values on a given column. The value is returned
82
143
  # with the same data type of the column, +0+ if there's no row. See
83
144
  # #calculate for examples with options.
84
145
  #
85
146
  # Person.sum(:age) # => 4562
86
- def sum(column_name = nil)
147
+ def sum(initial_value_or_column = 0, &block)
87
148
  if block_given?
88
- unless column_name.nil?
89
- raise ArgumentError, "Column name argument is not supported when a block is passed."
90
- end
91
-
92
- super()
149
+ map(&block).sum(initial_value_or_column)
93
150
  else
94
- calculate(:sum, column_name)
151
+ calculate(:sum, initial_value_or_column)
95
152
  end
96
153
  end
97
154
 
155
+ # Same as <tt>#sum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
156
+ def async_sum(identity_or_column = nil)
157
+ async.sum(identity_or_column)
158
+ end
159
+
98
160
  # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
99
161
  # #minimum, and #maximum have been added as shortcuts.
100
162
  #
@@ -127,10 +189,23 @@ module ActiveRecord
127
189
  # ...
128
190
  # end
129
191
  def calculate(operation, column_name)
192
+ operation = operation.to_s.downcase
193
+
194
+ if @none
195
+ case operation
196
+ when "count", "sum"
197
+ result = group_values.any? ? Hash.new : 0
198
+ return @async ? Promise::Complete.new(result) : result
199
+ when "average", "minimum", "maximum"
200
+ result = group_values.any? ? Hash.new : nil
201
+ return @async ? Promise::Complete.new(result) : result
202
+ end
203
+ end
204
+
130
205
  if has_include?(column_name)
131
206
  relation = apply_join_dependency
132
207
 
133
- if operation.to_s.downcase == "count"
208
+ if operation == "count"
134
209
  unless distinct_value || distinct_select?(column_name || select_for_count)
135
210
  relation.distinct!
136
211
  relation.select_values = [ klass.primary_key || table[Arel.star] ]
@@ -146,7 +221,7 @@ module ActiveRecord
146
221
  end
147
222
 
148
223
  # Use #pluck as a shortcut to select one or more attributes without
149
- # loading a bunch of records just to grab the attributes you want.
224
+ # loading an entire record object per row.
150
225
  #
151
226
  # Person.pluck(:name)
152
227
  #
@@ -179,31 +254,44 @@ module ActiveRecord
179
254
  # # => ['0', '27761', '173']
180
255
  #
181
256
  # See also #ids.
182
- #
183
257
  def pluck(*column_names)
258
+ return [] if @none
259
+
184
260
  if loaded? && all_attributes?(column_names)
185
- return records.pluck(*column_names)
261
+ result = records.pluck(*column_names)
262
+ if @async
263
+ return Promise::Complete.new(result)
264
+ else
265
+ return result
266
+ end
186
267
  end
187
268
 
188
269
  if has_include?(column_names.first)
189
270
  relation = apply_join_dependency
190
271
  relation.pluck(*column_names)
191
272
  else
192
- klass.disallow_raw_sql!(column_names)
273
+ klass.disallow_raw_sql!(column_names.flatten)
193
274
  columns = arel_columns(column_names)
194
275
  relation = spawn
195
276
  relation.select_values = columns
196
277
  result = skip_query_cache_if_necessary do
197
278
  if where_clause.contradiction?
198
- ActiveRecord::Result.new([], [])
279
+ ActiveRecord::Result.empty(async: @async)
199
280
  else
200
- klass.connection.select_all(relation.arel, nil)
281
+ klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
201
282
  end
202
283
  end
203
- type_cast_pluck_values(result, columns)
284
+ result.then do |result|
285
+ type_cast_pluck_values(result, columns)
286
+ end
204
287
  end
205
288
  end
206
289
 
290
+ # Same as <tt>#pluck</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
291
+ def async_pluck(*column_names)
292
+ async.pluck(*column_names)
293
+ end
294
+
207
295
  # Pick the value(s) from the named column(s) in the current relation.
208
296
  # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
209
297
  # when you have a relation that's already narrowed down to a single row.
@@ -220,18 +308,59 @@ module ActiveRecord
220
308
  # # => [ 'David', 'david@loudthinking.com' ]
221
309
  def pick(*column_names)
222
310
  if loaded? && all_attributes?(column_names)
223
- return records.pick(*column_names)
311
+ result = records.pick(*column_names)
312
+ return @async ? Promise::Complete.new(result) : result
224
313
  end
225
314
 
226
- limit(1).pluck(*column_names).first
315
+ limit(1).pluck(*column_names).then(&:first)
316
+ end
317
+
318
+ # Same as <tt>#pick</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
319
+ def async_pick(*column_names)
320
+ async.pick(*column_names)
227
321
  end
228
322
 
229
- # Pluck all the ID's for the relation using the table's primary key
323
+ # Returns the base model's ID's for the relation using the table's primary key
230
324
  #
231
325
  # Person.ids # SELECT people.id FROM people
232
- # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
326
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
233
327
  def ids
234
- pluck primary_key
328
+ primary_key_array = Array(primary_key)
329
+
330
+ if loaded?
331
+ result = records.map do |record|
332
+ if primary_key_array.one?
333
+ record._read_attribute(primary_key_array.first)
334
+ else
335
+ primary_key_array.map { |column| record._read_attribute(column) }
336
+ end
337
+ end
338
+ return @async ? Promise::Complete.new(result) : result
339
+ end
340
+
341
+ if has_include?(primary_key)
342
+ relation = apply_join_dependency.group(*primary_key_array)
343
+ return relation.ids
344
+ end
345
+
346
+ columns = arel_columns(primary_key_array)
347
+ relation = spawn
348
+ relation.select_values = columns
349
+
350
+ result = if relation.where_clause.contradiction?
351
+ ActiveRecord::Result.empty
352
+ else
353
+ skip_query_cache_if_necessary do
354
+ klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
355
+ end
356
+ end
357
+
358
+ result.then { |result| type_cast_pluck_values(result, columns) }
359
+ end
360
+
361
+ # Same as <tt>#ids</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
362
+ def async_ids
363
+ async.ids
235
364
  end
236
365
 
237
366
  private
@@ -286,11 +415,12 @@ module ActiveRecord
286
415
  operation == "count" ? column.count(distinct) : column.public_send(operation)
287
416
  end
288
417
 
289
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
418
+ def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
290
419
  if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
291
420
  # Shortcut when limit is zero.
292
421
  return 0 if limit_value == 0
293
422
 
423
+ relation = self
294
424
  query_builder = build_count_subquery(spawn, column_name, distinct)
295
425
  else
296
426
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
@@ -305,29 +435,29 @@ module ActiveRecord
305
435
  query_builder = relation.arel
306
436
  end
307
437
 
308
- result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
438
+ query_result = if relation.where_clause.contradiction?
439
+ ActiveRecord::Result.empty
440
+ else
441
+ skip_query_cache_if_necessary do
442
+ @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
443
+ end
444
+ end
309
445
 
310
- type_cast_calculated_value(result.cast_values.first, operation) do |value|
311
- type = column.try(:type_caster) ||
312
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
313
- type = type.subtype if Enum::EnumType === type
314
- type.deserialize(value)
446
+ query_result.then do |result|
447
+ if operation != "count"
448
+ type = column.try(:type_caster) ||
449
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
450
+ type = type.subtype if Enum::EnumType === type
451
+ end
452
+
453
+ type_cast_calculated_value(result.cast_values.first, operation, type)
315
454
  end
316
455
  end
317
456
 
318
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
457
+ def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
319
458
  group_fields = group_values
320
459
  group_fields = group_fields.uniq if group_fields.size > 1
321
460
 
322
- unless group_fields == group_values
323
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
324
- `#{operation}` with group by duplicated fields does no longer affect to result in Rails 7.0.
325
- To migrate to Rails 7.0's behavior, use `uniq!(:group)` to deduplicate group fields
326
- (`#{klass.name&.tableize || klass.table_name}.uniq!(:group).#{operation}(#{column_name.inspect})`).
327
- MSG
328
- group_fields = group_values
329
- end
330
-
331
461
  if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
332
462
  association = klass._reflect_on_association(group_fields.first)
333
463
  associated = association && association.belongs_to? # only count belongs_to associations
@@ -335,21 +465,24 @@ module ActiveRecord
335
465
  end
336
466
  group_fields = arel_columns(group_fields)
337
467
 
468
+ column_alias_tracker = ColumnAliasTracker.new(connection)
469
+
338
470
  group_aliases = group_fields.map { |field|
339
471
  field = connection.visitor.compile(field) if Arel.arel_node?(field)
340
- column_alias_for(field.to_s.downcase)
472
+ column_alias_tracker.alias_for(field.to_s.downcase)
341
473
  }
342
474
  group_columns = group_aliases.zip(group_fields)
343
475
 
344
476
  column = aggregate_column(column_name)
345
- column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
477
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
346
478
  select_value = operation_over_aggregate_column(column, operation, distinct)
347
- select_value.as(column_alias)
479
+ select_value.as(connection.quote_column_name(column_alias))
348
480
 
349
481
  select_values = [select_value]
350
482
  select_values += self.select_values unless having_clause.empty?
351
483
 
352
484
  select_values.concat group_columns.map { |aliaz, field|
485
+ aliaz = connection.quote_column_name(aliaz)
353
486
  if field.respond_to?(:as)
354
487
  field.as(aliaz)
355
488
  else
@@ -361,60 +494,43 @@ module ActiveRecord
361
494
  relation.group_values = group_fields
362
495
  relation.select_values = select_values
363
496
 
364
- calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
497
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async) }
498
+ result.then do |calculated_data|
499
+ if association
500
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
501
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
502
+ key_records = key_records.index_by(&:id)
503
+ end
365
504
 
366
- if association
367
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
368
- key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
369
- key_records = key_records.index_by(&:id)
370
- end
505
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
506
+ types[aliaz] = col_name.try(:type_caster) ||
507
+ type_for(col_name) do
508
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
509
+ end
510
+ end
371
511
 
372
- key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
373
- types[aliaz] = type_for(col_name) do
374
- calculated_data.column_types.fetch(aliaz, Type.default_value)
512
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
513
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
514
+ hash[col_name] = row[i]
515
+ end
375
516
  end
376
- end
377
517
 
378
- hash_rows = calculated_data.cast_values(key_types).map! do |row|
379
- calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
380
- hash[col_name] = row[i]
518
+ if operation != "count"
519
+ type = column.try(:type_caster) ||
520
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
521
+ type = type.subtype if Enum::EnumType === type
381
522
  end
382
- end
383
523
 
384
- type = nil
385
- hash_rows.each_with_object({}) do |row, result|
386
- key = group_aliases.map { |aliaz| row[aliaz] }
387
- key = key.first if key.size == 1
388
- key = key_records[key] if associated
389
-
390
- result[key] = type_cast_calculated_value(row[column_alias], operation) do |value|
391
- unless type
392
- type = column.try(:type_caster) ||
393
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
394
- type = type.subtype if Enum::EnumType === type
395
- end
396
- type.deserialize(value)
524
+ hash_rows.each_with_object({}) do |row, result|
525
+ key = group_aliases.map { |aliaz| row[aliaz] }
526
+ key = key.first if key.size == 1
527
+ key = key_records[key] if associated
528
+
529
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
397
530
  end
398
531
  end
399
532
  end
400
533
 
401
- # Converts the given field to the value that the database adapter returns as
402
- # a usable column name:
403
- #
404
- # column_alias_for("users.id") # => "users_id"
405
- # column_alias_for("sum(id)") # => "sum_id"
406
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
407
- # column_alias_for("count(*)") # => "count_all"
408
- def column_alias_for(field)
409
- column_alias = +field
410
- column_alias.gsub!(/\*/, "all")
411
- column_alias.gsub!(/\W+/, " ")
412
- column_alias.strip!
413
- column_alias.gsub!(/ +/, "_")
414
-
415
- connection.table_alias_for(column_alias)
416
- end
417
-
418
534
  def type_for(field, &block)
419
535
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
420
536
  @klass.type_for_attribute(field_name, &block)
@@ -445,16 +561,21 @@ module ActiveRecord
445
561
  result.cast_values(cast_types)
446
562
  end
447
563
 
448
- def type_cast_calculated_value(value, operation)
564
+ def type_cast_calculated_value(value, operation, type)
449
565
  case operation
450
566
  when "count"
451
567
  value.to_i
452
568
  when "sum"
453
- yield value || 0
569
+ type.deserialize(value || 0)
454
570
  when "average"
455
- value&.respond_to?(:to_d) ? value.to_d : value
571
+ case type.type
572
+ when :integer, :decimal
573
+ value&.to_d
574
+ else
575
+ type.deserialize(value)
576
+ end
456
577
  else # "minimum", "maximum"
457
- yield value
578
+ type.deserialize(value)
458
579
  end
459
580
  end
460
581
 
@@ -5,6 +5,23 @@ require "active_support/core_ext/module/delegation"
5
5
 
6
6
  module ActiveRecord
7
7
  module Delegation # :nodoc:
8
+ class << self
9
+ def delegated_classes
10
+ [
11
+ ActiveRecord::Relation,
12
+ ActiveRecord::Associations::CollectionProxy,
13
+ ActiveRecord::AssociationRelation,
14
+ ActiveRecord::DisableJoinsAssociationRelation,
15
+ ]
16
+ end
17
+
18
+ def uncacheable_methods
19
+ @uncacheable_methods ||= (
20
+ delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods
21
+ ).to_set.freeze
22
+ end
23
+ end
24
+
8
25
  module DelegateCache # :nodoc:
9
26
  def relation_delegate_class(klass)
10
27
  @relation_delegate_cache[klass]
@@ -12,11 +29,7 @@ module ActiveRecord
12
29
 
13
30
  def initialize_relation_delegate_cache
14
31
  @relation_delegate_cache = cache = {}
15
- [
16
- ActiveRecord::Relation,
17
- ActiveRecord::Associations::CollectionProxy,
18
- ActiveRecord::AssociationRelation
19
- ].each do |klass|
32
+ Delegation.delegated_classes.each do |klass|
20
33
  delegate = Class.new(klass) {
21
34
  include ClassSpecificRelation
22
35
  }
@@ -61,17 +74,16 @@ module ActiveRecord
61
74
  return if method_defined?(method)
62
75
 
63
76
  if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
64
- definition = RUBY_VERSION >= "2.7" ? "..." : "*args, &block"
65
77
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
66
- def #{method}(#{definition})
67
- scoping { klass.#{method}(#{definition}) }
78
+ def #{method}(...)
79
+ scoping { klass.#{method}(...) }
68
80
  end
69
81
  RUBY
70
82
  else
71
83
  define_method(method) do |*args, &block|
72
84
  scoping { klass.public_send(method, *args, &block) }
73
85
  end
74
- ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
86
+ ruby2_keywords(method)
75
87
  end
76
88
  end
77
89
  end
@@ -85,9 +97,9 @@ module ActiveRecord
85
97
  # may vary depending on the klass of a relation, so we create a subclass of Relation
86
98
  # for each different klass, and the delegations are compiled into that subclass only.
87
99
 
88
- delegate :to_xml, :encode_with, :length, :each, :join,
100
+ delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
89
101
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
90
- :to_sentence, :to_formatted_s, :as_json,
102
+ :to_sentence, :to_fs, :to_formatted_s, :as_json,
91
103
  :shuffle, :split, :slice, :index, :rindex, to: :records
92
104
 
93
105
  delegate :primary_key, :connection, to: :klass
@@ -104,13 +116,15 @@ module ActiveRecord
104
116
  private
105
117
  def method_missing(method, *args, &block)
106
118
  if @klass.respond_to?(method)
107
- @klass.generate_relation_method(method)
119
+ unless Delegation.uncacheable_methods.include?(method)
120
+ @klass.generate_relation_method(method)
121
+ end
108
122
  scoping { @klass.public_send(method, *args, &block) }
109
123
  else
110
124
  super
111
125
  end
112
126
  end
113
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
127
+ ruby2_keywords(:method_missing)
114
128
  end
115
129
 
116
130
  module ClassMethods # :nodoc: