activerecord 7.0.0 → 7.1.0

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1607 -1040
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -18
  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 +18 -3
  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 +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  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 +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +345 -219
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +3 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +358 -57
  83. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
  85. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  89. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  92. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  93. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  94. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  95. data/lib/active_record/connection_adapters.rb +3 -1
  96. data/lib/active_record/connection_handling.rb +73 -96
  97. data/lib/active_record/core.rb +136 -148
  98. data/lib/active_record/counter_cache.rb +46 -25
  99. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  100. data/lib/active_record/database_configurations/database_config.rb +9 -3
  101. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  102. data/lib/active_record/database_configurations/url_config.rb +17 -11
  103. data/lib/active_record/database_configurations.rb +87 -34
  104. data/lib/active_record/delegated_type.rb +9 -4
  105. data/lib/active_record/deprecator.rb +7 -0
  106. data/lib/active_record/destroy_association_async_job.rb +2 -0
  107. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  108. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  109. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  110. data/lib/active_record/encryption/config.rb +25 -1
  111. data/lib/active_record/encryption/configurable.rb +13 -14
  112. data/lib/active_record/encryption/context.rb +10 -3
  113. data/lib/active_record/encryption/contexts.rb +8 -4
  114. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  115. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  116. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  117. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  118. data/lib/active_record/encryption/encryptor.rb +7 -7
  119. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  120. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
  121. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  122. data/lib/active_record/encryption/key_generator.rb +12 -1
  123. data/lib/active_record/encryption/message.rb +1 -1
  124. data/lib/active_record/encryption/message_serializer.rb +2 -0
  125. data/lib/active_record/encryption/properties.rb +4 -4
  126. data/lib/active_record/encryption/scheme.rb +20 -23
  127. data/lib/active_record/encryption.rb +1 -0
  128. data/lib/active_record/enum.rb +114 -27
  129. data/lib/active_record/errors.rb +108 -15
  130. data/lib/active_record/explain.rb +23 -3
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  133. data/lib/active_record/fixture_set/render_context.rb +2 -0
  134. data/lib/active_record/fixture_set/table_row.rb +29 -8
  135. data/lib/active_record/fixtures.rb +121 -73
  136. data/lib/active_record/future_result.rb +30 -5
  137. data/lib/active_record/gem_version.rb +2 -2
  138. data/lib/active_record/inheritance.rb +30 -16
  139. data/lib/active_record/insert_all.rb +55 -8
  140. data/lib/active_record/integration.rb +10 -10
  141. data/lib/active_record/internal_metadata.rb +118 -30
  142. data/lib/active_record/locking/optimistic.rb +32 -18
  143. data/lib/active_record/locking/pessimistic.rb +8 -5
  144. data/lib/active_record/log_subscriber.rb +39 -17
  145. data/lib/active_record/marshalling.rb +56 -0
  146. data/lib/active_record/message_pack.rb +124 -0
  147. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  148. data/lib/active_record/middleware/database_selector.rb +18 -13
  149. data/lib/active_record/middleware/shard_selector.rb +7 -5
  150. data/lib/active_record/migration/command_recorder.rb +104 -9
  151. data/lib/active_record/migration/compatibility.rb +158 -64
  152. data/lib/active_record/migration/default_strategy.rb +23 -0
  153. data/lib/active_record/migration/execution_strategy.rb +19 -0
  154. data/lib/active_record/migration.rb +271 -117
  155. data/lib/active_record/model_schema.rb +82 -50
  156. data/lib/active_record/nested_attributes.rb +23 -3
  157. data/lib/active_record/normalization.rb +159 -0
  158. data/lib/active_record/persistence.rb +200 -47
  159. data/lib/active_record/promise.rb +84 -0
  160. data/lib/active_record/query_cache.rb +3 -21
  161. data/lib/active_record/query_logs.rb +87 -51
  162. data/lib/active_record/query_logs_formatter.rb +41 -0
  163. data/lib/active_record/querying.rb +16 -3
  164. data/lib/active_record/railtie.rb +127 -61
  165. data/lib/active_record/railties/controller_runtime.rb +12 -8
  166. data/lib/active_record/railties/databases.rake +142 -143
  167. data/lib/active_record/railties/job_runtime.rb +23 -0
  168. data/lib/active_record/readonly_attributes.rb +32 -5
  169. data/lib/active_record/reflection.rb +177 -45
  170. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  171. data/lib/active_record/relation/batches.rb +190 -61
  172. data/lib/active_record/relation/calculations.rb +200 -83
  173. data/lib/active_record/relation/delegation.rb +23 -9
  174. data/lib/active_record/relation/finder_methods.rb +77 -16
  175. data/lib/active_record/relation/merger.rb +2 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  177. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  179. data/lib/active_record/relation/predicate_builder.rb +26 -14
  180. data/lib/active_record/relation/query_attribute.rb +25 -1
  181. data/lib/active_record/relation/query_methods.rb +429 -76
  182. data/lib/active_record/relation/spawn_methods.rb +18 -1
  183. data/lib/active_record/relation.rb +98 -41
  184. data/lib/active_record/result.rb +25 -9
  185. data/lib/active_record/runtime_registry.rb +10 -1
  186. data/lib/active_record/sanitization.rb +57 -16
  187. data/lib/active_record/schema.rb +36 -22
  188. data/lib/active_record/schema_dumper.rb +65 -23
  189. data/lib/active_record/schema_migration.rb +68 -33
  190. data/lib/active_record/scoping/default.rb +20 -12
  191. data/lib/active_record/scoping/named.rb +2 -2
  192. data/lib/active_record/scoping.rb +2 -1
  193. data/lib/active_record/secure_password.rb +60 -0
  194. data/lib/active_record/secure_token.rb +21 -3
  195. data/lib/active_record/serialization.rb +5 -0
  196. data/lib/active_record/signed_id.rb +9 -7
  197. data/lib/active_record/store.rb +16 -11
  198. data/lib/active_record/suppressor.rb +3 -1
  199. data/lib/active_record/table_metadata.rb +16 -3
  200. data/lib/active_record/tasks/database_tasks.rb +138 -107
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  203. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  204. data/lib/active_record/test_fixtures.rb +123 -99
  205. data/lib/active_record/timestamp.rb +26 -14
  206. data/lib/active_record/token_for.rb +113 -0
  207. data/lib/active_record/touch_later.rb +11 -6
  208. data/lib/active_record/transactions.rb +39 -13
  209. data/lib/active_record/translation.rb +1 -1
  210. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  211. data/lib/active_record/type/internal/timezone.rb +7 -2
  212. data/lib/active_record/type/serialized.rb +8 -4
  213. data/lib/active_record/type/time.rb +4 -0
  214. data/lib/active_record/validations/absence.rb +1 -1
  215. data/lib/active_record/validations/associated.rb +3 -3
  216. data/lib/active_record/validations/numericality.rb +5 -4
  217. data/lib/active_record/validations/presence.rb +5 -28
  218. data/lib/active_record/validations/uniqueness.rb +50 -5
  219. data/lib/active_record/validations.rb +8 -4
  220. data/lib/active_record/version.rb +1 -1
  221. data/lib/active_record.rb +143 -16
  222. data/lib/arel/errors.rb +10 -0
  223. data/lib/arel/factory_methods.rb +4 -0
  224. data/lib/arel/filter_predications.rb +1 -1
  225. data/lib/arel/nodes/and.rb +4 -0
  226. data/lib/arel/nodes/binary.rb +6 -1
  227. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  228. data/lib/arel/nodes/cte.rb +36 -0
  229. data/lib/arel/nodes/filter.rb +1 -1
  230. data/lib/arel/nodes/fragments.rb +35 -0
  231. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  232. data/lib/arel/nodes/leading_join.rb +8 -0
  233. data/lib/arel/nodes/node.rb +111 -2
  234. data/lib/arel/nodes/sql_literal.rb +6 -0
  235. data/lib/arel/nodes/table_alias.rb +4 -0
  236. data/lib/arel/nodes.rb +4 -0
  237. data/lib/arel/predications.rb +2 -0
  238. data/lib/arel/table.rb +9 -5
  239. data/lib/arel/visitors/mysql.rb +8 -1
  240. data/lib/arel/visitors/to_sql.rb +81 -17
  241. data/lib/arel/visitors/visitor.rb +2 -2
  242. data/lib/arel.rb +16 -2
  243. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  244. data/lib/rails/generators/active_record/migration.rb +3 -1
  245. data/lib/rails/generators/active_record/model/USAGE +113 -0
  246. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  247. metadata +50 -15
  248. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  249. data/lib/active_record/null_relation.rb +0 -63
