activerecord 7.0.4 → 7.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1971 -1243
  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 +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 +20 -14
  15. data/lib/active_record/associations/collection_proxy.rb +20 -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/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader.rb +13 -10
  25. data/lib/active_record/associations/singular_association.rb +1 -1
  26. data/lib/active_record/associations/through_association.rb +22 -11
  27. data/lib/active_record/associations.rb +333 -222
  28. data/lib/active_record/attribute_assignment.rb +0 -2
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  31. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  32. data/lib/active_record/attribute_methods/query.rb +28 -16
  33. data/lib/active_record/attribute_methods/read.rb +21 -8
  34. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
  36. data/lib/active_record/attribute_methods/write.rb +6 -6
  37. data/lib/active_record/attribute_methods.rb +148 -26
  38. data/lib/active_record/attributes.rb +3 -3
  39. data/lib/active_record/autosave_association.rb +59 -10
  40. data/lib/active_record/base.rb +7 -2
  41. data/lib/active_record/callbacks.rb +16 -32
  42. data/lib/active_record/coders/column_serializer.rb +61 -0
  43. data/lib/active_record/coders/json.rb +1 -1
  44. data/lib/active_record/coders/yaml_column.rb +70 -42
  45. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  47. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +155 -25
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
  60. data/lib/active_record/connection_adapters/column.rb +9 -0
  61. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -14
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  71. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  81. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  82. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  83. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +365 -61
  85. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
  87. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  88. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  91. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
  94. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  97. data/lib/active_record/connection_adapters.rb +3 -1
  98. data/lib/active_record/connection_handling.rb +72 -95
  99. data/lib/active_record/core.rb +181 -154
  100. data/lib/active_record/counter_cache.rb +52 -27
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  102. data/lib/active_record/database_configurations/database_config.rb +9 -3
  103. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  104. data/lib/active_record/database_configurations/url_config.rb +17 -11
  105. data/lib/active_record/database_configurations.rb +86 -33
  106. data/lib/active_record/delegated_type.rb +15 -10
  107. data/lib/active_record/deprecator.rb +7 -0
  108. data/lib/active_record/destroy_association_async_job.rb +3 -1
  109. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  110. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  111. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  112. data/lib/active_record/encryption/config.rb +25 -1
  113. data/lib/active_record/encryption/configurable.rb +12 -19
  114. data/lib/active_record/encryption/context.rb +10 -3
  115. data/lib/active_record/encryption/contexts.rb +5 -1
  116. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  117. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  121. data/lib/active_record/encryption/key_generator.rb +12 -1
  122. data/lib/active_record/encryption/message_serializer.rb +2 -0
  123. data/lib/active_record/encryption/properties.rb +3 -3
  124. data/lib/active_record/encryption/scheme.rb +22 -21
  125. data/lib/active_record/encryption.rb +3 -0
  126. data/lib/active_record/enum.rb +112 -28
  127. data/lib/active_record/errors.rb +112 -18
  128. data/lib/active_record/explain.rb +23 -3
  129. data/lib/active_record/explain_subscriber.rb +1 -1
  130. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  131. data/lib/active_record/fixture_set/render_context.rb +2 -0
  132. data/lib/active_record/fixture_set/table_row.rb +29 -8
  133. data/lib/active_record/fixtures.rb +135 -71
  134. data/lib/active_record/future_result.rb +40 -5
  135. data/lib/active_record/gem_version.rb +4 -4
  136. data/lib/active_record/inheritance.rb +30 -16
  137. data/lib/active_record/insert_all.rb +57 -10
  138. data/lib/active_record/integration.rb +8 -8
  139. data/lib/active_record/internal_metadata.rb +120 -30
  140. data/lib/active_record/locking/optimistic.rb +33 -19
  141. data/lib/active_record/locking/pessimistic.rb +5 -2
  142. data/lib/active_record/log_subscriber.rb +29 -12
  143. data/lib/active_record/marshalling.rb +59 -0
  144. data/lib/active_record/message_pack.rb +124 -0
  145. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  146. data/lib/active_record/middleware/database_selector.rb +9 -11
  147. data/lib/active_record/middleware/shard_selector.rb +3 -1
  148. data/lib/active_record/migration/command_recorder.rb +105 -7
  149. data/lib/active_record/migration/compatibility.rb +163 -58
  150. data/lib/active_record/migration/default_strategy.rb +23 -0
  151. data/lib/active_record/migration/execution_strategy.rb +19 -0
  152. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  153. data/lib/active_record/migration.rb +271 -114
  154. data/lib/active_record/model_schema.rb +69 -44
  155. data/lib/active_record/nested_attributes.rb +37 -8
  156. data/lib/active_record/normalization.rb +167 -0
  157. data/lib/active_record/persistence.rb +195 -42
  158. data/lib/active_record/promise.rb +84 -0
  159. data/lib/active_record/query_cache.rb +4 -22
  160. data/lib/active_record/query_logs.rb +87 -51
  161. data/lib/active_record/query_logs_formatter.rb +41 -0
  162. data/lib/active_record/querying.rb +15 -2
  163. data/lib/active_record/railtie.rb +107 -45
  164. data/lib/active_record/railties/controller_runtime.rb +14 -9
  165. data/lib/active_record/railties/databases.rake +144 -150
  166. data/lib/active_record/railties/job_runtime.rb +23 -0
  167. data/lib/active_record/readonly_attributes.rb +32 -5
  168. data/lib/active_record/reflection.rb +189 -45
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  170. data/lib/active_record/relation/batches.rb +190 -61
  171. data/lib/active_record/relation/calculations.rb +232 -81
  172. data/lib/active_record/relation/delegation.rb +23 -9
  173. data/lib/active_record/relation/finder_methods.rb +77 -16
  174. data/lib/active_record/relation/merger.rb +2 -0
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  178. data/lib/active_record/relation/predicate_builder.rb +26 -14
  179. data/lib/active_record/relation/query_attribute.rb +25 -1
  180. data/lib/active_record/relation/query_methods.rb +408 -76
  181. data/lib/active_record/relation/spawn_methods.rb +18 -1
  182. data/lib/active_record/relation.rb +103 -37
  183. data/lib/active_record/result.rb +25 -9
  184. data/lib/active_record/runtime_registry.rb +24 -1
  185. data/lib/active_record/sanitization.rb +51 -11
  186. data/lib/active_record/schema.rb +2 -3
  187. data/lib/active_record/schema_dumper.rb +50 -7
  188. data/lib/active_record/schema_migration.rb +68 -33
  189. data/lib/active_record/scoping/default.rb +15 -5
  190. data/lib/active_record/scoping/named.rb +2 -2
  191. data/lib/active_record/scoping.rb +2 -1
  192. data/lib/active_record/secure_password.rb +60 -0
  193. data/lib/active_record/secure_token.rb +21 -3
  194. data/lib/active_record/signed_id.rb +7 -5
  195. data/lib/active_record/store.rb +9 -9
  196. data/lib/active_record/suppressor.rb +3 -1
  197. data/lib/active_record/table_metadata.rb +16 -3
  198. data/lib/active_record/tasks/database_tasks.rb +152 -108
  199. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  200. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  201. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  202. data/lib/active_record/test_fixtures.rb +114 -96
  203. data/lib/active_record/timestamp.rb +30 -16
  204. data/lib/active_record/token_for.rb +113 -0
  205. data/lib/active_record/touch_later.rb +11 -6
  206. data/lib/active_record/transactions.rb +39 -13
  207. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  208. data/lib/active_record/type/internal/timezone.rb +7 -2
  209. data/lib/active_record/type/serialized.rb +8 -4
  210. data/lib/active_record/type/time.rb +4 -0
  211. data/lib/active_record/validations/absence.rb +1 -1
  212. data/lib/active_record/validations/numericality.rb +5 -4
  213. data/lib/active_record/validations/presence.rb +5 -28
  214. data/lib/active_record/validations/uniqueness.rb +47 -2
  215. data/lib/active_record/validations.rb +8 -4
  216. data/lib/active_record/version.rb +1 -1
  217. data/lib/active_record.rb +130 -17
  218. data/lib/arel/errors.rb +10 -0
  219. data/lib/arel/factory_methods.rb +4 -0
  220. data/lib/arel/filter_predications.rb +1 -1
  221. data/lib/arel/nodes/and.rb +4 -0
  222. data/lib/arel/nodes/binary.rb +6 -1
  223. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  224. data/lib/arel/nodes/cte.rb +36 -0
  225. data/lib/arel/nodes/filter.rb +1 -1
  226. data/lib/arel/nodes/fragments.rb +35 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  228. data/lib/arel/nodes/leading_join.rb +8 -0
  229. data/lib/arel/nodes/node.rb +111 -2
  230. data/lib/arel/nodes/sql_literal.rb +6 -0
  231. data/lib/arel/nodes/table_alias.rb +4 -0
  232. data/lib/arel/nodes.rb +4 -0
  233. data/lib/arel/predications.rb +2 -0
  234. data/lib/arel/table.rb +9 -5
  235. data/lib/arel/tree_manager.rb +5 -1
  236. data/lib/arel/visitors/mysql.rb +8 -1
  237. data/lib/arel/visitors/to_sql.rb +83 -18
  238. data/lib/arel/visitors/visitor.rb +2 -2
  239. data/lib/arel.rb +16 -2
  240. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  241. data/lib/rails/generators/active_record/migration.rb +3 -1
  242. data/lib/rails/generators/active_record/model/USAGE +113 -0
  243. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  244. metadata +51 -15
  245. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  246. 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
  #
@@ -40,6 +81,16 @@ module ActiveRecord
40
81
  #
41
82
  # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
42
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.
43
94
  def count(column_name = nil)
44
95
  if block_given?
45
96
  unless column_name.nil?
@@ -52,6 +103,12 @@ module ActiveRecord
52
103
  end
53
104
  end
54
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
+
55
112
  # Calculates the average value on a given column. Returns +nil+ if there's
56
113
  # no row. See #calculate for examples with options.
57
114
  #
@@ -60,6 +117,12 @@ module ActiveRecord
60
117
  calculate(:average, column_name)
61
118
  end
62
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
+
63
126
  # Calculates the minimum value on a given column. The value is returned
64
127
  # with the same data type of the column, or +nil+ if there's no row. See
65
128
  # #calculate for examples with options.
@@ -69,6 +132,12 @@ module ActiveRecord
69
132
  calculate(:minimum, column_name)
70
133
  end
71
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
+
72
141
  # Calculates the maximum value on a given column. The value is returned
73
142
  # with the same data type of the column, or +nil+ if there's no row. See
74
143
  # #calculate for examples with options.
@@ -78,32 +147,42 @@ module ActiveRecord
78
147
  calculate(:maximum, column_name)
79
148
  end
80
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
+
81
156
  # Calculates the sum of values on a given column. The value is returned
82
157
  # with the same data type of the column, +0+ if there's no row. See
83
158
  # #calculate for examples with options.
84
159
  #
85
160
  # Person.sum(:age) # => 4562
86
- 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)
87
173
  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
174
+ map(&block).sum(initial_value_or_column)
102
175
  else
103
- calculate(:sum, identity_or_column)
176
+ calculate(:sum, initial_value_or_column)
104
177
  end
105
178
  end
106
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
+
107
186
  # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
108
187
  # #minimum, and #maximum have been added as shortcuts.
109
188
  #
@@ -136,10 +215,23 @@ module ActiveRecord
136
215
  # ...
137
216
  # end
138
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
+
139
231
  if has_include?(column_name)
140
232
  relation = apply_join_dependency
141
233
 
142
- if operation.to_s.downcase == "count"
234
+ if operation == "count"
143
235
  unless distinct_value || distinct_select?(column_name || select_for_count)
144
236
  relation.distinct!
145
237
  relation.select_values = [ klass.primary_key || table[Arel.star] ]
@@ -188,31 +280,51 @@ module ActiveRecord
188
280
  # # => ['0', '27761', '173']
189
281
  #
190
282
  # See also #ids.
191
- #
192
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
+
193
292
  if loaded? && all_attributes?(column_names)
194
- 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
195
299
  end
196
300
 
197
301
  if has_include?(column_names.first)
198
302
  relation = apply_join_dependency
199
303
  relation.pluck(*column_names)
200
304
  else
201
- klass.disallow_raw_sql!(column_names)
305
+ klass.disallow_raw_sql!(column_names.flatten)
202
306
  columns = arel_columns(column_names)
203
307
  relation = spawn
204
308
  relation.select_values = columns
205
309
  result = skip_query_cache_if_necessary do
206
310
  if where_clause.contradiction?
207
- ActiveRecord::Result.empty
311
+ ActiveRecord::Result.empty(async: @async)
208
312
  else
209
- klass.connection.select_all(relation.arel, "#{klass.name} Pluck")
313
+ klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
210
314
  end
211
315
  end
212
- type_cast_pluck_values(result, columns)
316
+ result.then do |result|
317
+ type_cast_pluck_values(result, columns)
318
+ end
213
319
  end
214
320
  end
215
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
+
216
328
  # Pick the value(s) from the named column(s) in the current relation.
217
329
  # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
218
330
  # when you have a relation that's already narrowed down to a single row.
@@ -229,18 +341,61 @@ module ActiveRecord
229
341
  # # => [ 'David', 'david@loudthinking.com' ]
230
342
  def pick(*column_names)
231
343
  if loaded? && all_attributes?(column_names)
232
- return records.pick(*column_names)
344
+ result = records.pick(*column_names)
345
+ return @async ? Promise::Complete.new(result) : result
233
346
  end
234
347
 
235
- limit(1).pluck(*column_names).first
348
+ limit(1).pluck(*column_names).then(&:first)
236
349
  end
237
350
 
238
- # Pluck all the ID's for the relation using the table's primary key
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)
355
+ end
356
+
357
+ # Returns the base model's ID's for the relation using the table's primary key
239
358
  #
240
359
  # 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
360
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
242
361
  def ids
243
- 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
244
399
  end
245
400
 
246
401
  private
@@ -300,6 +455,7 @@ module ActiveRecord
300
455
  # Shortcut when limit is zero.
301
456
  return 0 if limit_value == 0
302
457
 
458
+ relation = self
303
459
  query_builder = build_count_subquery(spawn, column_name, distinct)
304
460
  else
305
461
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
@@ -314,15 +470,23 @@ module ActiveRecord
314
470
  query_builder = relation.arel
315
471
  end
316
472
 
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
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
323
479
  end
324
480
 
325
- 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
326
490
  end
327
491
 
328
492
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
@@ -336,14 +500,16 @@ module ActiveRecord
336
500
  end
337
501
  group_fields = arel_columns(group_fields)
338
502
 
503
+ column_alias_tracker = ColumnAliasTracker.new(connection)
504
+
339
505
  group_aliases = group_fields.map { |field|
340
506
  field = connection.visitor.compile(field) if Arel.arel_node?(field)
341
- column_alias_for(field.to_s.downcase)
507
+ column_alias_tracker.alias_for(field.to_s.downcase)
342
508
  }
343
509
  group_columns = group_aliases.zip(group_fields)
344
510
 
345
511
  column = aggregate_column(column_name)
346
- column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
512
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
347
513
  select_value = operation_over_aggregate_column(column, operation, distinct)
348
514
  select_value.as(connection.quote_column_name(column_alias))
349
515
 
@@ -363,58 +529,43 @@ module ActiveRecord
363
529
  relation.group_values = group_fields
364
530
  relation.select_values = select_values
365
531
 
366
- calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}") }
367
-
368
- if association
369
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
370
- key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
371
- key_records = key_records.index_by(&:id)
372
- end
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
373
539
 
374
- key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
375
- types[aliaz] = type_for(col_name) do
376
- calculated_data.column_types.fetch(aliaz, Type.default_value)
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
377
545
  end
378
- end
379
546
 
380
- hash_rows = calculated_data.cast_values(key_types).map! do |row|
381
- calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
382
- hash[col_name] = row[i]
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]
550
+ end
383
551
  end
384
- end
385
552
 
386
- if operation != "count"
387
- type = column.try(:type_caster) ||
388
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
389
- type = type.subtype if Enum::EnumType === type
390
- 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
391
558
 
392
- hash_rows.each_with_object({}) do |row, result|
393
- key = group_aliases.map { |aliaz| row[aliaz] }
394
- key = key.first if key.size == 1
395
- 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
396
563
 
397
- 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
398
566
  end
399
567
  end
400
568
 
401
- # Converts the given field to the value that the database adapter returns as
402
- # a usable column name:
403
- #
404
- # column_alias_for("users.id") # => "users_id"
405
- # column_alias_for("sum(id)") # => "sum_id"
406
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
407
- # column_alias_for("count(*)") # => "count_all"
408
- def column_alias_for(field)
409
- column_alias = +field
410
- column_alias.gsub!(/\*/, "all")
411
- column_alias.gsub!(/\W+/, " ")
412
- column_alias.strip!
413
- column_alias.gsub!(/ +/, "_")
414
-
415
- connection.table_alias_for(column_alias)
416
- end
417
-
418
569
  def type_for(field, &block)
419
570
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
420
571
  @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,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