activerecord 7.0.8.7 → 7.2.2.1

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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -1944
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  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 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +54 -12
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -3,6 +3,7 @@
3
3
  require "active_support/core_ext/enumerable"
4
4
 
5
5
  module ActiveRecord
6
+ # = Active Record \Calculations
6
7
  module Calculations
7
8
  class ColumnAliasTracker # :nodoc:
8
9
  def initialize(connection)
@@ -71,8 +72,7 @@ module ActiveRecord
71
72
  # of each key would be the #count.
72
73
  #
73
74
  # Article.group(:status, :category).count
74
- # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
75
- # # ["published", "business"]=>0, ["published", "technology"]=>2}
75
+ # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
76
76
  #
77
77
  # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
78
78
  #
@@ -81,6 +81,16 @@ module ActiveRecord
81
81
  #
82
82
  # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
83
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.
84
94
  def count(column_name = nil)
85
95
  if block_given?
86
96
  unless column_name.nil?
@@ -93,6 +103,12 @@ module ActiveRecord
93
103
  end
94
104
  end
95
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
+
96
112
  # Calculates the average value on a given column. Returns +nil+ if there's
97
113
  # no row. See #calculate for examples with options.
98
114
  #
@@ -101,6 +117,12 @@ module ActiveRecord
101
117
  calculate(:average, column_name)
102
118
  end
103
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
+
104
126
  # Calculates the minimum value on a given column. The value is returned
105
127
  # with the same data type of the column, or +nil+ if there's no row. See
106
128
  # #calculate for examples with options.
@@ -110,6 +132,12 @@ module ActiveRecord
110
132
  calculate(:minimum, column_name)
111
133
  end
112
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
+
113
141
  # Calculates the maximum value on a given column. The value is returned
114
142
  # with the same data type of the column, or +nil+ if there's no row. See
115
143
  # #calculate for examples with options.
@@ -119,32 +147,42 @@ module ActiveRecord
119
147
  calculate(:maximum, column_name)
120
148
  end
121
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
+
122
156
  # Calculates the sum of values on a given column. The value is returned
123
157
  # with the same data type of the column, +0+ if there's no row. See
124
158
  # #calculate for examples with options.
125
159
  #
126
160
  # Person.sum(:age) # => 4562
127
- def sum(identity_or_column = nil, &block)
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)
128
173
  if block_given?
129
- values = map(&block)
130
- if identity_or_column.nil? && (values.first.is_a?(Numeric) || values.first(1) == [] || values.first.respond_to?(:coerce))
131
- identity_or_column = 0
132
- end
133
-
134
- if identity_or_column.nil?
135
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
136
- Rails 7.0 has deprecated Enumerable.sum in favor of Ruby's native implementation available since 2.4.
137
- Sum of non-numeric elements requires an initial argument.
138
- MSG
139
- values.inject(:+) || 0
140
- else
141
- values.sum(identity_or_column)
142
- end
174
+ map(&block).sum(initial_value_or_column)
143
175
  else
144
- calculate(:sum, identity_or_column)
176
+ calculate(:sum, initial_value_or_column)
145
177
  end
146
178
  end
147
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
+
148
186
  # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
149
187
  # #minimum, and #maximum have been added as shortcuts.
150
188
  #
@@ -177,13 +215,26 @@ module ActiveRecord
177
215
  # ...
178
216
  # end
179
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
+
180
231
  if has_include?(column_name)
181
232
  relation = apply_join_dependency
182
233
 
183
- if operation.to_s.downcase == "count"
234
+ if operation == "count"
184
235
  unless distinct_value || distinct_select?(column_name || select_for_count)
185
236
  relation.distinct!
186
- relation.select_values = [ klass.primary_key || table[Arel.star] ]
237
+ relation.select_values = Array(klass.primary_key || table[Arel.star])
187
238
  end
188
239
  # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
189
240
  relation.order_values = [] if group_values.empty?
@@ -224,36 +275,62 @@ module ActiveRecord
224
275
  # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
225
276
  # # => [2, 3]
226
277
  #
278
+ # Comment.joins(:person).pluck(:id, person: [:id])
279
+ # # SELECT comments.id, people.id FROM comments INNER JOIN people on comments.person_id = people.id
280
+ # # => [[1, 2], [2, 2]]
281
+ #
227
282
  # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
228
283
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
229
284
  # # => ['0', '27761', '173']
230
285
  #
231
286
  # See also #ids.
232
- #
233
287
  def pluck(*column_names)
288
+ if @none
289
+ if @async
290
+ return Promise::Complete.new([])
291
+ else
292
+ return []
293
+ end
294
+ end
295
+
234
296
  if loaded? && all_attributes?(column_names)
235
- return records.pluck(*column_names)
297
+ result = records.pluck(*column_names)
298
+ if @async
299
+ return Promise::Complete.new(result)
300
+ else
301
+ return result
302
+ end
236
303
  end
237
304
 
238
305
  if has_include?(column_names.first)
239
306
  relation = apply_join_dependency
240
307
  relation.pluck(*column_names)
241
308
  else
242
- klass.disallow_raw_sql!(column_names)
243
- columns = arel_columns(column_names)
309
+ klass.disallow_raw_sql!(flattened_args(column_names))
244
310
  relation = spawn
311
+ columns = relation.arel_columns(column_names)
245
312
  relation.select_values = columns
246
313
  result = skip_query_cache_if_necessary do
247
314
  if where_clause.contradiction?
248
- ActiveRecord::Result.empty
315
+ ActiveRecord::Result.empty(async: @async)
249
316
  else
250
- klass.connection.select_all(relation.arel, "#{klass.name} Pluck")
317
+ klass.with_connection do |c|
318
+ c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
319
+ end
251
320
  end
252
321
  end
253
- type_cast_pluck_values(result, columns)
322
+ result.then do |result|
323
+ type_cast_pluck_values(result, columns)
324
+ end
254
325
  end
255
326
  end
256
327
 
328
+ # Same as #pluck, but performs the query asynchronously and returns an
329
+ # ActiveRecord::Promise.
330
+ def async_pluck(*column_names)
331
+ async.pluck(*column_names)
332
+ end
333
+
257
334
  # Pick the value(s) from the named column(s) in the current relation.
258
335
  # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
259
336
  # when you have a relation that's already narrowed down to a single row.
@@ -270,18 +347,63 @@ module ActiveRecord
270
347
  # # => [ 'David', 'david@loudthinking.com' ]
271
348
  def pick(*column_names)
272
349
  if loaded? && all_attributes?(column_names)
273
- return records.pick(*column_names)
350
+ result = records.pick(*column_names)
351
+ return @async ? Promise::Complete.new(result) : result
274
352
  end
275
353
 
276
- limit(1).pluck(*column_names).first
354
+ limit(1).pluck(*column_names).then(&:first)
277
355
  end
278
356
 
279
- # Pluck all the ID's for the relation using the table's primary key
357
+ # Same as #pick, but performs the query asynchronously and returns an
358
+ # ActiveRecord::Promise.
359
+ def async_pick(*column_names)
360
+ async.pick(*column_names)
361
+ end
362
+
363
+ # Returns the base model's ID's for the relation using the table's primary key
280
364
  #
281
365
  # Person.ids # SELECT people.id FROM people
282
- # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
366
+ # Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
283
367
  def ids
284
- pluck primary_key
368
+ primary_key_array = Array(primary_key)
369
+
370
+ if loaded?
371
+ result = records.map do |record|
372
+ if primary_key_array.one?
373
+ record._read_attribute(primary_key_array.first)
374
+ else
375
+ primary_key_array.map { |column| record._read_attribute(column) }
376
+ end
377
+ end
378
+ return @async ? Promise::Complete.new(result) : result
379
+ end
380
+
381
+ if has_include?(primary_key)
382
+ relation = apply_join_dependency.group(*primary_key_array)
383
+ return relation.ids
384
+ end
385
+
386
+ columns = arel_columns(primary_key_array)
387
+ relation = spawn
388
+ relation.select_values = columns
389
+
390
+ result = if relation.where_clause.contradiction?
391
+ ActiveRecord::Result.empty
392
+ else
393
+ skip_query_cache_if_necessary do
394
+ klass.with_connection do |c|
395
+ c.select_all(relation, "#{klass.name} Ids", async: @async)
396
+ end
397
+ end
398
+ end
399
+
400
+ result.then { |result| type_cast_pluck_values(result, columns) }
401
+ end
402
+
403
+ # Same as #ids, but performs the query asynchronously and returns an
404
+ # ActiveRecord::Promise.
405
+ def async_ids
406
+ async.ids
285
407
  end
286
408
 
287
409
  private
@@ -328,7 +450,7 @@ module ActiveRecord
328
450
  return column_name if Arel::Expressions === column_name
329
451
 
330
452
  arel_column(column_name.to_s) do |name|
331
- Arel.sql(column_name == :all ? "*" : name)
453
+ column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
332
454
  end
333
455
  end
334
456
 
@@ -337,10 +459,11 @@ module ActiveRecord
337
459
  end
338
460
 
339
461
  def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
340
- if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
462
+ if build_count_subquery?(operation, column_name, distinct)
341
463
  # Shortcut when limit is zero.
342
464
  return 0 if limit_value == 0
343
465
 
466
+ relation = self
344
467
  query_builder = build_count_subquery(spawn, column_name, distinct)
345
468
  else
346
469
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
@@ -355,15 +478,25 @@ module ActiveRecord
355
478
  query_builder = relation.arel
356
479
  end
357
480
 
358
- result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}") }
359
-
360
- if operation != "count"
361
- type = column.try(:type_caster) ||
362
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
363
- type = type.subtype if Enum::EnumType === type
481
+ query_result = if relation.where_clause.contradiction?
482
+ ActiveRecord::Result.empty
483
+ else
484
+ skip_query_cache_if_necessary do
485
+ @klass.with_connection do |c|
486
+ c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
487
+ end
488
+ end
364
489
  end
365
490
 
366
- type_cast_calculated_value(result.cast_values.first, operation, type)
491
+ query_result.then do |result|
492
+ if operation != "count"
493
+ type = column.try(:type_caster) ||
494
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
495
+ type = type.subtype if Enum::EnumType === type
496
+ end
497
+
498
+ type_cast_calculated_value(result.cast_values.first, operation, type)
499
+ end
367
500
  end
368
501
 
369
502
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
@@ -377,68 +510,74 @@ module ActiveRecord
377
510
  end
378
511
  group_fields = arel_columns(group_fields)
379
512
 
380
- column_alias_tracker = ColumnAliasTracker.new(connection)
513
+ @klass.with_connection do |connection|
514
+ column_alias_tracker = ColumnAliasTracker.new(connection)
381
515
 
382
- group_aliases = group_fields.map { |field|
383
- field = connection.visitor.compile(field) if Arel.arel_node?(field)
384
- column_alias_tracker.alias_for(field.to_s.downcase)
385
- }
386
- group_columns = group_aliases.zip(group_fields)
516
+ group_aliases = group_fields.map { |field|
517
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
518
+ column_alias_tracker.alias_for(field.to_s.downcase)
519
+ }
520
+ group_columns = group_aliases.zip(group_fields)
387
521
 
388
- column = aggregate_column(column_name)
389
- column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
390
- select_value = operation_over_aggregate_column(column, operation, distinct)
391
- select_value.as(connection.quote_column_name(column_alias))
522
+ column = aggregate_column(column_name)
523
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
524
+ select_value = operation_over_aggregate_column(column, operation, distinct)
525
+ select_value.as(adapter_class.quote_column_name(column_alias))
392
526
 
393
- select_values = [select_value]
394
- select_values += self.select_values unless having_clause.empty?
527
+ select_values = [select_value]
528
+ select_values += self.select_values unless having_clause.empty?
395
529
 
396
- select_values.concat group_columns.map { |aliaz, field|
397
- aliaz = connection.quote_column_name(aliaz)
398
- if field.respond_to?(:as)
399
- field.as(aliaz)
400
- else
401
- "#{field} AS #{aliaz}"
402
- end
403
- }
530
+ select_values.concat group_columns.map { |aliaz, field|
531
+ aliaz = adapter_class.quote_column_name(aliaz)
532
+ if field.respond_to?(:as)
533
+ field.as(aliaz)
534
+ else
535
+ "#{field} AS #{aliaz}"
536
+ end
537
+ }
404
538
 
405
- relation = except(:group).distinct!(false)
406
- relation.group_values = group_fields
407
- relation.select_values = select_values
539
+ relation = except(:group).distinct!(false)
540
+ relation.group_values = group_fields
541
+ relation.select_values = select_values
408
542
 
409
- calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}") }
543
+ result = skip_query_cache_if_necessary do
544
+ connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async)
545
+ end
410
546
 
411
- if association
412
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
413
- key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
414
- key_records = key_records.index_by(&:id)
415
- end
547
+ result.then do |calculated_data|
548
+ if association
549
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
550
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
551
+ key_records = key_records.index_by(&:id)
552
+ end
416
553
 
417
- key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
418
- types[aliaz] = col_name.try(:type_caster) ||
419
- type_for(col_name) do
420
- calculated_data.column_types.fetch(aliaz, Type.default_value)
554
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
555
+ types[aliaz] = col_name.try(:type_caster) ||
556
+ type_for(col_name) do
557
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
558
+ end
421
559
  end
422
- end
423
560
 
424
- hash_rows = calculated_data.cast_values(key_types).map! do |row|
425
- calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
426
- hash[col_name] = row[i]
427
- end
428
- end
561
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
562
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
563
+ hash[col_name] = row[i]
564
+ end
565
+ end
429
566
 
430
- if operation != "count"
431
- type = column.try(:type_caster) ||
432
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
433
- type = type.subtype if Enum::EnumType === type
434
- end
567
+ if operation != "count"
568
+ type = column.try(:type_caster) ||
569
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
570
+ type = type.subtype if Enum::EnumType === type
571
+ end
435
572
 
436
- hash_rows.each_with_object({}) do |row, result|
437
- key = group_aliases.map { |aliaz| row[aliaz] }
438
- key = key.first if key.size == 1
439
- key = key_records[key] if associated
573
+ hash_rows.each_with_object({}) do |row, result|
574
+ key = group_aliases.map { |aliaz| row[aliaz] }
575
+ key = key.first if key.size == 1
576
+ key = key_records[key] if associated
440
577
 
441
- result[key] = type_cast_calculated_value(row[column_alias], operation, type)
578
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
579
+ end
580
+ end
442
581
  end
443
582
  end
444
583
 
@@ -465,7 +604,7 @@ module ActiveRecord
465
604
  klass.attribute_types.fetch(name = result.columns[i]) do
466
605
  join_dependencies ||= build_join_dependencies
467
606
  lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
468
- result.column_types[name] || Type.default_value
607
+ result.column_types[i] || Type.default_value
469
608
  end
470
609
  end
471
610
  end
@@ -493,12 +632,30 @@ module ActiveRecord
493
632
  def select_for_count
494
633
  if select_values.present?
495
634
  return select_values.first if select_values.one?
496
- select_values.join(", ")
635
+
636
+ select_values.map do |field|
637
+ column = arel_column(field.to_s) do |attr_name|
638
+ Arel.sql(attr_name)
639
+ end
640
+
641
+ if column.is_a?(Arel::Nodes::SqlLiteral)
642
+ column
643
+ else
644
+ "#{adapter_class.quote_table_name(column.relation.name)}.#{adapter_class.quote_column_name(column.name)}"
645
+ end
646
+ end.join(", ")
497
647
  else
498
648
  :all
499
649
  end
500
650
  end
501
651
 
