activerecord 7.0.8 → 7.1.3.4

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 (231) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1554 -1452
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +15 -9
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +313 -217
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  76. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  78. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
  80. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  81. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  82. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  83. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  84. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
  88. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  89. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  90. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  91. data/lib/active_record/connection_adapters.rb +3 -1
  92. data/lib/active_record/connection_handling.rb +72 -95
  93. data/lib/active_record/core.rb +175 -153
  94. data/lib/active_record/counter_cache.rb +46 -25
  95. data/lib/active_record/database_configurations/database_config.rb +9 -3
  96. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  97. data/lib/active_record/database_configurations/url_config.rb +17 -11
  98. data/lib/active_record/database_configurations.rb +86 -33
  99. data/lib/active_record/delegated_type.rb +9 -4
  100. data/lib/active_record/deprecator.rb +7 -0
  101. data/lib/active_record/destroy_association_async_job.rb +2 -0
  102. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  103. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  104. data/lib/active_record/encryption/config.rb +25 -1
  105. data/lib/active_record/encryption/configurable.rb +12 -19
  106. data/lib/active_record/encryption/context.rb +10 -3
  107. data/lib/active_record/encryption/contexts.rb +5 -1
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  109. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  113. data/lib/active_record/encryption/key_generator.rb +12 -1
  114. data/lib/active_record/encryption/message_serializer.rb +2 -0
  115. data/lib/active_record/encryption/properties.rb +3 -3
  116. data/lib/active_record/encryption/scheme.rb +19 -22
  117. data/lib/active_record/encryption.rb +1 -0
  118. data/lib/active_record/enum.rb +112 -28
  119. data/lib/active_record/errors.rb +112 -18
  120. data/lib/active_record/explain.rb +23 -3
  121. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  122. data/lib/active_record/fixture_set/render_context.rb +2 -0
  123. data/lib/active_record/fixture_set/table_row.rb +29 -8
  124. data/lib/active_record/fixtures.rb +135 -71
  125. data/lib/active_record/future_result.rb +31 -5
  126. data/lib/active_record/gem_version.rb +4 -4
  127. data/lib/active_record/inheritance.rb +30 -16
  128. data/lib/active_record/insert_all.rb +57 -10
  129. data/lib/active_record/integration.rb +8 -8
  130. data/lib/active_record/internal_metadata.rb +120 -30
  131. data/lib/active_record/locking/pessimistic.rb +5 -2
  132. data/lib/active_record/log_subscriber.rb +29 -12
  133. data/lib/active_record/marshalling.rb +56 -0
  134. data/lib/active_record/message_pack.rb +124 -0
  135. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  136. data/lib/active_record/middleware/database_selector.rb +6 -8
  137. data/lib/active_record/middleware/shard_selector.rb +3 -1
  138. data/lib/active_record/migration/command_recorder.rb +104 -5
  139. data/lib/active_record/migration/compatibility.rb +139 -5
  140. data/lib/active_record/migration/default_strategy.rb +23 -0
  141. data/lib/active_record/migration/execution_strategy.rb +19 -0
  142. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  143. data/lib/active_record/migration.rb +219 -111
  144. data/lib/active_record/model_schema.rb +64 -44
  145. data/lib/active_record/nested_attributes.rb +24 -6
  146. data/lib/active_record/normalization.rb +167 -0
  147. data/lib/active_record/persistence.rb +188 -37
  148. data/lib/active_record/promise.rb +84 -0
  149. data/lib/active_record/query_cache.rb +3 -21
  150. data/lib/active_record/query_logs.rb +77 -52
  151. data/lib/active_record/query_logs_formatter.rb +41 -0
  152. data/lib/active_record/querying.rb +15 -2
  153. data/lib/active_record/railtie.rb +109 -47
  154. data/lib/active_record/railties/controller_runtime.rb +12 -6
  155. data/lib/active_record/railties/databases.rake +142 -148
  156. data/lib/active_record/railties/job_runtime.rb +23 -0
  157. data/lib/active_record/readonly_attributes.rb +32 -5
  158. data/lib/active_record/reflection.rb +174 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  160. data/lib/active_record/relation/batches.rb +190 -61
  161. data/lib/active_record/relation/calculations.rb +187 -63
  162. data/lib/active_record/relation/delegation.rb +23 -9
  163. data/lib/active_record/relation/finder_methods.rb +77 -16
  164. data/lib/active_record/relation/merger.rb +2 -0
  165. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  167. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  168. data/lib/active_record/relation/predicate_builder.rb +26 -14
  169. data/lib/active_record/relation/query_attribute.rb +2 -1
  170. data/lib/active_record/relation/query_methods.rb +352 -63
  171. data/lib/active_record/relation/spawn_methods.rb +18 -1
  172. data/lib/active_record/relation.rb +91 -35
  173. data/lib/active_record/result.rb +19 -5
  174. data/lib/active_record/runtime_registry.rb +24 -1
  175. data/lib/active_record/sanitization.rb +51 -11
  176. data/lib/active_record/schema.rb +2 -3
  177. data/lib/active_record/schema_dumper.rb +46 -7
  178. data/lib/active_record/schema_migration.rb +68 -33
  179. data/lib/active_record/scoping/default.rb +15 -5
  180. data/lib/active_record/scoping/named.rb +2 -2
  181. data/lib/active_record/scoping.rb +2 -1
  182. data/lib/active_record/secure_password.rb +60 -0
  183. data/lib/active_record/secure_token.rb +21 -3
  184. data/lib/active_record/signed_id.rb +7 -5
  185. data/lib/active_record/store.rb +8 -8
  186. data/lib/active_record/suppressor.rb +3 -1
  187. data/lib/active_record/table_metadata.rb +10 -1
  188. data/lib/active_record/tasks/database_tasks.rb +127 -105
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  192. data/lib/active_record/test_fixtures.rb +113 -96
  193. data/lib/active_record/timestamp.rb +27 -15
  194. data/lib/active_record/token_for.rb +113 -0
  195. data/lib/active_record/touch_later.rb +11 -6
  196. data/lib/active_record/transactions.rb +36 -10
  197. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  198. data/lib/active_record/type/internal/timezone.rb +7 -2
  199. data/lib/active_record/type/time.rb +4 -0
  200. data/lib/active_record/validations/absence.rb +1 -1
  201. data/lib/active_record/validations/numericality.rb +5 -4
  202. data/lib/active_record/validations/presence.rb +5 -28
  203. data/lib/active_record/validations/uniqueness.rb +47 -2
  204. data/lib/active_record/validations.rb +8 -4
  205. data/lib/active_record/version.rb +1 -1
  206. data/lib/active_record.rb +121 -16
  207. data/lib/arel/errors.rb +10 -0
  208. data/lib/arel/factory_methods.rb +4 -0
  209. data/lib/arel/nodes/binary.rb +6 -1
  210. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  211. data/lib/arel/nodes/cte.rb +36 -0
  212. data/lib/arel/nodes/fragments.rb +35 -0
  213. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  214. data/lib/arel/nodes/leading_join.rb +8 -0
  215. data/lib/arel/nodes/node.rb +111 -2
  216. data/lib/arel/nodes/sql_literal.rb +6 -0
  217. data/lib/arel/nodes/table_alias.rb +4 -0
  218. data/lib/arel/nodes.rb +4 -0
  219. data/lib/arel/predications.rb +2 -0
  220. data/lib/arel/table.rb +9 -5
  221. data/lib/arel/visitors/mysql.rb +8 -1
  222. data/lib/arel/visitors/to_sql.rb +81 -17
  223. data/lib/arel/visitors/visitor.rb +2 -2
  224. data/lib/arel.rb +16 -2
  225. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  226. data/lib/rails/generators/active_record/migration.rb +3 -1
  227. data/lib/rails/generators/active_record/model/USAGE +113 -0
  228. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  229. metadata +48 -12
  230. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  231. 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,10 +215,23 @@ 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
237
  relation.select_values = [ klass.primary_key || table[Arel.star] ]
@@ -229,31 +280,51 @@ module ActiveRecord
229
280
  # # => ['0', '27761', '173']
230
281
  #
231
282
  # See also #ids.
232
- #
233
283
  def pluck(*column_names)
284
+ if @none
285
+ if @async
286
+ return Promise::Complete.new([])
287
+ else
288
+ return []
289
+ end
290
+ end
291
+
234
292
  if loaded? && all_attributes?(column_names)
235
- return records.pluck(*column_names)
293
+ result = records.pluck(*column_names)
294
+ if @async
295
+ return Promise::Complete.new(result)
296
+ else
297
+ return result
298
+ end
236
299
  end
237
300
 
238
301
  if has_include?(column_names.first)
239
302
  relation = apply_join_dependency
240
303
  relation.pluck(*column_names)
241
304
  else
242
- klass.disallow_raw_sql!(column_names)
305
+ klass.disallow_raw_sql!(column_names.flatten)
243
306
  columns = arel_columns(column_names)
244
307
  relation = spawn
245
308
  relation.select_values = columns
246
309
  result = skip_query_cache_if_necessary do
247
310
  if where_clause.contradiction?
248
- ActiveRecord::Result.empty
311
+ ActiveRecord::Result.empty(async: @async)
249
312
  else
250
- klass.connection.select_all(relation.arel, "#{klass.name} Pluck")
313
+ klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
251
314
  end
252
315
  end
253
- type_cast_pluck_values(result, columns)
316
+ result.then do |result|
317
+ type_cast_pluck_values(result, columns)
318
+ end
254
319
  end
255
320
  end
256
321
 
322
+ # Same as #pluck, but performs the query asynchronously and returns an
323
+ # ActiveRecord::Promise.
324
+ def async_pluck(*column_names)
325
+ async.pluck(*column_names)
326
+ end
327
+
257
328
  # Pick the value(s) from the named column(s) in the current relation.
258
329
  # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
259
330
  # when you have a relation that's already narrowed down to a single row.
@@ -270,18 +341,61 @@ module ActiveRecord
270
341
  # # => [ 'David', 'david@loudthinking.com' ]
271
342
  def pick(*column_names)
272
343
  if loaded? && all_attributes?(column_names)
273
- return records.pick(*column_names)
344
+ result = records.pick(*column_names)
345
+ return @async ? Promise::Complete.new(result) : result
274
346
  end
275
347
 
276
- limit(1).pluck(*column_names).first
348
+ limit(1).pluck(*column_names).then(&:first)
349
+ end
350
+
351
+ # Same as #pick, but performs the query asynchronously and returns an
352
+ # ActiveRecord::Promise.
353
+ def async_pick(*column_names)
354
+ async.pick(*column_names)
277
355
  end
278
356
 
279
- # Pluck all the ID's for the relation using the table's primary key
357
+ # Returns the base model's ID's for the relation using the table's primary key
280
358
  #
281
359
  # 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
360
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
283
361
  def ids
284
- pluck primary_key
362
+ primary_key_array = Array(primary_key)
363
+
364
+ if loaded?
365
+ result = records.map do |record|
366
+ if primary_key_array.one?
367
+ record._read_attribute(primary_key_array.first)
368
+ else
369
+ primary_key_array.map { |column| record._read_attribute(column) }
370
+ end
371
+ end
372
+ return @async ? Promise::Complete.new(result) : result
373
+ end
374
+
375
+ if has_include?(primary_key)
376
+ relation = apply_join_dependency.group(*primary_key_array)
377
+ return relation.ids
378
+ end
379
+
380
+ columns = arel_columns(primary_key_array)
381
+ relation = spawn
382
+ relation.select_values = columns
383
+
384
+ result = if relation.where_clause.contradiction?
385
+ ActiveRecord::Result.empty
386
+ else
387
+ skip_query_cache_if_necessary do
388
+ klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
389
+ end
390
+ end
391
+
392
+ result.then { |result| type_cast_pluck_values(result, columns) }
393
+ end
394
+
395
+ # Same as #ids, but performs the query asynchronously and returns an
396
+ # ActiveRecord::Promise.
397
+ def async_ids
398
+ async.ids
285
399
  end
286
400
 
287
401
  private
@@ -341,6 +455,7 @@ module ActiveRecord
341
455
  # Shortcut when limit is zero.
342
456
  return 0 if limit_value == 0
343
457
 
458
+ relation = self
344
459
  query_builder = build_count_subquery(spawn, column_name, distinct)
345
460
  else
346
461
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
@@ -355,15 +470,23 @@ module ActiveRecord
355
470
  query_builder = relation.arel
356
471
  end
357
472
 
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
473
+ query_result = if relation.where_clause.contradiction?
474
+ ActiveRecord::Result.empty
475
+ else
476
+ skip_query_cache_if_necessary do
477
+ @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
478
+ end
364
479
  end
365
480
 
366
- type_cast_calculated_value(result.cast_values.first, operation, type)
481
+ query_result.then do |result|
482
+ if operation != "count"
483
+ type = column.try(:type_caster) ||
484
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
485
+ type = type.subtype if Enum::EnumType === type
486
+ end
487
+
488
+ type_cast_calculated_value(result.cast_values.first, operation, type)
489
+ end
367
490
  end
368
491
 
369
492
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
@@ -406,39 +529,40 @@ module ActiveRecord
406
529
  relation.group_values = group_fields
407
530
  relation.select_values = select_values
408
531
 
409
- calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}") }
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
410
539
 
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
540
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
541
+ types[aliaz] = col_name.try(:type_caster) ||
542
+ type_for(col_name) do
543
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
544
+ end
545
+ end
416
546
 
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)
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]
421
550
  end
422
- end
423
-
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
551
  end
428
- end
429
552
 
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
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
435
558
 
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
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
440
563
 
441
- result[key] = type_cast_calculated_value(row[column_alias], operation, type)
564
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
565
+ end
442
566
  end
443
567
  end
444
568
 
@@ -5,6 +5,23 @@ require "active_support/core_ext/module/delegation"
5
5
 
6
6
  module ActiveRecord
7
7
  module Delegation # :nodoc:
8
+ class << self
9
+ def delegated_classes
10
+ [
11
+ ActiveRecord::Relation,
12
+ ActiveRecord::Associations::CollectionProxy,
13
+ ActiveRecord::AssociationRelation,
14
+ ActiveRecord::DisableJoinsAssociationRelation,
15
+ ]
16
+ end
17
+
18
+ def uncacheable_methods
19
+ @uncacheable_methods ||= (
20
+ delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods
21
+ ).to_set.freeze
22
+ end
23
+ end
24
+
8
25
  module DelegateCache # :nodoc:
9
26
  def relation_delegate_class(klass)
10
27
  @relation_delegate_cache[klass]
@@ -12,12 +29,7 @@ module ActiveRecord
12
29
 
13
30
  def initialize_relation_delegate_cache
14
31
  @relation_delegate_cache = cache = {}
15
- [
16
- ActiveRecord::Relation,
17
- ActiveRecord::Associations::CollectionProxy,
18
- ActiveRecord::AssociationRelation,
19
- ActiveRecord::DisableJoinsAssociationRelation
20
- ].each do |klass|
32
+ Delegation.delegated_classes.each do |klass|
21
33
  delegate = Class.new(klass) {
22
34
  include ClassSpecificRelation
23
35
  }
@@ -85,12 +97,12 @@ module ActiveRecord
85
97
  # may vary depending on the klass of a relation, so we create a subclass of Relation
86
98
  # for each different klass, and the delegations are compiled into that subclass only.
87
99
 
88
- delegate :to_xml, :encode_with, :length, :each, :join,
100
+ delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
89
101
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
90
102
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
91
103
  :shuffle, :split, :slice, :index, :rindex, to: :records
92
104
 
93
- delegate :primary_key, :connection, to: :klass
105
+ delegate :primary_key, :connection, :transaction, to: :klass
94
106
 
95
107
  module ClassSpecificRelation # :nodoc:
96
108
  extend ActiveSupport::Concern
@@ -104,7 +116,9 @@ module ActiveRecord
104
116
  private
105
117
  def method_missing(method, *args, &block)
106
118
  if @klass.respond_to?(method)
107
- @klass.generate_relation_method(method)
119
+ unless Delegation.uncacheable_methods.include?(method)
120
+ @klass.generate_relation_method(method)
121
+ end
108
122
  scoping { @klass.public_send(method, *args, &block) }
109
123
  else
110
124
  super
@@ -6,7 +6,9 @@ module ActiveRecord
6
6
  module FinderMethods
7
7
  ONE_AS_ONE = "1 AS one"
8
8
 
9
- # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
9
+ # Find by id - This can either be a specific id (ID), a list of ids (ID, ID, ID), or an array of ids ([ID, ID, ID]).
10
+ # `ID` refers to an "identifier". For models with a single-column primary key, `ID` will be a single value,
11
+ # and for models with a composite primary key, it will be an array of values.
10
12
  # If one or more records cannot be found for the requested ids, then ActiveRecord::RecordNotFound will be raised.
11
13
  # If the primary key is an integer, find by id coerces its arguments by using +to_i+.
12
14
  #
@@ -14,10 +16,31 @@ module ActiveRecord
14
16
  # Person.find("1") # returns the object for ID = 1
15
17
  # Person.find("31-sarah") # returns the object for ID = 31
16
18
  # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
17
- # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
19
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17), or with composite primary key [7, 17]
18
20
  # Person.find([1]) # returns an array for the object with ID = 1
19
21
  # Person.where("administrator = 1").order("created_on DESC").find(1)
20
22
  #
23
+ # ==== Find a record for a composite primary key model
24
+ # TravelRoute.primary_key = [:origin, :destination]
25
+ #
26
+ # TravelRoute.find(["Ottawa", "London"])
27
+ # => #<TravelRoute origin: "Ottawa", destination: "London">
28
+ #
29
+ # TravelRoute.find([["Paris", "Montreal"]])
30
+ # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
31
+ #
32
+ # TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
33
+ # => [
34
+ # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
+ # #<TravelRoute origin: "New York", destination: "Portland">
36
+ # ]
37
+ #
38
+ # TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
39
+ # => [
40
+ # #<TravelRoute origin: "Berlin", destination: "London">,
41
+ # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
+ # ]
43
+ #
21
44
  # NOTE: The returned records are in the same order as the ids you provide.
22
45
  # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
23
46
  # method and provide an explicit ActiveRecord::QueryMethods#order option.
@@ -324,6 +347,8 @@ module ActiveRecord
324
347
  # Person.exists?
325
348
  # Person.where(name: 'Spartacus', rating: 4).exists?
326
349
  def exists?(conditions = :none)
350
+ return false if @none
351
+
327
352
  if Base === conditions
328
353
  raise ArgumentError, <<-MSG.squish
329
354
  You are passing an instance of ActiveRecord::Base to `exists?`.
@@ -350,10 +375,20 @@ module ActiveRecord
350
375
  # compared to the records in memory. If the relation is unloaded, an
351
376
  # efficient existence query is performed, as in #exists?.
352
377
  def include?(record)
378
+ # The existing implementation relies on receiving an Active Record instance as the input parameter named record.
379
+ # Any non-Active Record object passed to this implementation is guaranteed to return `false`.
380
+ return false unless record.is_a?(klass)
381
+
353
382
  if loaded? || offset_value || limit_value || having_clause.any?
354
383
  records.include?(record)
355
384
  else
356
- record.is_a?(klass) && exists?(record.id)
385
+ id = if record.class.composite_primary_key?
386
+ record.class.primary_key.zip(record.id).to_h
387
+ else
388
+ record.id
389
+ end
390
+
391
+ exists?(id)
357
392
  end
358
393
  end
359
394
 
@@ -442,10 +477,17 @@ module ActiveRecord
442
477
  def find_with_ids(*ids)
443
478
  raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
444
479
 
445
- expects_array = ids.first.kind_of?(Array)
480
+ expects_array = if klass.composite_primary_key?
481
+ ids.first.first.is_a?(Array)
482
+ else
483
+ ids.first.is_a?(Array)
484
+ end
485
+
446
486
  return [] if expects_array && ids.first.empty?
447
487
 
448
- ids = ids.flatten.compact.uniq
488
+ ids = ids.first if expects_array
489
+
490
+ ids = ids.compact.uniq
449
491
 
450
492
  model_name = @klass.name
451
493
 
@@ -469,7 +511,12 @@ module ActiveRecord
469
511
  MSG
470
512
  end
471
513
 
472
- relation = where(primary_key => id)
514
+ relation = if klass.composite_primary_key?
515
+ where(primary_key.zip(id).to_h)
516
+ else
517
+ where(primary_key => id)
518
+ end
519
+
473
520
  record = relation.take
474
521
 
475
522
  raise_record_not_found_exception!(id, 0, 1) unless record
@@ -480,7 +527,9 @@ module ActiveRecord
480
527
  def find_some(ids)
481
528
  return find_some_ordered(ids) unless order_values.present?
482
529
 
483
- result = where(primary_key => ids).to_a
530
+ relation = where(primary_key => ids)
531
+ relation = relation.select(table[primary_key]) unless select_values.empty?
532
+ result = relation.to_a
484
533
 
485
534
  expected_size =
486
535
  if limit_value && ids.size > limit_value
@@ -504,7 +553,10 @@ module ActiveRecord
504
553
  def find_some_ordered(ids)
505
554
  ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
506
555
 
507
- result = except(:limit, :offset).where(primary_key => ids).records
556
+ relation = except(:limit, :offset)
557
+ relation = relation.where(primary_key => ids)
558
+ relation = relation.select(table[primary_key]) unless select_values.empty?
559
+ result = relation.records
508
560
 
509
561
  if result.size == ids.size
510
562
  result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
@@ -559,10 +611,10 @@ module ActiveRecord
559
611
  else
560
612
  relation = ordered_relation
561
613
 
562
- if equal?(relation) || has_limit_or_offset?
614
+ if relation.order_values.empty? || relation.has_limit_or_offset?
563
615
  relation.records[-index]
564
616
  else
565
- relation.last(index)[-index]
617
+ relation.reverse_order.offset(index - 1).first
566
618
  end
567
619
  end
568
620
  end
@@ -572,15 +624,24 @@ module ActiveRecord
572
624
  end
573
625
 
574
626
  def ordered_relation
575
- if order_values.empty? && (implicit_order_column || primary_key)
576
- if implicit_order_column && primary_key && implicit_order_column != primary_key
577
- order(table[implicit_order_column].asc, table[primary_key].asc)
578
- else
579
- order(table[implicit_order_column || primary_key].asc)
580
- end
627
+ if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
628
+ order(_order_columns.map { |column| table[column].asc })
581
629
  else
582
630
  self
583
631
  end
584
632
  end
633
+
634
+ def _order_columns
635
+ oc = []
636
+
637
+ oc << implicit_order_column if implicit_order_column
638
+ oc << query_constraints_list if query_constraints_list
639
+
640
+ if primary_key && query_constraints_list.nil?
641
+ oc << primary_key
642
+ end
643
+
644
+ oc.flatten.uniq.compact
645
+ end
585
646
  end
586
647
  end
@@ -69,6 +69,8 @@ module ActiveRecord
69
69
  end
70
70
  end
71
71
 
72
+ relation.none! if other.null_relation?
73
+
72
74
  merge_select_values
73
75
  merge_multi_values
74
76
  merge_single_values
@@ -9,7 +9,14 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def queries
12
- [ associated_table.join_foreign_key => ids ]
12
+ if associated_table.join_foreign_key.is_a?(Array)
13
+ id_list = ids
14
+ id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation)
15
+
16
+ id_list.map { |ids_set| associated_table.join_foreign_key.zip(ids_set).to_h }
17
+ else
18
+ [ associated_table.join_foreign_key => ids ]
19
+ end
13
20
  end
14
21
 
15
22
  private
@@ -25,7 +32,7 @@ module ActiveRecord
25
32
  when Array
26
33
  value.map { |v| convert_to_id(v) }
27
34
  else
28
- convert_to_id(value)
35
+ [convert_to_id(value)]
29
36
  end
30
37
  end
31
38
 
@@ -50,6 +57,8 @@ module ActiveRecord
50
57
  end
51
58
 
52
59
  def convert_to_id(value)
60
+ return primary_key.map { |pk| value.public_send(pk) } if primary_key.is_a?(Array)
61
+
53
62
  if value.respond_to?(primary_key)
54
63
  value.public_send(primary_key)
55
64
  else