activerecord 6.1.6 → 7.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (309) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1627 -983
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +50 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +35 -31
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency.rb +26 -16
  27. data/lib/active_record/associations/preloader/association.rb +207 -52
  28. data/lib/active_record/associations/preloader/batch.rb +48 -0
  29. data/lib/active_record/associations/preloader/branch.rb +147 -0
  30. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  31. data/lib/active_record/associations/preloader.rb +50 -121
  32. data/lib/active_record/associations/singular_association.rb +9 -3
  33. data/lib/active_record/associations/through_association.rb +25 -14
  34. data/lib/active_record/associations.rb +439 -305
  35. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  36. data/lib/active_record/attribute_assignment.rb +1 -3
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  39. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  40. data/lib/active_record/attribute_methods/query.rb +31 -19
  41. data/lib/active_record/attribute_methods/read.rb +25 -10
  42. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  44. data/lib/active_record/attribute_methods/write.rb +10 -13
  45. data/lib/active_record/attribute_methods.rb +121 -40
  46. data/lib/active_record/attributes.rb +27 -38
  47. data/lib/active_record/autosave_association.rb +61 -30
  48. data/lib/active_record/base.rb +25 -2
  49. data/lib/active_record/callbacks.rb +18 -34
  50. data/lib/active_record/coders/column_serializer.rb +61 -0
  51. data/lib/active_record/coders/json.rb +1 -1
  52. data/lib/active_record/coders/yaml_column.rb +70 -34
  53. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  54. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -138
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -149
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
  69. data/lib/active_record/connection_adapters/column.rb +13 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  83. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  87. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  89. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  95. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  97. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +394 -74
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +509 -247
  101. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  102. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  103. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  104. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  107. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
  108. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  109. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  110. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  111. data/lib/active_record/connection_adapters.rb +9 -6
  112. data/lib/active_record/connection_handling.rb +107 -136
  113. data/lib/active_record/core.rb +202 -223
  114. data/lib/active_record/counter_cache.rb +46 -25
  115. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  116. data/lib/active_record/database_configurations/database_config.rb +21 -12
  117. data/lib/active_record/database_configurations/hash_config.rb +84 -16
  118. data/lib/active_record/database_configurations/url_config.rb +18 -12
  119. data/lib/active_record/database_configurations.rb +95 -59
  120. data/lib/active_record/delegated_type.rb +61 -15
  121. data/lib/active_record/deprecator.rb +7 -0
  122. data/lib/active_record/destroy_association_async_job.rb +3 -1
  123. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  124. data/lib/active_record/dynamic_matchers.rb +1 -1
  125. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  126. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  127. data/lib/active_record/encryption/cipher.rb +53 -0
  128. data/lib/active_record/encryption/config.rb +68 -0
  129. data/lib/active_record/encryption/configurable.rb +60 -0
  130. data/lib/active_record/encryption/context.rb +42 -0
  131. data/lib/active_record/encryption/contexts.rb +76 -0
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  134. data/lib/active_record/encryption/encryptable_record.rb +224 -0
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
  136. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  137. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  138. data/lib/active_record/encryption/encryptor.rb +155 -0
  139. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  140. data/lib/active_record/encryption/errors.rb +15 -0
  141. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  142. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  143. data/lib/active_record/encryption/key.rb +28 -0
  144. data/lib/active_record/encryption/key_generator.rb +53 -0
  145. data/lib/active_record/encryption/key_provider.rb +46 -0
  146. data/lib/active_record/encryption/message.rb +33 -0
  147. data/lib/active_record/encryption/message_serializer.rb +92 -0
  148. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  149. data/lib/active_record/encryption/properties.rb +76 -0
  150. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  151. data/lib/active_record/encryption/scheme.rb +96 -0
  152. data/lib/active_record/encryption.rb +56 -0
  153. data/lib/active_record/enum.rb +154 -63
  154. data/lib/active_record/errors.rb +171 -15
  155. data/lib/active_record/explain.rb +23 -3
  156. data/lib/active_record/explain_registry.rb +11 -6
  157. data/lib/active_record/explain_subscriber.rb +1 -1
  158. data/lib/active_record/fixture_set/file.rb +15 -1
  159. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  160. data/lib/active_record/fixture_set/render_context.rb +2 -0
  161. data/lib/active_record/fixture_set/table_row.rb +70 -14
  162. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  163. data/lib/active_record/fixtures.rb +131 -86
  164. data/lib/active_record/future_result.rb +164 -0
  165. data/lib/active_record/gem_version.rb +3 -3
  166. data/lib/active_record/inheritance.rb +81 -29
  167. data/lib/active_record/insert_all.rb +135 -22
  168. data/lib/active_record/integration.rb +11 -10
  169. data/lib/active_record/internal_metadata.rb +119 -33
  170. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  171. data/lib/active_record/locking/optimistic.rb +36 -21
  172. data/lib/active_record/locking/pessimistic.rb +15 -6
  173. data/lib/active_record/log_subscriber.rb +52 -19
  174. data/lib/active_record/marshalling.rb +56 -0
  175. data/lib/active_record/message_pack.rb +124 -0
  176. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  177. data/lib/active_record/middleware/database_selector.rb +23 -13
  178. data/lib/active_record/middleware/shard_selector.rb +62 -0
  179. data/lib/active_record/migration/command_recorder.rb +112 -14
  180. data/lib/active_record/migration/compatibility.rb +221 -48
  181. data/lib/active_record/migration/default_strategy.rb +23 -0
  182. data/lib/active_record/migration/execution_strategy.rb +19 -0
  183. data/lib/active_record/migration/join_table.rb +1 -1
  184. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  185. data/lib/active_record/migration.rb +358 -171
  186. data/lib/active_record/model_schema.rb +120 -101
  187. data/lib/active_record/nested_attributes.rb +37 -18
  188. data/lib/active_record/no_touching.rb +3 -3
  189. data/lib/active_record/normalization.rb +167 -0
  190. data/lib/active_record/persistence.rb +405 -85
  191. data/lib/active_record/promise.rb +84 -0
  192. data/lib/active_record/query_cache.rb +3 -21
  193. data/lib/active_record/query_logs.rb +174 -0
  194. data/lib/active_record/query_logs_formatter.rb +41 -0
  195. data/lib/active_record/querying.rb +29 -6
  196. data/lib/active_record/railtie.rb +219 -43
  197. data/lib/active_record/railties/controller_runtime.rb +13 -9
  198. data/lib/active_record/railties/databases.rake +188 -252
  199. data/lib/active_record/railties/job_runtime.rb +23 -0
  200. data/lib/active_record/readonly_attributes.rb +41 -3
  201. data/lib/active_record/reflection.rb +241 -80
  202. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  203. data/lib/active_record/relation/batches.rb +192 -63
  204. data/lib/active_record/relation/calculations.rb +219 -90
  205. data/lib/active_record/relation/delegation.rb +27 -13
  206. data/lib/active_record/relation/finder_methods.rb +108 -51
  207. data/lib/active_record/relation/merger.rb +22 -13
  208. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  209. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  210. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  211. data/lib/active_record/relation/predicate_builder.rb +27 -20
  212. data/lib/active_record/relation/query_attribute.rb +30 -12
  213. data/lib/active_record/relation/query_methods.rb +654 -127
  214. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  215. data/lib/active_record/relation/spawn_methods.rb +20 -3
  216. data/lib/active_record/relation/where_clause.rb +10 -19
  217. data/lib/active_record/relation.rb +262 -120
  218. data/lib/active_record/result.rb +37 -11
  219. data/lib/active_record/runtime_registry.rb +18 -13
  220. data/lib/active_record/sanitization.rb +65 -20
  221. data/lib/active_record/schema.rb +36 -22
  222. data/lib/active_record/schema_dumper.rb +73 -24
  223. data/lib/active_record/schema_migration.rb +68 -33
  224. data/lib/active_record/scoping/default.rb +72 -15
  225. data/lib/active_record/scoping/named.rb +5 -13
  226. data/lib/active_record/scoping.rb +65 -34
  227. data/lib/active_record/secure_password.rb +60 -0
  228. data/lib/active_record/secure_token.rb +21 -3
  229. data/lib/active_record/serialization.rb +6 -1
  230. data/lib/active_record/signed_id.rb +10 -8
  231. data/lib/active_record/store.rb +16 -11
  232. data/lib/active_record/suppressor.rb +13 -15
  233. data/lib/active_record/table_metadata.rb +16 -3
  234. data/lib/active_record/tasks/database_tasks.rb +225 -136
  235. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  236. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  237. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  238. data/lib/active_record/test_databases.rb +1 -1
  239. data/lib/active_record/test_fixtures.rb +123 -99
  240. data/lib/active_record/timestamp.rb +29 -18
  241. data/lib/active_record/token_for.rb +113 -0
  242. data/lib/active_record/touch_later.rb +11 -6
  243. data/lib/active_record/transactions.rb +48 -27
  244. data/lib/active_record/translation.rb +3 -3
  245. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  246. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  247. data/lib/active_record/type/internal/timezone.rb +7 -2
  248. data/lib/active_record/type/serialized.rb +9 -5
  249. data/lib/active_record/type/time.rb +4 -0
  250. data/lib/active_record/type/type_map.rb +17 -20
  251. data/lib/active_record/type.rb +1 -2
  252. data/lib/active_record/validations/absence.rb +1 -1
  253. data/lib/active_record/validations/associated.rb +4 -4
  254. data/lib/active_record/validations/numericality.rb +5 -4
  255. data/lib/active_record/validations/presence.rb +5 -28
  256. data/lib/active_record/validations/uniqueness.rb +51 -6
  257. data/lib/active_record/validations.rb +8 -4
  258. data/lib/active_record/version.rb +1 -1
  259. data/lib/active_record.rb +335 -32
  260. data/lib/arel/attributes/attribute.rb +0 -8
  261. data/lib/arel/crud.rb +28 -22
  262. data/lib/arel/delete_manager.rb +18 -4
  263. data/lib/arel/errors.rb +10 -0
  264. data/lib/arel/factory_methods.rb +4 -0
  265. data/lib/arel/filter_predications.rb +9 -0
  266. data/lib/arel/insert_manager.rb +2 -3
  267. data/lib/arel/nodes/and.rb +4 -0
  268. data/lib/arel/nodes/binary.rb +6 -1
  269. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  270. data/lib/arel/nodes/casted.rb +1 -1
  271. data/lib/arel/nodes/cte.rb +36 -0
  272. data/lib/arel/nodes/delete_statement.rb +12 -13
  273. data/lib/arel/nodes/filter.rb +10 -0
  274. data/lib/arel/nodes/fragments.rb +35 -0
  275. data/lib/arel/nodes/function.rb +1 -0
  276. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  277. data/lib/arel/nodes/insert_statement.rb +2 -2
  278. data/lib/arel/nodes/leading_join.rb +8 -0
  279. data/lib/arel/nodes/node.rb +111 -2
  280. data/lib/arel/nodes/select_core.rb +2 -2
  281. data/lib/arel/nodes/select_statement.rb +2 -2
  282. data/lib/arel/nodes/sql_literal.rb +6 -0
  283. data/lib/arel/nodes/table_alias.rb +4 -0
  284. data/lib/arel/nodes/update_statement.rb +8 -3
  285. data/lib/arel/nodes.rb +5 -0
  286. data/lib/arel/predications.rb +13 -3
  287. data/lib/arel/select_manager.rb +10 -4
  288. data/lib/arel/table.rb +9 -6
  289. data/lib/arel/tree_manager.rb +0 -12
  290. data/lib/arel/update_manager.rb +18 -4
  291. data/lib/arel/visitors/dot.rb +80 -90
  292. data/lib/arel/visitors/mysql.rb +16 -3
  293. data/lib/arel/visitors/postgresql.rb +0 -10
  294. data/lib/arel/visitors/to_sql.rb +139 -19
  295. data/lib/arel/visitors/visitor.rb +2 -2
  296. data/lib/arel.rb +18 -3
  297. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  298. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  299. data/lib/rails/generators/active_record/migration.rb +3 -1
  300. data/lib/rails/generators/active_record/model/USAGE +113 -0
  301. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  302. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  303. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  304. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  305. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  306. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  307. metadata +93 -13
  308. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  309. data/lib/active_record/null_relation.rb +0 -67
@@ -3,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,12 @@ module ActiveRecord
52
93
  end
53
94
  end
54
95
 
96
+ # Same as #count, but performs the query asynchronously and returns an
97
+ # ActiveRecord::Promise.
98
+ def async_count(column_name = nil)
99
+ async.count(column_name)
100
+ end
101
+
55
102
  # Calculates the average value on a given column. Returns +nil+ if there's
56
103
  # no row. See #calculate for examples with options.
57
104
  #
@@ -60,6 +107,12 @@ module ActiveRecord
60
107
  calculate(:average, column_name)
61
108
  end
62
109
 
110
+ # Same as #average, but performs the query asynchronously and returns an
111
+ # ActiveRecord::Promise.
112
+ def async_average(column_name)
113
+ async.average(column_name)
114
+ end
115
+
63
116
  # Calculates the minimum value on a given column. The value is returned
64
117
  # with the same data type of the column, or +nil+ if there's no row. See
65
118
  # #calculate for examples with options.
@@ -69,6 +122,12 @@ module ActiveRecord
69
122
  calculate(:minimum, column_name)
70
123
  end
71
124
 
125
+ # Same as #minimum, but performs the query asynchronously and returns an
126
+ # ActiveRecord::Promise.
127
+ def async_minimum(column_name)
128
+ async.minimum(column_name)
129
+ end
130
+
72
131
  # Calculates the maximum value on a given column. The value is returned
73
132
  # with the same data type of the column, or +nil+ if there's no row. See
74
133
  # #calculate for examples with options.
@@ -78,23 +137,31 @@ module ActiveRecord
78
137
  calculate(:maximum, column_name)
79
138
  end
80
139
 
140
+ # Same as #maximum, but performs the query asynchronously and returns an
141
+ # ActiveRecord::Promise.
142
+ def async_maximum(column_name)
143
+ async.maximum(column_name)
144
+ end
145
+
81
146
  # Calculates the sum of values on a given column. The value is returned
82
147
  # with the same data type of the column, +0+ if there's no row. See
83
148
  # #calculate for examples with options.
84
149
  #
85
150
  # Person.sum(:age) # => 4562
86
- def sum(column_name = nil)
151
+ def sum(initial_value_or_column = 0, &block)
87
152
  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()
153
+ map(&block).sum(initial_value_or_column)
93
154
  else
94
- calculate(:sum, column_name)
155
+ calculate(:sum, initial_value_or_column)
95
156
  end
96
157
  end
97
158
 
159
+ # Same as #sum, but performs the query asynchronously and returns an
160
+ # ActiveRecord::Promise.
161
+ def async_sum(identity_or_column = nil)
162
+ async.sum(identity_or_column)
163
+ end
164
+
98
165
  # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
99
166
  # #minimum, and #maximum have been added as shortcuts.
100
167
  #
@@ -127,10 +194,23 @@ module ActiveRecord
127
194
  # ...
128
195
  # end
129
196
  def calculate(operation, column_name)
197
+ operation = operation.to_s.downcase
198
+
199
+ if @none
200
+ case operation
201
+ when "count", "sum"
202
+ result = group_values.any? ? Hash.new : 0
203
+ return @async ? Promise::Complete.new(result) : result
204
+ when "average", "minimum", "maximum"
205
+ result = group_values.any? ? Hash.new : nil
206
+ return @async ? Promise::Complete.new(result) : result
207
+ end
208
+ end
209
+
130
210
  if has_include?(column_name)
131
211
  relation = apply_join_dependency
132
212
 
133
- if operation.to_s.downcase == "count"
213
+ if operation == "count"
134
214
  unless distinct_value || distinct_select?(column_name || select_for_count)
135
215
  relation.distinct!
136
216
  relation.select_values = [ klass.primary_key || table[Arel.star] ]
@@ -146,7 +226,7 @@ module ActiveRecord
146
226
  end
147
227
 
148
228
  # 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.
229
+ # loading an entire record object per row.
150
230
  #
151
231
  # Person.pluck(:name)
152
232
  #
@@ -179,31 +259,45 @@ module ActiveRecord
179
259
  # # => ['0', '27761', '173']
180
260
  #
181
261
  # See also #ids.
182
- #
183
262
  def pluck(*column_names)
263
+ return [] if @none
264
+
184
265
  if loaded? && all_attributes?(column_names)
185
- return records.pluck(*column_names)
266
+ result = records.pluck(*column_names)
267
+ if @async
268
+ return Promise::Complete.new(result)
269
+ else
270
+ return result
271
+ end
186
272
  end
187
273
 
188
274
  if has_include?(column_names.first)
189
275
  relation = apply_join_dependency
190
276
  relation.pluck(*column_names)
191
277
  else
192
- klass.disallow_raw_sql!(column_names)
278
+ klass.disallow_raw_sql!(column_names.flatten)
193
279
  columns = arel_columns(column_names)
194
280
  relation = spawn
195
281
  relation.select_values = columns
196
282
  result = skip_query_cache_if_necessary do
197
283
  if where_clause.contradiction?
198
- ActiveRecord::Result.new([], [])
284
+ ActiveRecord::Result.empty(async: @async)
199
285
  else
200
- klass.connection.select_all(relation.arel, nil)
286
+ klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
201
287
  end
202
288
  end
203
- type_cast_pluck_values(result, columns)
289
+ result.then do |result|
290
+ type_cast_pluck_values(result, columns)
291
+ end
204
292
  end
205
293
  end
206
294
 
295
+ # Same as #pluck, but performs the query asynchronously and returns an
296
+ # ActiveRecord::Promise.
297
+ def async_pluck(*column_names)
298
+ async.pluck(*column_names)
299
+ end
300
+
207
301
  # Pick the value(s) from the named column(s) in the current relation.
208
302
  # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
209
303
  # when you have a relation that's already narrowed down to a single row.
@@ -220,18 +314,61 @@ module ActiveRecord
220
314
  # # => [ 'David', 'david@loudthinking.com' ]
221
315
  def pick(*column_names)
222
316
  if loaded? && all_attributes?(column_names)
223
- return records.pick(*column_names)
317
+ result = records.pick(*column_names)
318
+ return @async ? Promise::Complete.new(result) : result
224
319
  end
225
320
 
226
- limit(1).pluck(*column_names).first
321
+ limit(1).pluck(*column_names).then(&:first)
322
+ end
323
+
324
+ # Same as #pick, but performs the query asynchronously and returns an
325
+ # ActiveRecord::Promise.
326
+ def async_pick(*column_names)
327
+ async.pick(*column_names)
227
328
  end
228
329
 
229
- # Pluck all the ID's for the relation using the table's primary key
330
+ # Returns the base model's ID's for the relation using the table's primary key
230
331
  #
231
332
  # 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
333
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
233
334
  def ids
234
- pluck primary_key
335
+ primary_key_array = Array(primary_key)
336
+
337
+ if loaded?
338
+ result = records.map do |record|
339
+ if primary_key_array.one?
340
+ record._read_attribute(primary_key_array.first)
341
+ else
342
+ primary_key_array.map { |column| record._read_attribute(column) }
343
+ end
344
+ end
345
+ return @async ? Promise::Complete.new(result) : result
346
+ end
347
+
348
+ if has_include?(primary_key)
349
+ relation = apply_join_dependency.group(*primary_key_array)
350
+ return relation.ids
351
+ end
352
+
353
+ columns = arel_columns(primary_key_array)
354
+ relation = spawn
355
+ relation.select_values = columns
356
+
357
+ result = if relation.where_clause.contradiction?
358
+ ActiveRecord::Result.empty
359
+ else
360
+ skip_query_cache_if_necessary do
361
+ klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
362
+ end
363
+ end
364
+
365
+ result.then { |result| type_cast_pluck_values(result, columns) }
366
+ end
367
+
368
+ # Same as #ids, but performs the query asynchronously and returns an
369
+ # ActiveRecord::Promise.
370
+ def async_ids
371
+ async.ids
235
372
  end
236
373
 
237
374
  private
@@ -286,11 +423,12 @@ module ActiveRecord
286
423
  operation == "count" ? column.count(distinct) : column.public_send(operation)
287
424
  end
288
425
 
289
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
426
+ def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
290
427
  if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
291
428
  # Shortcut when limit is zero.
292
429
  return 0 if limit_value == 0
293
430
 
431
+ relation = self
294
432
  query_builder = build_count_subquery(spawn, column_name, distinct)
295
433
  else
296
434
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
@@ -305,29 +443,29 @@ module ActiveRecord
305
443
  query_builder = relation.arel
306
444
  end
