activerecord 6.1.7 → 7.1.5

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