@@ -3,7 +3,49 @@
3
3
  require "active_support/core_ext/enumerable"
4
4
 
5
5
  module ActiveRecord
6
+ # = Active Record \Calculations
6
7
  module Calculations
8
+ class ColumnAliasTracker # :nodoc:
9
+ def initialize(connection)
10
+ @connection = connection
11
+ @aliases = Hash.new(0)
12
+ end
13
+
14
+ def alias_for(field)
15
+ aliased_name = column_alias_for(field)
16
+
17
+ if @aliases[aliased_name] == 0
18
+ @aliases[aliased_name] = 1
19
+ aliased_name
20
+ else
21
+ # Update the count
22
+ count = @aliases[aliased_name] += 1
23
+ "#{truncate(aliased_name)}_#{count}"
24
+ end
25
+ end
26
+
27
+ private
28
+ # Converts the given field to the value that the database adapter returns as
29
+ # a usable column name:
30
+ #
31
+ # column_alias_for("users.id") # => "users_id"
32
+ # column_alias_for("sum(id)") # => "sum_id"
33
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
34
+ # column_alias_for("count(*)") # => "count_all"
35
+ def column_alias_for(field)
36
+ column_alias = +field
37
+ column_alias.gsub!(/\*/, "all")
38
+ column_alias.gsub!(/\W+/, " ")
39
+ column_alias.strip!
40
+ column_alias.gsub!(/ +/, "_")
41
+ @connection.table_alias_for(column_alias)
42
+ end
43
+
44
+ def truncate(name)
45
+ name.slice(0, @connection.table_alias_length - 2)
46
+ end
47
+ end
48
+
7
49
  # Count the records.
8
50
  #
9
51
  # Person.count
@@ -30,8 +72,7 @@ module ActiveRecord
30
72
  # of each key would be the #count.
31
73
  #
32
74
  # Article.group(:status, :category).count
33
- # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
34
- # # ["published", "business"]=>0, ["published", "technology"]=>2}
75
+ # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
35
76
  #
36
77
  # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
37
78
  #
@@ -52,6 +93,11 @@ module ActiveRecord
52
93
  end
53
94
  end
54
95
 
96
+ # Same as <tt>#count</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
97
+ def async_count(column_name = nil)
98
+ async.count(column_name)
99
+ end
100
+
55
101
  # Calculates the average value on a given column. Returns +nil+ if there's
56
102
  # no row. See #calculate for examples with options.
57
103
  #
@@ -60,6 +106,11 @@ module ActiveRecord
60
106
  calculate(:average, column_name)
61
107
  end
62
108
 
109
+ # Same as <tt>#average</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
110
+ def async_average(column_name)
111
+ async.average(column_name)
112
+ end
113
+
63
114
  # Calculates the minimum value on a given column. The value is returned
64
115
  # with the same data type of the column, or +nil+ if there's no row. See
65
116
  # #calculate for examples with options.
@@ -69,6 +120,11 @@ module ActiveRecord
69
120
  calculate(:minimum, column_name)
70
121
  end
71
122
 
123
+ # Same as <tt>#minimum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
124
+ def async_minimum(column_name)
125
+ async.minimum(column_name)
126
+ end
127
+
72
128
  # Calculates the maximum value on a given column. The value is returned
73
129
  # with the same data type of the column, or +nil+ if there's no row. See
74
130
  # #calculate for examples with options.
@@ -78,32 +134,29 @@ module ActiveRecord
78
134
  calculate(:maximum, column_name)
79
135
  end
80
136
 
137
+ # Same as <tt>#maximum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
138
+ def async_maximum(column_name)
139
+ async.maximum(column_name)
140
+ end
141
+
81
142
  # Calculates the sum of values on a given column. The value is returned
82
143
  # with the same data type of the column, +0+ if there's no row. See
83
144
  # #calculate for examples with options.
84
145
  #
85
146
  # Person.sum(:age) # => 4562
86
- def sum(identity_or_column = nil, &block)
147
+ def sum(initial_value_or_column = 0, &block)
87
148
  if block_given?
88
- values = map(&block)
89
- if identity_or_column.nil? && (values.first.is_a?(Numeric) || values.first(1) == [])
90
- identity_or_column = 0
91
- end
92
-
93
- if identity_or_column.nil?
94
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
95
- Rails 7.0 has deprecated Enumerable.sum in favor of Ruby's native implementation available since 2.4.
96
- Sum of non-numeric elements requires an initial argument.
97
- MSG
98
- values.inject(:+) || 0
99
- else
100
- values.sum(identity_or_column)
101
- end
149
+ map(&block).sum(initial_value_or_column)
102
150
  else
103
- calculate(:sum, identity_or_column)
151
+ calculate(:sum, initial_value_or_column)
104
152
  end
105
153
  end
106
154
 
155
+ # Same as <tt>#sum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
156
+ def async_sum(identity_or_column = nil)
157
+ async.sum(identity_or_column)
158
+ end
159
+
107
160
  # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
108
161
  # #minimum, and #maximum have been added as shortcuts.
109
162
  #
@@ -136,10 +189,23 @@ module ActiveRecord
136
189
  # ...
137
190
  # end
138
191
  def calculate(operation, column_name)
192
+ operation = operation.to_s.downcase
193
+
194
+ if @none
195
+ case operation
196
+ when "count", "sum"
197
+ result = group_values.any? ? Hash.new : 0
198
+ return @async ? Promise::Complete.new(result) : result
199
+ when "average", "minimum", "maximum"
200
+ result = group_values.any? ? Hash.new : nil
201
+ return @async ? Promise::Complete.new(result) : result
202
+ end
203
+ end
204
+
139
205
  if has_include?(column_name)
140
206
  relation = apply_join_dependency
141
207
 
142
- if operation.to_s.downcase == "count"
208
+ if operation == "count"
143
209
  unless distinct_value || distinct_select?(column_name || select_for_count)
144
210
  relation.distinct!
145
211
  relation.select_values = [ klass.primary_key || table[Arel.star] ]
@@ -155,7 +221,7 @@ module ActiveRecord
155
221
  end
156
222
 
157
223
  # Use #pluck as a shortcut to select one or more attributes without
158
- # loading a bunch of records just to grab the attributes you want.
224
+ # loading an entire record object per row.
159
225
  #
160
226
  # Person.pluck(:name)
161
227
  #
@@ -188,31 +254,44 @@ module ActiveRecord
188
254
  # # => ['0', '27761', '173']
189
255
  #
190
256
  # See also #ids.
191
- #
192
257
  def pluck(*column_names)
258
+ return [] if @none
259
+
193
260
  if loaded? && all_attributes?(column_names)
194
- return records.pluck(*column_names)
261
+ result = records.pluck(*column_names)
262
+ if @async
263
+ return Promise::Complete.new(result)
264
+ else
265
+ return result
266
+ end
195
267
  end
196
268
 
197
269
  if has_include?(column_names.first)
198
270
  relation = apply_join_dependency
199
271
  relation.pluck(*column_names)
200
272
  else
201
- klass.disallow_raw_sql!(column_names)
273
+ klass.disallow_raw_sql!(column_names.flatten)
202
274
  columns = arel_columns(column_names)
203
275
  relation = spawn
204
276
  relation.select_values = columns
205
277
  result = skip_query_cache_if_necessary do
206
278
  if where_clause.contradiction?
207
- ActiveRecord::Result.empty
279
+ ActiveRecord::Result.empty(async: @async)
208
280
  else
209
- klass.connection.select_all(relation.arel, "#{klass.name} Pluck")
281
+ klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
210
282
  end
211
283
  end
212
- type_cast_pluck_values(result, columns)
284
+ result.then do |result|
285
+ type_cast_pluck_values(result, columns)
286
+ end
213
287
  end
214
288
  end
215
289
 
290
+ # Same as <tt>#pluck</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
291
+ def async_pluck(*column_names)
292
+ async.pluck(*column_names)
293
+ end
294
+
216
295
  # Pick the value(s) from the named column(s) in the current relation.
217
296
  # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
218
297
  # when you have a relation that's already narrowed down to a single row.
@@ -229,18 +308,59 @@ module ActiveRecord
229
308
  # # => [ 'David', 'david@loudthinking.com' ]
230
309
  def pick(*column_names)
231
310
  if loaded? && all_attributes?(column_names)
232
- return records.pick(*column_names)
311
+ result = records.pick(*column_names)
312
+ return @async ? Promise::Complete.new(result) : result
233
313
  end
234
314
 
235
- limit(1).pluck(*column_names).first
315
+ limit(1).pluck(*column_names).then(&:first)
316
+ end
317
+
318
+ # Same as <tt>#pick</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
319
+ def async_pick(*column_names)
320
+ async.pick(*column_names)
236
321
  end
237
322
 
238
- # Pluck all the ID's for the relation using the table's primary key
323
+ # Returns the base model's ID's for the relation using the table's primary key
239
324
  #
240
325
  # Person.ids # SELECT people.id FROM people
241
- # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
326
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
242
327
  def ids
243
- pluck primary_key
328
+ primary_key_array = Array(primary_key)
329
+
330
+ if loaded?
331
+ result = records.map do |record|
332
+ if primary_key_array.one?
333
+ record._read_attribute(primary_key_array.first)
334
+ else
335
+ primary_key_array.map { |column| record._read_attribute(column) }
336
+ end
337
+ end
338
+ return @async ? Promise::Complete.new(result) : result
339
+ end
340
+
341
+ if has_include?(primary_key)
342
+ relation = apply_join_dependency.group(*primary_key_array)
343
+ return relation.ids
344
+ end
345
+
346
+ columns = arel_columns(primary_key_array)
347
+ relation = spawn
348
+ relation.select_values = columns
349
+
350
+ result = if relation.where_clause.contradiction?
351
+ ActiveRecord::Result.empty
352
+ else
353
+ skip_query_cache_if_necessary do
354
+ klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
355
+ end
356
+ end
357
+
358
+ result.then { |result| type_cast_pluck_values(result, columns) }
359
+ end
360
+
361
+ # Same as <tt>#ids</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
362
+ def async_ids
363
+ async.ids
244
364
  end
245
365
 
246
366
  private
@@ -300,6 +420,7 @@ module ActiveRecord
300
420
  # Shortcut when limit is zero.
301
421
  return 0 if limit_value == 0
302
422
 
423
+ relation = self
303
424
  query_builder = build_count_subquery(spawn, column_name, distinct)
304
425
  else
305
426
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
@@ -314,15 +435,23 @@ module ActiveRecord
314
435
  query_builder = relation.arel
315
436
  end
316
437
 
317
- result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}") }
318
-
319
- if operation != "count"
320
- type = column.try(:type_caster) ||
321
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
322
- type = type.subtype if Enum::EnumType === type
438
+ query_result = if relation.where_clause.contradiction?
439
+ ActiveRecord::Result.empty
440
+ else
441
+ skip_query_cache_if_necessary do
442
+ @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
443
+ end
323
444
  end
324
445
 
325
- type_cast_calculated_value(result.cast_values.first, operation, type)
446
+ query_result.then do |result|
447
+ if operation != "count"
448
+ type = column.try(:type_caster) ||
449
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
450
+ type = type.subtype if Enum::EnumType === type
451
+ end
452
+
453
+ type_cast_calculated_value(result.cast_values.first, operation, type)
454
+ end
326
455
  end
327
456
 
328
457
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
@@ -336,21 +465,24 @@ module ActiveRecord
336
465
  end
337
466
  group_fields = arel_columns(group_fields)
338
467
 
468
+ column_alias_tracker = ColumnAliasTracker.new(connection)
469
+
339
470
  group_aliases = group_fields.map { |field|
340
471
  field = connection.visitor.compile(field) if Arel.arel_node?(field)
341
- column_alias_for(field.to_s.downcase)
472
+ column_alias_tracker.alias_for(field.to_s.downcase)
342
473
  }
343
474
  group_columns = group_aliases.zip(group_fields)
344
475
 
345
476
  column = aggregate_column(column_name)
346
- column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
477
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
347
478
  select_value = operation_over_aggregate_column(column, operation, distinct)
348
- select_value.as(column_alias)
479
+ select_value.as(connection.quote_column_name(column_alias))
349
480
 
350
481
  select_values = [select_value]
351
482
  select_values += self.select_values unless having_clause.empty?
352
483
 
353
484
  select_values.concat group_columns.map { |aliaz, field|
485
+ aliaz = connection.quote_column_name(aliaz)
354
486
  if field.respond_to?(:as)
355
487
  field.as(aliaz)
356
488
  else
@@ -362,58 +494,43 @@ module ActiveRecord
362
494
  relation.group_values = group_fields
363
495
  relation.select_values = select_values
364
496
 
365
- calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}") }
366
-
367
- if association
368
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
369
- key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
370
- key_records = key_records.index_by(&:id)
371
- end
497
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async) }
498
+ result.then do |calculated_data|
499
+ if association
500
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
501
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
502
+ key_records = key_records.index_by(&:id)
503
+ end
372
504
 
373
- key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
374
- types[aliaz] = type_for(col_name) do
375
- calculated_data.column_types.fetch(aliaz, Type.default_value)
505
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
506
+ types[aliaz] = col_name.try(:type_caster) ||
507
+ type_for(col_name) do
508
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
509
+ end
376
510
  end
377
- end
378
511
 
379
- hash_rows = calculated_data.cast_values(key_types).map! do |row|
380
- calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
381
- hash[col_name] = row[i]
512
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
513
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
514
+ hash[col_name] = row[i]
515
+ end
382
516
  end
383
- end
384
517
 
385
- if operation != "count"
386
- type = column.try(:type_caster) ||
387
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
388
- type = type.subtype if Enum::EnumType === type
389
- end
518
+ if operation != "count"
519
+ type = column.try(:type_caster) ||
520
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
521
+ type = type.subtype if Enum::EnumType === type
522
+ end
390
523
 
391
- hash_rows.each_with_object({}) do |row, result|
392
- key = group_aliases.map { |aliaz| row[aliaz] }
393
- key = key.first if key.size == 1
394
- key = key_records[key] if associated
524
+ hash_rows.each_with_object({}) do |row, result|
525
+ key = group_aliases.map { |aliaz| row[aliaz] }
526
+ key = key.first if key.size == 1
527
+ key = key_records[key] if associated
395
528
 
396
- result[key] = type_cast_calculated_value(row[column_alias], operation, type)
529
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
530
+ end
397
531
  end
398
532
  end
399
533
 
400
- # Converts the given field to the value that the database adapter returns as
401
- # a usable column name:
402
- #
403
- # column_alias_for("users.id") # => "users_id"
404
- # column_alias_for("sum(id)") # => "sum_id"
405
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
406
- # column_alias_for("count(*)") # => "count_all"
407
- def column_alias_for(field)
408
- column_alias = +field
409
- column_alias.gsub!(/\*/, "all")
410
- column_alias.gsub!(/\W+/, " ")
411
- column_alias.strip!
412
- column_alias.gsub!(/ +/, "_")
413
-
414
- connection.table_alias_for(column_alias)
415
- end
416
-
417
534
  def type_for(field, &block)
418
535
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
419
536
  @klass.type_for_attribute(field_name, &block)
@@ -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,9 +97,9 @@ module ActiveRecord
85
97
  # may vary depending on the klass of a relation, so we create a subclass of Relation
86
98
  # for each different klass, and the delegations are compiled into that subclass only.
87
99
 
88
- delegate :to_xml, :encode_with, :length, :each, :join,
100
+ delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
89
101
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
90
- :to_sentence, :to_formatted_s, :as_json,
102
+ :to_sentence, :to_fs, :to_formatted_s, :as_json,
91
103
  :shuffle, :split, :slice, :index, :rindex, to: :records
92
104
 
93
105
  delegate :primary_key, :connection, to: :klass
@@ -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