activerecord 6.1.6 → 7.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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: