activerecord 6.1.7.6 → 7.0.8.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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1575 -1016
  3. data/README.rdoc +3 -3
  4. data/lib/active_record/aggregations.rb +1 -1
  5. data/lib/active_record/association_relation.rb +0 -10
  6. data/lib/active_record/associations/association.rb +33 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -3
  8. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  10. data/lib/active_record/associations/builder/association.rb +8 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  12. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  13. data/lib/active_record/associations/builder/has_many.rb +3 -2
  14. data/lib/active_record/associations/builder/has_one.rb +2 -1
  15. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  16. data/lib/active_record/associations/collection_association.rb +20 -22
  17. data/lib/active_record/associations/collection_proxy.rb +15 -5
  18. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  19. data/lib/active_record/associations/has_many_association.rb +8 -5
  20. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  21. data/lib/active_record/associations/has_one_association.rb +10 -7
  22. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency.rb +23 -15
  24. data/lib/active_record/associations/preloader/association.rb +186 -52
  25. data/lib/active_record/associations/preloader/batch.rb +48 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +138 -100
  32. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +8 -6
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +19 -22
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +8 -23
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +14 -16
  47. data/lib/active_record/coders/yaml_column.rb +4 -8
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +52 -23
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +82 -25
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +144 -82
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +115 -85
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +37 -25
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -23
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +4 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  68. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  70. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +19 -1
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -17
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/quoting.rb +76 -73
  83. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  86. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +40 -21
  88. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  89. data/lib/active_record/connection_adapters/postgresql_adapter.rb +207 -106
  90. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  91. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  92. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +33 -18
  93. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -0
  94. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +19 -17
  95. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +98 -36
  96. data/lib/active_record/connection_adapters.rb +6 -5
  97. data/lib/active_record/connection_handling.rb +49 -55
  98. data/lib/active_record/core.rb +123 -148
  99. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  100. data/lib/active_record/database_configurations/database_config.rb +12 -9
  101. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  102. data/lib/active_record/database_configurations/url_config.rb +2 -2
  103. data/lib/active_record/database_configurations.rb +15 -32
  104. data/lib/active_record/delegated_type.rb +53 -12
  105. data/lib/active_record/destroy_association_async_job.rb +1 -1
  106. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  107. data/lib/active_record/dynamic_matchers.rb +1 -1
  108. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  109. data/lib/active_record/encryption/cipher.rb +53 -0
  110. data/lib/active_record/encryption/config.rb +44 -0
  111. data/lib/active_record/encryption/configurable.rb +67 -0
  112. data/lib/active_record/encryption/context.rb +35 -0
  113. data/lib/active_record/encryption/contexts.rb +72 -0
  114. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  115. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  116. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  117. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  118. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  119. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  120. data/lib/active_record/encryption/encryptor.rb +155 -0
  121. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  122. data/lib/active_record/encryption/errors.rb +15 -0
  123. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  124. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  125. data/lib/active_record/encryption/key.rb +28 -0
  126. data/lib/active_record/encryption/key_generator.rb +42 -0
  127. data/lib/active_record/encryption/key_provider.rb +46 -0
  128. data/lib/active_record/encryption/message.rb +33 -0
  129. data/lib/active_record/encryption/message_serializer.rb +90 -0
  130. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  131. data/lib/active_record/encryption/properties.rb +76 -0
  132. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  133. data/lib/active_record/encryption/scheme.rb +99 -0
  134. data/lib/active_record/encryption.rb +55 -0
  135. data/lib/active_record/enum.rb +50 -43
  136. data/lib/active_record/errors.rb +67 -4
  137. data/lib/active_record/explain_registry.rb +11 -6
  138. data/lib/active_record/explain_subscriber.rb +1 -1
  139. data/lib/active_record/fixture_set/file.rb +15 -1
  140. data/lib/active_record/fixture_set/table_row.rb +41 -6
  141. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  142. data/lib/active_record/fixtures.rb +20 -23
  143. data/lib/active_record/future_result.rb +139 -0
  144. data/lib/active_record/gem_version.rb +5 -5
  145. data/lib/active_record/inheritance.rb +55 -17
  146. data/lib/active_record/insert_all.rb +80 -14
  147. data/lib/active_record/integration.rb +4 -3
  148. data/lib/active_record/internal_metadata.rb +1 -5
  149. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  150. data/lib/active_record/locking/optimistic.rb +36 -21
  151. data/lib/active_record/locking/pessimistic.rb +10 -4
  152. data/lib/active_record/log_subscriber.rb +23 -7
  153. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  154. data/lib/active_record/middleware/database_selector.rb +18 -6
  155. data/lib/active_record/middleware/shard_selector.rb +60 -0
  156. data/lib/active_record/migration/command_recorder.rb +8 -9
  157. data/lib/active_record/migration/compatibility.rb +93 -46
  158. data/lib/active_record/migration/join_table.rb +1 -1
  159. data/lib/active_record/migration.rb +167 -87
  160. data/lib/active_record/model_schema.rb +58 -59
  161. data/lib/active_record/nested_attributes.rb +13 -12
  162. data/lib/active_record/no_touching.rb +3 -3
  163. data/lib/active_record/null_relation.rb +2 -6
  164. data/lib/active_record/persistence.rb +231 -61
  165. data/lib/active_record/query_cache.rb +2 -2
  166. data/lib/active_record/query_logs.rb +149 -0
  167. data/lib/active_record/querying.rb +16 -6
  168. data/lib/active_record/railtie.rb +136 -22
  169. data/lib/active_record/railties/controller_runtime.rb +4 -5
  170. data/lib/active_record/railties/databases.rake +78 -136
  171. data/lib/active_record/readonly_attributes.rb +11 -0
  172. data/lib/active_record/reflection.rb +80 -49
  173. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  174. data/lib/active_record/relation/batches.rb +6 -6
  175. data/lib/active_record/relation/calculations.rb +92 -60
  176. data/lib/active_record/relation/delegation.rb +7 -7
  177. data/lib/active_record/relation/finder_methods.rb +31 -35
  178. data/lib/active_record/relation/merger.rb +20 -13
  179. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -1
  180. data/lib/active_record/relation/predicate_builder.rb +1 -6
  181. data/lib/active_record/relation/query_attribute.rb +28 -11
  182. data/lib/active_record/relation/query_methods.rb +304 -68
  183. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  184. data/lib/active_record/relation/spawn_methods.rb +2 -2
  185. data/lib/active_record/relation/where_clause.rb +10 -19
  186. data/lib/active_record/relation.rb +189 -88
  187. data/lib/active_record/result.rb +23 -11
  188. data/lib/active_record/runtime_registry.rb +9 -13
  189. data/lib/active_record/sanitization.rb +17 -12
  190. data/lib/active_record/schema.rb +38 -23
  191. data/lib/active_record/schema_dumper.rb +29 -19
  192. data/lib/active_record/schema_migration.rb +4 -4
  193. data/lib/active_record/scoping/default.rb +60 -13
  194. data/lib/active_record/scoping/named.rb +3 -11
  195. data/lib/active_record/scoping.rb +64 -34
  196. data/lib/active_record/serialization.rb +6 -1
  197. data/lib/active_record/signed_id.rb +3 -3
  198. data/lib/active_record/store.rb +2 -2
  199. data/lib/active_record/suppressor.rb +11 -15
  200. data/lib/active_record/table_metadata.rb +6 -2
  201. data/lib/active_record/tasks/database_tasks.rb +127 -60
  202. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  203. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  204. data/lib/active_record/test_databases.rb +1 -1
  205. data/lib/active_record/test_fixtures.rb +9 -6
  206. data/lib/active_record/timestamp.rb +3 -4
  207. data/lib/active_record/transactions.rb +12 -17
  208. data/lib/active_record/translation.rb +3 -3
  209. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  210. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  211. data/lib/active_record/type/internal/timezone.rb +2 -2
  212. data/lib/active_record/type/serialized.rb +9 -5
  213. data/lib/active_record/type/type_map.rb +17 -20
  214. data/lib/active_record/type.rb +1 -2
  215. data/lib/active_record/validations/associated.rb +4 -4
  216. data/lib/active_record/validations/presence.rb +2 -2
  217. data/lib/active_record/validations/uniqueness.rb +4 -4
  218. data/lib/active_record/version.rb +1 -1
  219. data/lib/active_record.rb +225 -27
  220. data/lib/arel/attributes/attribute.rb +0 -8
  221. data/lib/arel/crud.rb +28 -22
  222. data/lib/arel/delete_manager.rb +18 -4
  223. data/lib/arel/filter_predications.rb +9 -0
  224. data/lib/arel/insert_manager.rb +2 -3
  225. data/lib/arel/nodes/and.rb +4 -0
  226. data/lib/arel/nodes/casted.rb +1 -1
  227. data/lib/arel/nodes/delete_statement.rb +12 -13
  228. data/lib/arel/nodes/filter.rb +10 -0
  229. data/lib/arel/nodes/function.rb +1 -0
  230. data/lib/arel/nodes/insert_statement.rb +2 -2
  231. data/lib/arel/nodes/select_core.rb +2 -2
  232. data/lib/arel/nodes/select_statement.rb +2 -2
  233. data/lib/arel/nodes/update_statement.rb +8 -3
  234. data/lib/arel/nodes.rb +1 -0
  235. data/lib/arel/predications.rb +11 -3
  236. data/lib/arel/select_manager.rb +10 -4
  237. data/lib/arel/table.rb +0 -1
  238. data/lib/arel/tree_manager.rb +0 -12
  239. data/lib/arel/update_manager.rb +18 -4
  240. data/lib/arel/visitors/dot.rb +80 -90
  241. data/lib/arel/visitors/mysql.rb +8 -2
  242. data/lib/arel/visitors/postgresql.rb +0 -10
  243. data/lib/arel/visitors/to_sql.rb +58 -2
  244. data/lib/arel.rb +2 -1
  245. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  246. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  247. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  248. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  249. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  250. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  251. metadata +55 -11
@@ -5,13 +5,27 @@ module ActiveRecord
5
5
  class BatchEnumerator
6
6
  include Enumerable
7
7
 
8
- def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
8
+ def initialize(of: 1000, start: nil, finish: nil, relation:) # :nodoc:
9
9
  @of = of
10
10
  @relation = relation
11
11
  @start = start
12
12
  @finish = finish
13
13
  end
14
14
 
15
+ # The primary key value from which the BatchEnumerator starts, inclusive of the value.
16
+ attr_reader :start
17
+
18
+ # The primary key value at which the BatchEnumerator ends, inclusive of the value.
19
+ attr_reader :finish
20
+
21
+ # The relation from which the BatchEnumerator yields batches.
22
+ attr_reader :relation
23
+
24
+ # The size of the batches yielded by the BatchEnumerator.
25
+ def batch_size
26
+ @of
27
+ end
28
+
15
29
  # Looping through a collection of records from the database (using the
16
30
  # +all+ method, for example) is very inefficient since it will try to
17
31
  # instantiate all the objects at once.
@@ -33,11 +47,11 @@ module ActiveRecord
33
47
  # Person.in_batches.each_record.with_index do |person, index|
34
48
  # person.award_trophy(index + 1)
35
49
  # end
36
- def each_record
50
+ def each_record(&block)
37
51
  return to_enum(:each_record) unless block_given?
38
52
 
39
53
  @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
40
- relation.records.each { |record| yield record }
54
+ relation.records.each(&block)
41
55
  end
42
56
  end
43
57
 
@@ -75,9 +89,9 @@ module ActiveRecord
75
89
  # Person.in_batches.each do |relation|
76
90
  # relation.update_all(awesome: true)
77
91
  # end
78
- def each
92
+ def each(&block)
79
93
  enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
80
- return enum.each { |relation| yield relation } if block_given?
94
+ return enum.each(&block) if block_given?
81
95
  enum
82
96
  end
83
97
  end
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
38
38
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
39
39
  # an order is present in the relation.
40
- # * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
40
+ # * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+). Defaults to +:asc+.
41
41
  #
42
42
  # Limits are honored, and if present there is no requirement for the batch
43
43
  # size: it can be less than, equal to, or greater than the limit.
@@ -65,10 +65,10 @@ module ActiveRecord
65
65
  #
66
66
  # NOTE: By its nature, batch processing is subject to race conditions if
67
67
  # other processes are modifying the database.
68
- def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
68
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc, &block)
69
69
  if block_given?
70
70
  find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records|
71
- records.each { |record| yield record }
71
+ records.each(&block)
72
72
  end
73
73
  else
74
74
  enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
@@ -102,7 +102,7 @@ module ActiveRecord
102
102
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
103
103
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
104
104
  # an order is present in the relation.
105
- # * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
105
+ # * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+). Defaults to +:asc+.
106
106
  #
107
107
  # Limits are honored, and if present there is no requirement for the batch
108
108
  # size: it can be less than, equal to, or greater than the limit.
@@ -167,7 +167,7 @@ module ActiveRecord
167
167
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
168
168
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
169
169
  # an order is present in the relation.
170
- # * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
170
+ # * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+). Defaults to +:asc+.
171
171
  #
172
172
  # Limits are honored, and if present there is no requirement for the batch
173
173
  # size, it can be less than, equal, or greater than the limit.
@@ -284,7 +284,7 @@ module ActiveRecord
284
284
  end
285
285
 
286
286
  def act_on_ignored_order(error_on_ignore)
287
- raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
287
+ raise_error = (error_on_ignore.nil? ? ActiveRecord.error_on_ignored_order : error_on_ignore)
288
288
 
289
289
  if raise_error
290
290
  raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
@@ -4,6 +4,47 @@ require "active_support/core_ext/enumerable"
4
4
 
5
5
  module ActiveRecord
6
6
  module Calculations
7
+ class ColumnAliasTracker # :nodoc:
8
+ def initialize(connection)
9
+ @connection = connection
10
+ @aliases = Hash.new(0)
11
+ end
12
+
13
+ def alias_for(field)
14
+ aliased_name = column_alias_for(field)
15
+
16
+ if @aliases[aliased_name] == 0
17
+ @aliases[aliased_name] = 1
18
+ aliased_name
19
+ else
20
+ # Update the count
21
+ count = @aliases[aliased_name] += 1
22
+ "#{truncate(aliased_name)}_#{count}"
23
+ end
24
+ end
25
+
26
+ private
27
+ # Converts the given field to the value that the database adapter returns as
28
+ # a usable column name:
29
+ #
30
+ # column_alias_for("users.id") # => "users_id"
31
+ # column_alias_for("sum(id)") # => "sum_id"
32
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
33
+ # column_alias_for("count(*)") # => "count_all"
34
+ def column_alias_for(field)
35
+ column_alias = +field
36
+ column_alias.gsub!(/\*/, "all")
37
+ column_alias.gsub!(/\W+/, " ")
38
+ column_alias.strip!
39
+ column_alias.gsub!(/ +/, "_")
40
+ @connection.table_alias_for(column_alias)
41
+ end
42
+
43
+ def truncate(name)
44
+ name.slice(0, @connection.table_alias_length - 2)
45
+ end
46
+ end
47
+
7
48
  # Count the records.
8
49
  #
9
50
  # Person.count
@@ -31,7 +72,7 @@ module ActiveRecord
31
72
  #
32
73
  # Article.group(:status, :category).count
33
74
  # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
34
- # ["published", "business"]=>0, ["published", "technology"]=>2}
75
+ # # ["published", "business"]=>0, ["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
  #
@@ -83,15 +124,24 @@ module ActiveRecord
83
124
  # #calculate for examples with options.
84
125
  #
85
126
  # Person.sum(:age) # => 4562
86
- def sum(column_name = nil)
127
+ def sum(identity_or_column = nil, &block)
87
128
  if block_given?
88
- unless column_name.nil?
89
- raise ArgumentError, "Column name argument is not supported when a block is passed."
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
90
132
  end
91
133
 
92
- super()
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
93
143
  else
94
- calculate(:sum, column_name)
144
+ calculate(:sum, identity_or_column)
95
145
  end
96
146
  end
97
147
 
@@ -146,7 +196,7 @@ module ActiveRecord
146
196
  end
147
197
 
148
198
  # 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.
199
+ # loading an entire record object per row.
150
200
  #
151
201
  # Person.pluck(:name)
152
202
  #
@@ -195,9 +245,9 @@ module ActiveRecord
195
245
  relation.select_values = columns
196
246
  result = skip_query_cache_if_necessary do
197
247
  if where_clause.contradiction?
198
- ActiveRecord::Result.new([], [])
248
+ ActiveRecord::Result.empty
199
249
  else
200
- klass.connection.select_all(relation.arel, nil)
250
+ klass.connection.select_all(relation.arel, "#{klass.name} Pluck")
201
251
  end
202
252
  end
203
253
  type_cast_pluck_values(result, columns)
@@ -286,7 +336,7 @@ module ActiveRecord
286
336
  operation == "count" ? column.count(distinct) : column.public_send(operation)
287
337
  end
288
338
 
289
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
339
+ def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
290
340
  if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
291
341
  # Shortcut when limit is zero.
292
342
  return 0 if limit_value == 0
@@ -305,29 +355,21 @@ module ActiveRecord
305
355
  query_builder = relation.arel
306
356
  end
307
357
 
308
- result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
358
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}") }
309
359
 
310
- type_cast_calculated_value(result.cast_values.first, operation) do |value|
360
+ if operation != "count"
311
361
  type = column.try(:type_caster) ||
312
362
  lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
313
363
  type = type.subtype if Enum::EnumType === type
314
- type.deserialize(value)
315
364
  end
365
+
366
+ type_cast_calculated_value(result.cast_values.first, operation, type)
316
367
  end
317
368
 
318
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
369
+ def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
319
370
  group_fields = group_values
320
371
  group_fields = group_fields.uniq if group_fields.size > 1
321
372
 
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
373
  if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
332
374
  association = klass._reflect_on_association(group_fields.first)
333
375
  associated = association && association.belongs_to? # only count belongs_to associations
@@ -335,21 +377,24 @@ module ActiveRecord
335
377
  end
336
378
  group_fields = arel_columns(group_fields)
337
379
 
380
+ column_alias_tracker = ColumnAliasTracker.new(connection)
381
+
338
382
  group_aliases = group_fields.map { |field|
339
383
  field = connection.visitor.compile(field) if Arel.arel_node?(field)
340
- column_alias_for(field.to_s.downcase)
384
+ column_alias_tracker.alias_for(field.to_s.downcase)
341
385
  }
342
386
  group_columns = group_aliases.zip(group_fields)
343
387
 
344
388
  column = aggregate_column(column_name)
345
- column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
389
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
346
390
  select_value = operation_over_aggregate_column(column, operation, distinct)
347
- select_value.as(column_alias)
391
+ select_value.as(connection.quote_column_name(column_alias))
348
392
 
349
393
  select_values = [select_value]
350
394
  select_values += self.select_values unless having_clause.empty?
351
395
 
352
396
  select_values.concat group_columns.map { |aliaz, field|
397
+ aliaz = connection.quote_column_name(aliaz)
353
398
  if field.respond_to?(:as)
354
399
  field.as(aliaz)
355
400
  else
@@ -361,7 +406,7 @@ module ActiveRecord
361
406
  relation.group_values = group_fields
362
407
  relation.select_values = select_values
363
408
 
364
- calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
409
+ calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}") }
365
410
 
366
411
  if association
367
412
  key_ids = calculated_data.collect { |row| row[group_aliases.first] }
@@ -370,9 +415,10 @@ module ActiveRecord
370
415
  end
371
416
 
372
417
  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)
375
- end
418
+ types[aliaz] = col_name.try(:type_caster) ||
419
+ type_for(col_name) do
420
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
421
+ end
376
422
  end
377
423
 
378
424
  hash_rows = calculated_data.cast_values(key_types).map! do |row|
@@ -381,40 +427,21 @@ module ActiveRecord
381
427
  end
382
428
  end
383
429
 
384
- type = nil
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
435
+
385
436
  hash_rows.each_with_object({}) do |row, result|
386
437
  key = group_aliases.map { |aliaz| row[aliaz] }
387
438
  key = key.first if key.size == 1
388
439
  key = key_records[key] if associated
389
440
 
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)
397
- end
441
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
398
442
  end
399
443
  end
400
444
 
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
445
  def type_for(field, &block)
419
446
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
420
447
  @klass.type_for_attribute(field_name, &block)
@@ -445,16 +472,21 @@ module ActiveRecord
445
472
  result.cast_values(cast_types)
446
473
  end
447
474
 
448
- def type_cast_calculated_value(value, operation)
475
+ def type_cast_calculated_value(value, operation, type)
449
476
  case operation
450
477
  when "count"
451
478
  value.to_i
452
479
  when "sum"
453
- yield value || 0
480
+ type.deserialize(value || 0)
454
481
  when "average"
455
- value&.respond_to?(:to_d) ? value.to_d : value
482
+ case type.type
483
+ when :integer, :decimal
484
+ value&.to_d
485
+ else
486
+ type.deserialize(value)
487
+ end
456
488
  else # "minimum", "maximum"
457
- yield value
489
+ type.deserialize(value)
458
490
  end
459
491
  end
460
492
 
@@ -15,7 +15,8 @@ module ActiveRecord
15
15
  [
16
16
  ActiveRecord::Relation,
17
17
  ActiveRecord::Associations::CollectionProxy,
18
- ActiveRecord::AssociationRelation
18
+ ActiveRecord::AssociationRelation,
19
+ ActiveRecord::DisableJoinsAssociationRelation
19
20
  ].each do |klass|
20
21
  delegate = Class.new(klass) {
21
22
  include ClassSpecificRelation
@@ -61,17 +62,16 @@ module ActiveRecord
61
62
  return if method_defined?(method)
62
63
 
63
64
  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
65
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
66
- def #{method}(#{definition})
67
- scoping { klass.#{method}(#{definition}) }
66
+ def #{method}(...)
67
+ scoping { klass.#{method}(...) }
68
68
  end
69
69
  RUBY
70
70
  else
71
71
  define_method(method) do |*args, &block|
72
72
  scoping { klass.public_send(method, *args, &block) }
73
73
  end
74
- ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
74
+ ruby2_keywords(method)
75
75
  end
76
76
  end
77
77
  end
@@ -87,7 +87,7 @@ module ActiveRecord
87
87
 
88
88
  delegate :to_xml, :encode_with, :length, :each, :join,
89
89
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
90
- :to_sentence, :to_formatted_s, :as_json,
90
+ :to_sentence, :to_fs, :to_formatted_s, :as_json,
91
91
  :shuffle, :split, :slice, :index, :rindex, to: :records
92
92
 
93
93
  delegate :primary_key, :connection, to: :klass
@@ -110,7 +110,7 @@ module ActiveRecord
110
110
  super
111
111
  end
112
112
  end
113
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
113
+ ruby2_keywords(:method_missing)
114
114
  end
115
115
 
116
116
  module ClassMethods # :nodoc:
@@ -104,6 +104,32 @@ module ActiveRecord
104
104
  take || raise_record_not_found_exception!
105
105
  end
106
106
 
107
+ # Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no
108
+ # record is found. Raises ActiveRecord::SoleRecordExceeded if more than one
109
+ # record is found.
110
+ #
111
+ # Product.where(["price = %?", price]).sole
112
+ def sole
113
+ found, undesired = first(2)
114
+
115
+ if found.nil?
116
+ raise_record_not_found_exception!
117
+ elsif undesired.present?
118
+ raise ActiveRecord::SoleRecordExceeded.new(self)
119
+ else
120
+ found
121
+ end
122
+ end
123
+
124
+ # Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no
125
+ # record is found. Raises ActiveRecord::SoleRecordExceeded if more than one
126
+ # record is found.
127
+ #
128
+ # Product.find_sole_by(["price = %?", price])
129
+ def find_sole_by(arg, *args)
130
+ where(arg, *args).sole
131
+ end
132
+
107
133
  # Find the first record (or first N records if a parameter is supplied).
108
134
  # If no order is defined it will order by primary key.
109
135
  #
@@ -114,8 +140,6 @@ module ActiveRecord
114
140
  # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
115
141
  #
116
142
  def first(limit = nil)
117
- check_reorder_deprecation unless loaded?
118
-
119
143
  if limit
120
144
  find_nth_with_limit(0, limit)
121
145
  else
@@ -364,17 +388,6 @@ module ActiveRecord
364
388
  end
365
389
 
366
390
  private
367
- def check_reorder_deprecation
368
- if !order_values.empty? && order_values.all?(&:blank?)
369
- blank_value = order_values.first
370
- ActiveSupport::Deprecation.warn(<<~MSG.squish)
371
- `.reorder(#{blank_value.inspect})` with `.first` / `.first!` no longer
372
- takes non-deterministic result in Rails 7.0.
373
- To continue taking non-deterministic result, use `.take` / `.take!` instead.
374
- MSG
375
- end
376
- end
377
-
378
391
  def construct_relation_for_exists(conditions)
379
392
  conditions = sanitize_forbidden_attributes(conditions)
380
393
 
@@ -400,7 +413,7 @@ module ActiveRecord
400
413
  )
401
414
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
402
415
 
403
- if eager_loading && !(
416
+ if eager_loading && has_limit_or_offset? && !(
404
417
  using_limitable_reflections?(join_dependency.reflections) &&
405
418
  using_limitable_reflections?(
406
419
  construct_join_dependency(
@@ -409,12 +422,10 @@ module ActiveRecord
409
422
  ), nil
410
423
  ).reflections
411
424
  )
412
- )
413
- if has_limit_or_offset?
414
- limited_ids = limited_ids_for(relation)
415
- limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
425
+ )
426
+ relation = skip_query_cache_if_necessary do
427
+ klass.connection.distinct_relation_for_primary_key(relation)
416
428
  end
417
- relation.limit_value = relation.offset_value = nil
418
429
  end
419
430
 
420
431
  if block_given?
@@ -424,18 +435,6 @@ module ActiveRecord
424
435
  end
425
436
  end
426
437
 
427
- def limited_ids_for(relation)
428
- values = @klass.connection.columns_for_distinct(
429
- connection.visitor.compile(table[primary_key]),
430
- relation.order_values
431
- )
432
-
433
- relation = relation.except(:select).select(values).distinct!
434
-
435
- id_rows = skip_query_cache_if_necessary { @klass.connection.select_rows(relation.arel, "SQL") }
436
- id_rows.map(&:last)
437
- end
438
-
439
438
  def using_limitable_reflections?(reflections)
440
439
  reflections.none?(&:collection?)
441
440
  end
@@ -508,10 +507,7 @@ module ActiveRecord
508
507
  result = except(:limit, :offset).where(primary_key => ids).records
509
508
 
510
509
  if result.size == ids.size
511
- pk_type = @klass.type_for_attribute(primary_key)
512
-
513
- records_by_id = result.index_by(&:id)
514
- ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
510
+ result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
515
511
  else
516
512
  raise_record_not_found_exception!(ids, result.size, ids.size)
517
513
  end
@@ -51,30 +51,25 @@ module ActiveRecord
51
51
  @rewhere = rewhere
52
52
  end
53
53
 
54
- NORMAL_VALUES = Relation::VALUE_METHODS -
55
- Relation::CLAUSE_METHODS -
56
- [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
57
-
58
- def normal_values
59
- NORMAL_VALUES
60
- end
54
+ NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS -
55
+ [
56
+ :select, :includes, :preload, :joins, :left_outer_joins,
57
+ :order, :reverse_order, :lock, :create_with, :reordering
58
+ ]
61
59
 
62
60
  def merge
63
- normal_values.each do |name|
61
+ NORMAL_VALUES.each do |name|
64
62
  value = values[name]
65
63
  # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
66
64
  # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
67
65
  # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
68
66
  # don't fall through the cracks.
69
67
  unless value.nil? || (value.blank? && false != value)
70
- if name == :select
71
- relation._select!(*value)
72
- else
73
- relation.public_send("#{name}!", *value)
74
- end
68
+ relation.public_send(:"#{name}!", *value)
75
69
  end
76
70
  end
77
71
 
72
+ merge_select_values
78
73
  merge_multi_values
79
74
  merge_single_values
80
75
  merge_clauses
@@ -86,6 +81,18 @@ module ActiveRecord
86
81
  end
87
82
 
88
83
  private
84
+ def merge_select_values
85
+ return if other.select_values.empty?
86
+
87
+ if other.klass == relation.klass
88
+ relation.select_values |= other.select_values
89
+ else
90
+ relation.select_values |= other.instance_eval do
91
+ arel_columns(select_values)
92
+ end
93
+ end
94
+ end
95
+
89
96
  def merge_preloads
90
97
  return if other.preload_values.empty? && other.includes_values.empty?
91
98
 
@@ -18,7 +18,10 @@ module ActiveRecord
18
18
  def ids
19
19
  case value
20
20
  when Relation
21
- value.select_values.empty? ? value.select(primary_key) : value
21
+ relation = value
22
+ relation = relation.select(primary_key) if select_clause?
23
+ relation = relation.where(primary_type => polymorphic_name) if polymorphic_clause?
24
+ relation
22
25
  when Array
23
26
  value.map { |v| convert_to_id(v) }
24
27
  else
@@ -30,6 +33,22 @@ module ActiveRecord
30
33
  associated_table.join_primary_key
31
34
  end
32
35
 
36
+ def primary_type
37
+ associated_table.join_primary_type
38
+ end
39
+
40
+ def polymorphic_name
41
+ associated_table.polymorphic_name_association
42
+ end
43
+
44
+ def select_clause?
45
+ value.select_values.empty?
46
+ end
47
+
48
+ def polymorphic_clause?
49
+ primary_type && !value.where_values_hash.has_key?(primary_type)
50
+ end
51
+
33
52
  def convert_to_id(value)
34
53
  if value.respond_to?(primary_key)
35
54
  value.public_send(primary_key)
@@ -9,10 +9,6 @@ module ActiveRecord
9
9
  require "active_record/relation/predicate_builder/association_query_value"
10
10
  require "active_record/relation/predicate_builder/polymorphic_array_value"
11
11
 
12
- # No-op BaseHandler to work Mashal.load(File.read("legacy_relation.dump")).
13
- # TODO: Remove the constant alias once Rails 6.1 has released.
14
- BaseHandler = BasicObjectHandler
15
-
16
12
  def initialize(table)
17
13
  @table = table
18
14
  @handlers = []
@@ -69,8 +65,7 @@ module ActiveRecord
69
65
  end
70
66
 
71
67
  def build_bind_attribute(column_name, value)
72
- attr = Relation::QueryAttribute.new(column_name, value, table.type(column_name))
73
- Arel::Nodes::BindParam.new(attr)
68
+ Relation::QueryAttribute.new(column_name, value, table.type(column_name))
74
69
  end
75
70
 
76
71
  def resolve_arel_attribute(table_name, column_name, &block)