652
+ def build_count_subquery?(operation, column_name, distinct)
653
+ # SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
654
+ # multiple columns, so we need to use subquery for this.
655
+ operation == "count" &&
656
+ (((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
657
+ end
658
+
502
659
  def build_count_subquery(relation, column_name, distinct)
503
660
  if column_name == :all
504
661
  column_alias = Arel.star
@@ -508,7 +665,7 @@ module ActiveRecord
508
665
  relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
509
666
  end
510
667
 
511
- subquery_alias = Arel.sql("subquery_for_count")
668
+ subquery_alias = Arel.sql("subquery_for_count", retryable: true)
512
669
  select_value = operation_over_aggregate_column(column_alias, "count", false)
513
670
 
514
671
  relation.build_subquery(subquery_alias, select_value)
@@ -1,10 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mutex_m"
4
3
  require "active_support/core_ext/module/delegation"
5
4
 
6
5
  module ActiveRecord
7
6
  module Delegation # :nodoc:
7
+ class << self
8
+ def delegated_classes
9
+ [
10
+ ActiveRecord::Relation,
11
+ ActiveRecord::Associations::CollectionProxy,
12
+ ActiveRecord::AssociationRelation,
13
+ ActiveRecord::DisableJoinsAssociationRelation,
14
+ ]
15
+ end
16
+
17
+ def uncacheable_methods
18
+ @uncacheable_methods ||= (
19
+ delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods
20
+ ).to_set.freeze
21
+ end
22
+ end
23
+
8
24
  module DelegateCache # :nodoc:
9
25
  def relation_delegate_class(klass)
10
26
  @relation_delegate_cache[klass]
@@ -12,12 +28,7 @@ module ActiveRecord
12
28
 
13
29
  def initialize_relation_delegate_cache
14
30
  @relation_delegate_cache = cache = {}
15
- [
16
- ActiveRecord::Relation,
17
- ActiveRecord::Associations::CollectionProxy,
18
- ActiveRecord::AssociationRelation,
19
- ActiveRecord::DisableJoinsAssociationRelation
20
- ].each do |klass|
31
+ Delegation.delegated_classes.each do |klass|
21
32
  delegate = Class.new(klass) {
22
33
  include ClassSpecificRelation
23
34
  }
@@ -55,23 +66,22 @@ module ActiveRecord
55
66
  end
56
67
 
57
68
  class GeneratedRelationMethods < Module # :nodoc:
58
- include Mutex_m
69
+ MUTEX = Mutex.new
59
70
 
60
71
  def generate_method(method)
61
- synchronize do
72
+ MUTEX.synchronize do
62
73
  return if method_defined?(method)
63
74
 
64
- if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
75
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
65
76
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
66
77
  def #{method}(...)
67
78
  scoping { klass.#{method}(...) }
68
79
  end
69
80
  RUBY
70
81
  else
71
- define_method(method) do |*args, &block|
72
- scoping { klass.public_send(method, *args, &block) }
82
+ define_method(method) do |*args, **kwargs, &block|
83
+ scoping { klass.public_send(method, *args, **kwargs, &block) }
73
84
  end
74
- ruby2_keywords(method)
75
85
  end
76
86
  end
77
87
  end
@@ -85,12 +95,12 @@ module ActiveRecord
85
95
  # may vary depending on the klass of a relation, so we create a subclass of Relation
86
96
  # for each different klass, and the delegations are compiled into that subclass only.
87
97
 
88
- delegate :to_xml, :encode_with, :length, :each, :join,
98
+ delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
89
99
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
90
100
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
91
101
  :shuffle, :split, :slice, :index, :rindex, to: :records
92
102
 
93
- delegate :primary_key, :connection, to: :klass
103
+ delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
94
104
 
95
105
  module ClassSpecificRelation # :nodoc:
96
106
  extend ActiveSupport::Concern
@@ -102,15 +112,16 @@ module ActiveRecord
102
112
  end
103
113
 
104
114
  private
105
- def method_missing(method, *args, &block)
115
+ def method_missing(method, ...)
106
116
  if @klass.respond_to?(method)
107
- @klass.generate_relation_method(method)
108
- scoping { @klass.public_send(method, *args, &block) }
117
+ unless Delegation.uncacheable_methods.include?(method)
118
+ @klass.generate_relation_method(method)
119
+ end
120
+ scoping { @klass.public_send(method, ...) }
109
121
  else
110
122
  super
111
123
  end
112
124
  end
113
- ruby2_keywords(:method_missing)
114
125
  end
115
126
 
116
127
  module ClassMethods # :nodoc: