activerecord 7.0.0 → 7.1.2

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 +1701 -1039
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -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 +362 -236
  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 +52 -34
  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 +129 -31
  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 -131
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -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 +151 -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 +16 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -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/money.rb +3 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
  84. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  85. data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
  86. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  87. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  93. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  94. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  95. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  96. data/lib/active_record/connection_adapters.rb +3 -1
  97. data/lib/active_record/connection_handling.rb +73 -96
  98. data/lib/active_record/core.rb +142 -153
  99. data/lib/active_record/counter_cache.rb +46 -25
  100. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  101. data/lib/active_record/database_configurations/database_config.rb +9 -3
  102. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  103. data/lib/active_record/database_configurations/url_config.rb +17 -11
  104. data/lib/active_record/database_configurations.rb +87 -34
  105. data/lib/active_record/delegated_type.rb +9 -4
  106. data/lib/active_record/deprecator.rb +7 -0
  107. data/lib/active_record/destroy_association_async_job.rb +2 -0
  108. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  109. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  110. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  111. data/lib/active_record/encryption/config.rb +25 -1
  112. data/lib/active_record/encryption/configurable.rb +13 -14
  113. data/lib/active_record/encryption/context.rb +10 -3
  114. data/lib/active_record/encryption/contexts.rb +8 -4
  115. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  116. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  117. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  119. data/lib/active_record/encryption/encryptor.rb +7 -7
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  121. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  122. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  123. data/lib/active_record/encryption/key_generator.rb +12 -1
  124. data/lib/active_record/encryption/message.rb +1 -1
  125. data/lib/active_record/encryption/message_serializer.rb +2 -0
  126. data/lib/active_record/encryption/properties.rb +4 -4
  127. data/lib/active_record/encryption/scheme.rb +20 -23
  128. data/lib/active_record/encryption.rb +1 -0
  129. data/lib/active_record/enum.rb +113 -29
  130. data/lib/active_record/errors.rb +108 -15
  131. data/lib/active_record/explain.rb +23 -3
  132. data/lib/active_record/explain_subscriber.rb +1 -1
  133. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  134. data/lib/active_record/fixture_set/render_context.rb +2 -0
  135. data/lib/active_record/fixture_set/table_row.rb +29 -8
  136. data/lib/active_record/fixtures.rb +121 -73
  137. data/lib/active_record/future_result.rb +30 -5
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +30 -16
  140. data/lib/active_record/insert_all.rb +57 -10
  141. data/lib/active_record/integration.rb +10 -10
  142. data/lib/active_record/internal_metadata.rb +120 -30
  143. data/lib/active_record/locking/optimistic.rb +32 -18
  144. data/lib/active_record/locking/pessimistic.rb +8 -5
  145. data/lib/active_record/log_subscriber.rb +39 -17
  146. data/lib/active_record/marshalling.rb +56 -0
  147. data/lib/active_record/message_pack.rb +124 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  149. data/lib/active_record/middleware/database_selector.rb +18 -13
  150. data/lib/active_record/middleware/shard_selector.rb +7 -5
  151. data/lib/active_record/migration/command_recorder.rb +108 -10
  152. data/lib/active_record/migration/compatibility.rb +158 -64
  153. data/lib/active_record/migration/default_strategy.rb +23 -0
  154. data/lib/active_record/migration/execution_strategy.rb +19 -0
  155. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  156. data/lib/active_record/migration.rb +274 -117
  157. data/lib/active_record/model_schema.rb +86 -54
  158. data/lib/active_record/nested_attributes.rb +24 -6
  159. data/lib/active_record/normalization.rb +167 -0
  160. data/lib/active_record/persistence.rb +200 -47
  161. data/lib/active_record/promise.rb +84 -0
  162. data/lib/active_record/query_cache.rb +3 -21
  163. data/lib/active_record/query_logs.rb +87 -51
  164. data/lib/active_record/query_logs_formatter.rb +41 -0
  165. data/lib/active_record/querying.rb +16 -3
  166. data/lib/active_record/railtie.rb +128 -62
  167. data/lib/active_record/railties/controller_runtime.rb +12 -8
  168. data/lib/active_record/railties/databases.rake +145 -146
  169. data/lib/active_record/railties/job_runtime.rb +23 -0
  170. data/lib/active_record/readonly_attributes.rb +32 -5
  171. data/lib/active_record/reflection.rb +189 -45
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  173. data/lib/active_record/relation/batches.rb +190 -61
  174. data/lib/active_record/relation/calculations.rb +208 -83
  175. data/lib/active_record/relation/delegation.rb +23 -9
  176. data/lib/active_record/relation/finder_methods.rb +77 -16
  177. data/lib/active_record/relation/merger.rb +2 -0
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  181. data/lib/active_record/relation/predicate_builder.rb +26 -14
  182. data/lib/active_record/relation/query_attribute.rb +25 -1
  183. data/lib/active_record/relation/query_methods.rb +430 -77
  184. data/lib/active_record/relation/spawn_methods.rb +18 -1
  185. data/lib/active_record/relation.rb +98 -41
  186. data/lib/active_record/result.rb +25 -9
  187. data/lib/active_record/runtime_registry.rb +10 -1
  188. data/lib/active_record/sanitization.rb +57 -16
  189. data/lib/active_record/schema.rb +36 -22
  190. data/lib/active_record/schema_dumper.rb +65 -23
  191. data/lib/active_record/schema_migration.rb +68 -33
  192. data/lib/active_record/scoping/default.rb +20 -12
  193. data/lib/active_record/scoping/named.rb +2 -2
  194. data/lib/active_record/scoping.rb +2 -1
  195. data/lib/active_record/secure_password.rb +60 -0
  196. data/lib/active_record/secure_token.rb +21 -3
  197. data/lib/active_record/serialization.rb +5 -0
  198. data/lib/active_record/signed_id.rb +9 -7
  199. data/lib/active_record/store.rb +16 -11
  200. data/lib/active_record/suppressor.rb +3 -1
  201. data/lib/active_record/table_metadata.rb +16 -3
  202. data/lib/active_record/tasks/database_tasks.rb +138 -107
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  206. data/lib/active_record/test_fixtures.rb +123 -99
  207. data/lib/active_record/timestamp.rb +27 -15
  208. data/lib/active_record/token_for.rb +113 -0
  209. data/lib/active_record/touch_later.rb +11 -6
  210. data/lib/active_record/transactions.rb +39 -13
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  213. data/lib/active_record/type/internal/timezone.rb +7 -2
  214. data/lib/active_record/type/serialized.rb +8 -4
  215. data/lib/active_record/type/time.rb +4 -0
  216. data/lib/active_record/validations/absence.rb +1 -1
  217. data/lib/active_record/validations/associated.rb +3 -3
  218. data/lib/active_record/validations/numericality.rb +5 -4
  219. data/lib/active_record/validations/presence.rb +5 -28
  220. data/lib/active_record/validations/uniqueness.rb +50 -5
  221. data/lib/active_record/validations.rb +8 -4
  222. data/lib/active_record/version.rb +1 -1
  223. data/lib/active_record.rb +143 -16
  224. data/lib/arel/errors.rb +10 -0
  225. data/lib/arel/factory_methods.rb +4 -0
  226. data/lib/arel/filter_predications.rb +1 -1
  227. data/lib/arel/nodes/and.rb +4 -0
  228. data/lib/arel/nodes/binary.rb +6 -1
  229. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  230. data/lib/arel/nodes/cte.rb +36 -0
  231. data/lib/arel/nodes/filter.rb +1 -1
  232. data/lib/arel/nodes/fragments.rb +35 -0
  233. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  234. data/lib/arel/nodes/leading_join.rb +8 -0
  235. data/lib/arel/nodes/node.rb +111 -2
  236. data/lib/arel/nodes/sql_literal.rb +6 -0
  237. data/lib/arel/nodes/table_alias.rb +4 -0
  238. data/lib/arel/nodes.rb +4 -0
  239. data/lib/arel/predications.rb +2 -0
  240. data/lib/arel/table.rb +9 -5
  241. data/lib/arel/visitors/mysql.rb +8 -1
  242. data/lib/arel/visitors/to_sql.rb +81 -17
  243. data/lib/arel/visitors/visitor.rb +2 -2
  244. data/lib/arel.rb +16 -2
  245. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  246. data/lib/rails/generators/active_record/migration.rb +3 -1
  247. data/lib/rails/generators/active_record/model/USAGE +113 -0
  248. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  249. metadata +51 -15
  250. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  251. 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,12 @@ module ActiveRecord
52
93
  end
53
94
  end
54
95
 
96
+ # Same as #count, but performs the query asynchronously and returns an
97
+ # ActiveRecord::Promise.
98
+ def async_count(column_name = nil)
99
+ async.count(column_name)
100
+ end
101
+
55
102
  # Calculates the average value on a given column. Returns +nil+ if there's
56
103
  # no row. See #calculate for examples with options.
57
104
  #
@@ -60,6 +107,12 @@ module ActiveRecord
60
107
  calculate(:average, column_name)
61
108
  end
62
109
 
110
+ # Same as #average, but performs the query asynchronously and returns an
111
+ # ActiveRecord::Promise.
112
+ def async_average(column_name)
113
+ async.average(column_name)
114
+ end
115
+
63
116
  # Calculates the minimum value on a given column. The value is returned
64
117
  # with the same data type of the column, or +nil+ if there's no row. See
65
118
  # #calculate for examples with options.
@@ -69,6 +122,12 @@ module ActiveRecord
69
122
  calculate(:minimum, column_name)
70
123
  end
71
124
 
125
+ # Same as #minimum, but performs the query asynchronously and returns an
126
+ # ActiveRecord::Promise.
127
+ def async_minimum(column_name)
128
+ async.minimum(column_name)
129
+ end
130
+
72
131
  # Calculates the maximum value on a given column. The value is returned
73
132
  # with the same data type of the column, or +nil+ if there's no row. See
74
133
  # #calculate for examples with options.
@@ -78,32 +137,31 @@ module ActiveRecord
78
137
  calculate(:maximum, column_name)
79
138
  end
80
139
 
140
+ # Same as #maximum, but performs the query asynchronously and returns an
141
+ # ActiveRecord::Promise.
142
+ def async_maximum(column_name)
143
+ async.maximum(column_name)
144
+ end
145
+
81
146
  # Calculates the sum of values on a given column. The value is returned
82
147
  # with the same data type of the column, +0+ if there's no row. See
83
148
  # #calculate for examples with options.
84
149
  #
85
150
  # Person.sum(:age) # => 4562
86
- def sum(identity_or_column = nil, &block)
151
+ def sum(initial_value_or_column = 0, &block)
87
152
  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
153
+ map(&block).sum(initial_value_or_column)
102
154
  else
103
- calculate(:sum, identity_or_column)
155
+ calculate(:sum, initial_value_or_column)
104
156
  end
105
157
  end
106
158
 
159
+ # Same as #sum, but performs the query asynchronously and returns an
160
+ # ActiveRecord::Promise.
161
+ def async_sum(identity_or_column = nil)
162
+ async.sum(identity_or_column)
163
+ end
164
+
107
165
  # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
108
166
  # #minimum, and #maximum have been added as shortcuts.
109
167
  #
@@ -136,10 +194,23 @@ module ActiveRecord
136
194
  # ...
137
195
  # end
138
196
  def calculate(operation, column_name)
197
+ operation = operation.to_s.downcase
198
+
199
+ if @none
200
+ case operation
201
+ when "count", "sum"
202
+ result = group_values.any? ? Hash.new : 0
203
+ return @async ? Promise::Complete.new(result) : result
204
+ when "average", "minimum", "maximum"
205
+ result = group_values.any? ? Hash.new : nil
206
+ return @async ? Promise::Complete.new(result) : result
207
+ end
208
+ end
209
+
139
210
  if has_include?(column_name)
140
211
  relation = apply_join_dependency
141
212
 
142
- if operation.to_s.downcase == "count"
213
+ if operation == "count"
143
214
  unless distinct_value || distinct_select?(column_name || select_for_count)
144
215
  relation.distinct!
145
216
  relation.select_values = [ klass.primary_key || table[Arel.star] ]
@@ -155,7 +226,7 @@ module ActiveRecord
155
226
  end
156
227
 
157
228
  # 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.
229
+ # loading an entire record object per row.
159
230
  #
160
231
  # Person.pluck(:name)
161
232
  #
@@ -188,31 +259,45 @@ module ActiveRecord
188
259
  # # => ['0', '27761', '173']
189
260
  #
190
261
  # See also #ids.
191
- #
192
262
  def pluck(*column_names)
263
+ return [] if @none
264
+
193
265
  if loaded? && all_attributes?(column_names)
194
- return records.pluck(*column_names)
266
+ result = records.pluck(*column_names)
267
+ if @async
268
+ return Promise::Complete.new(result)
269
+ else
270
+ return result
271
+ end
195
272
  end
196
273
 
197
274
  if has_include?(column_names.first)
198
275
  relation = apply_join_dependency
199
276
  relation.pluck(*column_names)
200
277
  else
201
- klass.disallow_raw_sql!(column_names)
278
+ klass.disallow_raw_sql!(column_names.flatten)
202
279
  columns = arel_columns(column_names)
203
280
  relation = spawn
204
281
  relation.select_values = columns
205
282
  result = skip_query_cache_if_necessary do
206
283
  if where_clause.contradiction?
207
- ActiveRecord::Result.empty
284
+ ActiveRecord::Result.empty(async: @async)
208
285
  else
209
- klass.connection.select_all(relation.arel, "#{klass.name} Pluck")
286
+ klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
210
287
  end
211
288
  end
212
- type_cast_pluck_values(result, columns)
289
+ result.then do |result|
290
+ type_cast_pluck_values(result, columns)
291
+ end
213
292
  end
214
293
  end
215
294
 
295
+ # Same as #pluck, but performs the query asynchronously and returns an
296
+ # ActiveRecord::Promise.
297
+ def async_pluck(*column_names)
298
+ async.pluck(*column_names)
299
+ end
300
+
216
301
  # Pick the value(s) from the named column(s) in the current relation.
217
302
  # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
218
303
  # when you have a relation that's already narrowed down to a single row.
@@ -229,18 +314,61 @@ module ActiveRecord
229
314
  # # => [ 'David', 'david@loudthinking.com' ]
230
315
  def pick(*column_names)
231
316
  if loaded? && all_attributes?(column_names)
232
- return records.pick(*column_names)
317
+ result = records.pick(*column_names)
318
+ return @async ? Promise::Complete.new(result) : result
233
319
  end
234
320
 
235
- limit(1).pluck(*column_names).first
321
+ limit(1).pluck(*column_names).then(&:first)
322
+ end
323
+
324
+ # Same as #pick, but performs the query asynchronously and returns an
325
+ # ActiveRecord::Promise.
326
+ def async_pick(*column_names)
327
+ async.pick(*column_names)
236
328
  end
237
329
 
238
- # Pluck all the ID's for the relation using the table's primary key
330
+ # Returns the base model's ID's for the relation using the table's primary key
239
331
  #
240
332
  # 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
333
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
242
334
  def ids
243
- pluck primary_key
335
+ primary_key_array = Array(primary_key)
336
+
337
+ if loaded?
338
+ result = records.map do |record|
339
+ if primary_key_array.one?
340
+ record._read_attribute(primary_key_array.first)
341
+ else
342
+ primary_key_array.map { |column| record._read_attribute(column) }
343
+ end
344
+ end
345
+ return @async ? Promise::Complete.new(result) : result
346
+ end
347
+
348
+ if has_include?(primary_key)
349
+ relation = apply_join_dependency.group(*primary_key_array)
350
+ return relation.ids
351
+ end
352
+
353
+ columns = arel_columns(primary_key_array)
354
+ relation = spawn
355
+ relation.select_values = columns
356
+
357
+ result = if relation.where_clause.contradiction?
358
+ ActiveRecord::Result.empty
359
+ else
360
+ skip_query_cache_if_necessary do
361
+ klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
362
+ end
363
+ end
364
+
365
+ result.then { |result| type_cast_pluck_values(result, columns) }
366
+ end
367
+
368
+ # Same as #ids, but performs the query asynchronously and returns an
369
+ # ActiveRecord::Promise.
370
+ def async_ids
371
+ async.ids
244
372
  end
245
373
 
246
374
  private
@@ -300,6 +428,7 @@ module ActiveRecord
300
428
  # Shortcut when limit is zero.
301
429
  return 0 if limit_value == 0
302
430
 
431
+ relation = self
303
432
  query_builder = build_count_subquery(spawn, column_name, distinct)
304
433
  else
305
434
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
@@ -314,15 +443,23 @@ module ActiveRecord
314
443
  query_builder = relation.arel
315
444
  end
316
445
 
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
446
+ query_result = if relation.where_clause.contradiction?
447
+ ActiveRecord::Result.empty
448
+ else
449
+ skip_query_cache_if_necessary do
450
+ @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
451
+ end
323
452
  end
324
453
 
325
- type_cast_calculated_value(result.cast_values.first, operation, type)
454
+ query_result.then do |result|
455
+ if operation != "count"
456
+ type = column.try(:type_caster) ||
457
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
458
+ type = type.subtype if Enum::EnumType === type
459
+ end
460
+
461
+ type_cast_calculated_value(result.cast_values.first, operation, type)
462
+ end
326
463
  end
327
464
 
328
465
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
@@ -336,21 +473,24 @@ module ActiveRecord
336
473
  end
337
474
  group_fields = arel_columns(group_fields)
338
475
 
476
+ column_alias_tracker = ColumnAliasTracker.new(connection)
477
+
339
478
  group_aliases = group_fields.map { |field|
340
479
  field = connection.visitor.compile(field) if Arel.arel_node?(field)
341
- column_alias_for(field.to_s.downcase)
480
+ column_alias_tracker.alias_for(field.to_s.downcase)
342
481
  }
343
482
  group_columns = group_aliases.zip(group_fields)
344
483
 
345
484
  column = aggregate_column(column_name)
346
- column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
485
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
347
486
  select_value = operation_over_aggregate_column(column, operation, distinct)
348
- select_value.as(column_alias)
487
+ select_value.as(connection.quote_column_name(column_alias))
349
488
 
350
489
  select_values = [select_value]
351
490
  select_values += self.select_values unless having_clause.empty?
352
491
 
353
492
  select_values.concat group_columns.map { |aliaz, field|
493
+ aliaz = connection.quote_column_name(aliaz)
354
494
  if field.respond_to?(:as)
355
495
  field.as(aliaz)
356
496
  else
@@ -362,58 +502,43 @@ module ActiveRecord
362
502
  relation.group_values = group_fields
363
503
  relation.select_values = select_values
364
504
 
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
505
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async) }
506
+ result.then do |calculated_data|
507
+ if association
508
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
509
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
510
+ key_records = key_records.index_by(&:id)
511
+ end
372
512
 
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)
513
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
514
+ types[aliaz] = col_name.try(:type_caster) ||
515
+ type_for(col_name) do
516
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
517
+ end
376
518
  end
377
- end
378
519
 
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]
520
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
521
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
522
+ hash[col_name] = row[i]
523
+ end
382
524
  end
383
- end
384
525
 
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
526
+ if operation != "count"
527
+ type = column.try(:type_caster) ||
528
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
529
+ type = type.subtype if Enum::EnumType === type
530
+ end
390
531
 
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
532
+ hash_rows.each_with_object({}) do |row, result|
533
+ key = group_aliases.map { |aliaz| row[aliaz] }
534
+ key = key.first if key.size == 1
535
+ key = key_records[key] if associated
395
536
 
396
- result[key] = type_cast_calculated_value(row[column_alias], operation, type)
537
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
538
+ end
397
539
  end
398
540
  end
399
541
 
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
542
  def type_for(field, &block)
418
543
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
419
544
  @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