307
445
 
308
- result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
446
+ query_result = if relation.where_clause.contradiction?
447
+ ActiveRecord::Result.empty
448
+ else
449
+ skip_query_cache_if_necessary do
450
+ @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
451
+ end
452
+ end
309
453
 
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)
454
+ query_result.then do |result|
455
+ if operation != "count"
456
+ type = column.try(:type_caster) ||
457
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
458
+ type = type.subtype if Enum::EnumType === type
459
+ end
460
+
461
+ type_cast_calculated_value(result.cast_values.first, operation, type)
315
462
  end
316
463
  end
317
464
 
318
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
465
+ def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
319
466
  group_fields = group_values
320
467
  group_fields = group_fields.uniq if group_fields.size > 1
321
468
 
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
469
  if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
332
470
  association = klass._reflect_on_association(group_fields.first)
333
471
  associated = association && association.belongs_to? # only count belongs_to associations
@@ -335,21 +473,24 @@ module ActiveRecord
335
473
  end
336
474
  group_fields = arel_columns(group_fields)
337
475
 
476
+ column_alias_tracker = ColumnAliasTracker.new(connection)
477
+
338
478
  group_aliases = group_fields.map { |field|
339
479
  field = connection.visitor.compile(field) if Arel.arel_node?(field)
340
- column_alias_for(field.to_s.downcase)
480
+ column_alias_tracker.alias_for(field.to_s.downcase)
341
481
  }
342
482
  group_columns = group_aliases.zip(group_fields)
343
483
 
344
484
  column = aggregate_column(column_name)
345
- column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
485
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
346
486
  select_value = operation_over_aggregate_column(column, operation, distinct)
347
- select_value.as(column_alias)
487
+ select_value.as(connection.quote_column_name(column_alias))
348
488
 
349
489
  select_values = [select_value]
350
490
  select_values += self.select_values unless having_clause.empty?
351
491
 
352
492
  select_values.concat group_columns.map { |aliaz, field|
493
+ aliaz = connection.quote_column_name(aliaz)
353
494
  if field.respond_to?(:as)
354
495
  field.as(aliaz)
355
496
  else
@@ -361,60 +502,43 @@ module ActiveRecord
361
502
  relation.group_values = group_fields
362
503
  relation.select_values = select_values
363
504
 
364
- calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
505
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async) }
506
+ result.then do |calculated_data|
507
+ if association
508
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
509
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
510
+ key_records = key_records.index_by(&:id)
511
+ end
365
512
 
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
513
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
514
+ types[aliaz] = col_name.try(:type_caster) ||
515
+ type_for(col_name) do
516
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
517
+ end
518
+ end
371
519
 
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)
520
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
521
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
522
+ hash[col_name] = row[i]
523
+ end
375
524
  end
376
- end
377
525
 
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]
526
+ if operation != "count"
527
+ type = column.try(:type_caster) ||
528
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
529
+ type = type.subtype if Enum::EnumType === type
381
530
  end
382
- end
383
531
 
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)
532
+ hash_rows.each_with_object({}) do |row, result|
533
+ key = group_aliases.map { |aliaz| row[aliaz] }
534
+ key = key.first if key.size == 1
535
+ key = key_records[key] if associated
536
+
537
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
397
538
  end
398
539
  end
399
540
  end
400
541
 
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
542
  def type_for(field, &block)
419
543
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
420
544
  @klass.type_for_attribute(field_name, &block)
@@ -445,16 +569,21 @@ module ActiveRecord
445
569
  result.cast_values(cast_types)
446
570
  end
447
571
 
448
- def type_cast_calculated_value(value, operation)
572
+ def type_cast_calculated_value(value, operation, type)
449
573
  case operation
450
574
  when "count"
451
575
  value.to_i
452
576
  when "sum"
453
- yield value || 0
577
+ type.deserialize(value || 0)
454
578
  when "average"
455
- value&.respond_to?(:to_d) ? value.to_d : value
579
+ case type.type
580
+ when :integer, :decimal
581
+ value&.to_d
582
+ else
583
+ type.deserialize(value)
584
+ end
456
585
  else # "minimum", "maximum"
457
- yield value
586
+ type.deserialize(value)
458
587
  end
459
588
  end
460
589
 
@@ -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: