activerecord 7.1.5.2 → 8.1.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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -60,37 +60,37 @@ module ActiveRecord
60
60
  # Person.distinct.count(:age)
61
61
  # # => counts the number of different age values
62
62
  #
63
- # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
63
+ # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
64
64
  # it returns a Hash whose keys represent the aggregated column,
65
65
  # and the values are the respective amounts:
66
66
  #
67
67
  # Person.group(:city).count
68
68
  # # => { 'Rome' => 5, 'Paris' => 3 }
69
69
  #
70
- # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
70
+ # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
71
71
  # keys are an array containing the individual values of each column and the value
72
- # of each key would be the #count.
72
+ # of each key would be the count.
73
73
  #
74
74
  # Article.group(:status, :category).count
75
75
  # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
76
76
  #
77
- # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
77
+ # If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
78
78
  #
79
79
  # Person.select(:age).count
80
80
  # # => counts the number of different age values
81
81
  #
82
- # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
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
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.
85
+ # When given a block, calls the block with each record in the relation and
86
+ # returns the number of records for which the block returns a truthy value.
88
87
  #
89
88
  # Person.count { |person| person.age > 21 }
90
89
  # # => counts the number of people older that 21
91
90
  #
92
- # Note: If there are a lot of records in the relation, loading all records
93
- # could result in performance issues.
91
+ # If the relation hasn't been loaded yet, calling +count+ with a block will
92
+ # load all records in the relation. If there are a lot of records in the
93
+ # relation, loading all records could result in performance issues.
94
94
  def count(column_name = nil)
95
95
  if block_given?
96
96
  unless column_name.nil?
@@ -159,16 +159,15 @@ module ActiveRecord
159
159
  #
160
160
  # Person.sum(:age) # => 4562
161
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:
162
+ # When given a block, calls the block with each record in the relation and
163
+ # returns the sum of +initial_value_or_column+ plus the block return values:
166
164
  #
167
165
  # Person.sum { |person| person.age } # => 4562
168
166
  # Person.sum(1000) { |person| person.age } # => 5562
169
167
  #
170
- # Note: If there are a lot of records in the relation, loading all records
171
- # could result in performance issues.
168
+ # If the relation hasn't been loaded yet, calling +sum+ with a block will
169
+ # load all records in the relation. If there are a lot of records in the
170
+ # relation, loading all records could result in performance issues.
172
171
  def sum(initial_value_or_column = 0, &block)
173
172
  if block_given?
174
173
  map(&block).sum(initial_value_or_column)
@@ -234,7 +233,7 @@ module ActiveRecord
234
233
  if operation == "count"
235
234
  unless distinct_value || distinct_select?(column_name || select_for_count)
236
235
  relation.distinct!
237
- relation.select_values = [ klass.primary_key || table[Arel.star] ]
236
+ relation.select_values = Array(model.primary_key || table[Arel.star])
238
237
  end
239
238
  # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
240
239
  relation.order_values = [] if group_values.empty?
@@ -275,10 +274,23 @@ module ActiveRecord
275
274
  # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
276
275
  # # => [2, 3]
277
276
  #
277
+ # Comment.joins(:person).pluck(:id, person: :id)
278
+ # # SELECT comments.id, person.id FROM comments INNER JOIN people person ON person.id = comments.person_id
279
+ # # => [[1, 2], [2, 2]]
280
+ #
281
+ # Comment.joins(:person).pluck(:id, person: [:id, :name])
282
+ # # SELECT comments.id, person.id, person.name FROM comments INNER JOIN people person ON person.id = comments.person_id
283
+ # # => [[1, 2, 'David'], [2, 2, 'David']]
284
+ #
278
285
  # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
279
286
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
280
287
  # # => ['0', '27761', '173']
281
288
  #
289
+ # Be aware that #pluck ignores any previous select clauses
290
+ #
291
+ # Person.select(:name).pluck(:id)
292
+ # # SELECT people.id FROM people
293
+ #
282
294
  # See also #ids.
283
295
  def pluck(*column_names)
284
296
  if @none
@@ -302,15 +314,17 @@ module ActiveRecord
302
314
  relation = apply_join_dependency
303
315
  relation.pluck(*column_names)
304
316
  else
305
- klass.disallow_raw_sql!(column_names.flatten)
306
- columns = arel_columns(column_names)
317
+ model.disallow_raw_sql!(flattened_args(column_names))
307
318
  relation = spawn
319
+ columns = relation.arel_columns(column_names)
308
320
  relation.select_values = columns
309
321
  result = skip_query_cache_if_necessary do
310
- if where_clause.contradiction?
322
+ if where_clause.contradiction? && !possible_aggregation?(column_names)
311
323
  ActiveRecord::Result.empty(async: @async)
312
324
  else
313
- klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
325
+ model.with_connection do |c|
326
+ c.select_all(relation.arel, "#{model.name} Pluck", async: @async)
327
+ end
314
328
  end
315
329
  end
316
330
  result.then do |result|
@@ -357,7 +371,7 @@ module ActiveRecord
357
371
  # Returns the base model's ID's for the relation using the table's primary key
358
372
  #
359
373
  # Person.ids # SELECT people.id FROM people
360
- # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
374
+ # Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
361
375
  def ids
362
376
  primary_key_array = Array(primary_key)
363
377
 
@@ -385,7 +399,9 @@ module ActiveRecord
385
399
  ActiveRecord::Result.empty
386
400
  else
387
401
  skip_query_cache_if_necessary do
388
- klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
402
+ model.with_connection do |c|
403
+ c.select_all(relation, "#{model.name} Ids", async: @async)
404
+ end
389
405
  end
390
406
  end
391
407
 
@@ -398,9 +414,21 @@ module ActiveRecord
398
414
  async.ids
399
415
  end
400
416
 
417
+ protected
418
+ def aggregate_column(column_name)
419
+ case column_name
420
+ when Arel::Expressions
421
+ column_name
422
+ when :all
423
+ Arel.star
424
+ else
425
+ arel_column(column_name.to_s)
426
+ end
427
+ end
428
+
401
429
  private
402
430
  def all_attributes?(column_names)
403
- (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
431
+ (column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
404
432
  end
405
433
 
406
434
  def has_include?(column_name)
@@ -438,11 +466,13 @@ module ActiveRecord
438
466
  column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
439
467
  end
440
468
 
441
- def aggregate_column(column_name)
442
- return column_name if Arel::Expressions === column_name
443
-
444
- arel_column(column_name.to_s) do |name|
445
- Arel.sql(column_name == :all ? "*" : name)
469
+ def possible_aggregation?(column_names)
470
+ column_names.all? do |column_name|
471
+ if column_name.is_a?(String)
472
+ column_name.include?("(")
473
+ else
474
+ Arel.arel_node?(column_name)
475
+ end
446
476
  end
447
477
  end
448
478
 
@@ -451,7 +481,7 @@ module ActiveRecord
451
481
  end
452
482
 
453
483
  def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
454
- if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
484
+ if build_count_subquery?(operation, column_name, distinct)
455
485
  # Shortcut when limit is zero.
456
486
  return 0 if limit_value == 0
457
487
 
@@ -461,7 +491,7 @@ module ActiveRecord
461
491
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
462
492
  relation = unscope(:order).distinct!(false)
463
493
 
464
- column = aggregate_column(column_name)
494
+ column = relation.aggregate_column(column_name)
465
495
  select_value = operation_over_aggregate_column(column, operation, distinct)
466
496
  select_value.distinct = true if operation == "sum" && distinct
467
497
 
@@ -471,10 +501,16 @@ module ActiveRecord
471
501
  end
472
502
 
473
503
  query_result = if relation.where_clause.contradiction?
474
- ActiveRecord::Result.empty
504
+ if @async
505
+ FutureResult.wrap(ActiveRecord::Result.empty)
506
+ else
507
+ ActiveRecord::Result.empty
508
+ end
475
509
  else
476
510
  skip_query_cache_if_necessary do
477
- @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
511
+ model.with_connection do |c|
512
+ c.select_all(query_builder, "#{model.name} #{operation.capitalize}", async: @async)
513
+ end
478
514
  end
479
515
  end
480
516
 
@@ -491,84 +527,89 @@ module ActiveRecord
491
527
 
492
528
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
493
529
  group_fields = group_values
494
- group_fields = group_fields.uniq if group_fields.size > 1
495
530
 
496
531
  if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
497
- association = klass._reflect_on_association(group_fields.first)
532
+ association = model._reflect_on_association(group_fields.first)
498
533
  associated = association && association.belongs_to? # only count belongs_to associations
499
534
  group_fields = Array(association.foreign_key) if associated
500
535
  end
501
- group_fields = arel_columns(group_fields)
502
536
 
503
- column_alias_tracker = ColumnAliasTracker.new(connection)
537
+ relation = except(:group).distinct!(false)
538
+ group_fields = relation.arel_columns(group_fields)
504
539
 
505
- group_aliases = group_fields.map { |field|
506
- field = connection.visitor.compile(field) if Arel.arel_node?(field)
507
- column_alias_tracker.alias_for(field.to_s.downcase)
508
- }
509
- group_columns = group_aliases.zip(group_fields)
540
+ model.with_connection do |connection|
541
+ column_alias_tracker = ColumnAliasTracker.new(connection)
510
542
 
511
- column = aggregate_column(column_name)
512
- column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
513
- select_value = operation_over_aggregate_column(column, operation, distinct)
514
- select_value.as(connection.quote_column_name(column_alias))
543
+ group_aliases = group_fields.map { |field|
544
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
545
+ column_alias_tracker.alias_for(field.to_s.downcase)
546
+ }
547
+ group_columns = group_aliases.zip(group_fields)
515
548
 
516
- select_values = [select_value]
517
- select_values += self.select_values unless having_clause.empty?
549
+ column = relation.aggregate_column(column_name)
550
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
551
+ select_value = operation_over_aggregate_column(column, operation, distinct)
552
+ select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
518
553
 
519
- select_values.concat group_columns.map { |aliaz, field|
520
- aliaz = connection.quote_column_name(aliaz)
521
- if field.respond_to?(:as)
522
- field.as(aliaz)
523
- else
524
- "#{field} AS #{aliaz}"
525
- end
526
- }
554
+ select_values = [select_value]
555
+ select_values += self.select_values unless having_clause.empty?
527
556
 
528
- relation = except(:group).distinct!(false)
529
- relation.group_values = group_fields
530
- relation.select_values = select_values
531
-
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
557
+ select_values.concat group_columns.map { |aliaz, field|
558
+ aliaz = model.adapter_class.quote_column_name(aliaz)
559
+ if field.respond_to?(:as)
560
+ field.as(aliaz)
561
+ else
562
+ "#{field} AS #{aliaz}"
563
+ end
564
+ }
539
565
 
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
566
+ relation.group_values = group_fields
567
+ relation.select_values = select_values
568
+
569
+ result = skip_query_cache_if_necessary do
570
+ connection.select_all(relation.arel, "#{model.name} #{operation.capitalize}", async: @async)
545
571
  end
546
572
 
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]
573
+ result.then do |calculated_data|
574
+ if association
575
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
576
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
577
+ key_records = key_records.index_by(&:id)
550
578
  end
551
- end
552
579
 
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
557
- end
580
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
581
+ types[aliaz] = col_name.try(:type_caster) ||
582
+ type_for(col_name) do
583
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
584
+ end
585
+ end
558
586
 
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
587
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
588
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
589
+ hash[col_name] = row[i]
590
+ end
591
+ end
592
+
593
+ if operation != "count"
594
+ type = column.try(:type_caster) ||
595
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
596
+ type = type.subtype if Enum::EnumType === type
597
+ end
598
+
599
+ hash_rows.each_with_object({}) do |row, result|
600
+ key = group_aliases.map { |aliaz| row[aliaz] }
601
+ key = key.first if key.size == 1
602
+ key = key_records[key] if associated
563
603
 
564
- result[key] = type_cast_calculated_value(row[column_alias], operation, type)
604
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
605
+ end
565
606
  end
566
607
  end
567
608
  end
568
609
 
569
610
  def type_for(field, &block)
570
611
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
571
- @klass.type_for_attribute(field_name, &block)
612
+ model.type_for_attribute(field_name, &block)
572
613
  end
573
614
 
574
615
  def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
@@ -581,15 +622,15 @@ module ActiveRecord
581
622
 
582
623
  def type_cast_pluck_values(result, columns)
583
624
  cast_types = if result.columns.size != columns.size
584
- klass.attribute_types
625
+ model.attribute_types
585
626
  else
586
627
  join_dependencies = nil
587
628
  columns.map.with_index do |column, i|
588
629
  column.try(:type_caster) ||
589
- klass.attribute_types.fetch(name = result.columns[i]) do
630
+ model.attribute_types.fetch(name = result.columns[i]) do
590
631
  join_dependencies ||= build_join_dependencies
591
632
  lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
592
- result.column_types[name] || Type.default_value
633
+ result.column_types[i] || Type.default_value
593
634
  end
594
635
  end
595
636
  end
@@ -615,24 +656,33 @@ module ActiveRecord
615
656
  end
616
657
 
617
658
  def select_for_count
618
- if select_values.present?
619
- return select_values.first if select_values.one?
620
- select_values.join(", ")
621
- else
659
+ if select_values.empty?
622
660
  :all
661
+ else
662
+ with_connection do |conn|
663
+ arel_columns(select_values).map { |column| conn.visitor.compile(column) }.join(", ")
664
+ end
623
665
  end
624
666
  end
625
667
 
668
+ def build_count_subquery?(operation, column_name, distinct)
669
+ # SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
670
+ # multiple columns, so we need to use subquery for this.
671
+ operation == "count" &&
672
+ (((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
673
+ end
674
+
626
675
  def build_count_subquery(relation, column_name, distinct)
627
676
  if column_name == :all
628
677
  column_alias = Arel.star
629
678
  relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
679
+ relation.unscope!(:order)
630
680
  else
631
681
  column_alias = Arel.sql("count_column")
632
- relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
682
+ relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
633
683
  end
634
684
 
635
- subquery_alias = Arel.sql("subquery_for_count")
685
+ subquery_alias = Arel.sql("subquery_for_count", retryable: true)
636
686
  select_value = operation_over_aggregate_column(column_alias, "count", false)
637
687
 
638
688
  relation.build_subquery(subquery_alias, select_value)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mutex_m"
4
- require "active_support/core_ext/module/delegation"
5
3
 
6
4
  module ActiveRecord
7
5
  module Delegation # :nodoc:
@@ -23,6 +21,9 @@ module ActiveRecord
23
21
  end
24
22
 
25
23
  module DelegateCache # :nodoc:
24
+ @delegate_base_methods = true
25
+ singleton_class.attr_accessor :delegate_base_methods
26
+
26
27
  def relation_delegate_class(klass)
27
28
  @relation_delegate_cache[klass]
28
29
  end
@@ -67,23 +68,22 @@ module ActiveRecord
67
68
  end
68
69
 
69
70
  class GeneratedRelationMethods < Module # :nodoc:
70
- include Mutex_m
71
+ MUTEX = Mutex.new
71
72
 
72
73
  def generate_method(method)
73
- synchronize do
74
+ MUTEX.synchronize do
74
75
  return if method_defined?(method)
75
76
 
76
- if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
77
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
77
78
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
78
79
  def #{method}(...)
79
- scoping { klass.#{method}(...) }
80
+ scoping { model.#{method}(...) }
80
81
  end
81
82
  RUBY
82
83
  else
83
- define_method(method) do |*args, &block|
84
- scoping { klass.public_send(method, *args, &block) }
84
+ define_method(method) do |*args, **kwargs, &block|
85
+ scoping { model.public_send(method, *args, **kwargs, &block) }
85
86
  end
86
- ruby2_keywords(method)
87
87
  end
88
88
  end
89
89
  end
@@ -94,15 +94,15 @@ module ActiveRecord
94
94
 
95
95
  # This module creates compiled delegation methods dynamically at runtime, which makes
96
96
  # subsequent calls to that method faster by avoiding method_missing. The delegations
97
- # may vary depending on the klass of a relation, so we create a subclass of Relation
98
- # for each different klass, and the delegations are compiled into that subclass only.
97
+ # may vary depending on the model of a relation, so we create a subclass of Relation
98
+ # for each different model, and the delegations are compiled into that subclass only.
99
99
 
100
100
  delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
101
101
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
102
102
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
103
103
  :shuffle, :split, :slice, :index, :rindex, to: :records
104
104
 
105
- delegate :primary_key, :connection, :transaction, to: :klass
105
+ delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model
106
106
 
107
107
  module ClassSpecificRelation # :nodoc:
108
108
  extend ActiveSupport::Concern
@@ -114,33 +114,40 @@ module ActiveRecord
114
114
  end
115
115
 
116
116
  private
117
- def method_missing(method, *args, &block)
118
- if @klass.respond_to?(method)
119
- unless Delegation.uncacheable_methods.include?(method)
120
- @klass.generate_relation_method(method)
117
+ def method_missing(method, ...)
118
+ if model.respond_to?(method)
119
+ if !DelegateCache.delegate_base_methods && Base.respond_to?(method)
120
+ # A common mistake in Active Record's own code is to call `ActiveRecord::Base`
121
+ # class methods on Association. It works because it's automatically delegated, but
122
+ # can introduce subtle bugs because it sets the global scope.
123
+ # We can't deprecate this behavior because gems might depend on it, however we
124
+ # can ban it from Active Record's own test suite to avoid regressions.
125
+ raise NotImplementedError, "Active Record code shouldn't rely on association delegation into ActiveRecord::Base methods"
126
+ elsif !Delegation.uncacheable_methods.include?(method)
127
+ model.generate_relation_method(method)
121
128
  end
122
- scoping { @klass.public_send(method, *args, &block) }
129
+
130
+ scoping { model.public_send(method, ...) }
123
131
  else
124
132
  super
125
133
  end
126
134
  end
127
- ruby2_keywords(:method_missing)
128
135
  end
129
136
 
130
137
  module ClassMethods # :nodoc:
131
- def create(klass, *args, **kwargs)
132
- relation_class_for(klass).new(klass, *args, **kwargs)
138
+ def create(model, ...)
139
+ relation_class_for(model).new(model, ...)
133
140
  end
134
141
 
135
142
  private
136
- def relation_class_for(klass)
137
- klass.relation_delegate_class(self)
143
+ def relation_class_for(model)
144
+ model.relation_delegate_class(self)
138
145
  end
139
146
  end
140
147
 
141
148
  private
142
149
  def respond_to_missing?(method, _)
143
- super || @klass.respond_to?(method)
150
+ super || model.respond_to?(method)
144
151
  end
145
152
  end
146
153
  end