activerecord 7.0.8 → 7.1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1554 -1452
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +15 -9
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +313 -217
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +175 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +135 -71
- data/lib/active_record/future_result.rb +31 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +104 -5
- data/lib/active_record/migration/compatibility.rb +139 -5
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +219 -111
- data/lib/active_record/model_schema.rb +64 -44
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +188 -37
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +109 -47
- data/lib/active_record/railties/controller_runtime.rb +12 -6
- data/lib/active_record/railties/databases.rake +142 -148
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +174 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +187 -63
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +352 -63
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +91 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +48 -12
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -3,6 +3,7 @@
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
|
+
# = Active Record \Calculations
|
6
7
|
module Calculations
|
7
8
|
class ColumnAliasTracker # :nodoc:
|
8
9
|
def initialize(connection)
|
@@ -71,8 +72,7 @@ module ActiveRecord
|
|
71
72
|
# of each key would be the #count.
|
72
73
|
#
|
73
74
|
# Article.group(:status, :category).count
|
74
|
-
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
|
75
|
-
# # ["published", "business"]=>0, ["published", "technology"]=>2}
|
75
|
+
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
76
76
|
#
|
77
77
|
# If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
78
78
|
#
|
@@ -81,6 +81,16 @@ module ActiveRecord
|
|
81
81
|
#
|
82
82
|
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
|
83
83
|
# between databases. In invalid cases, an error from the database is thrown.
|
84
|
+
#
|
85
|
+
# When given a block, loads all records in the relation, if the relation
|
86
|
+
# hasn't been loaded yet. Calls the block with each record in the relation.
|
87
|
+
# Returns the number of records for which the block returns a truthy value.
|
88
|
+
#
|
89
|
+
# Person.count { |person| person.age > 21 }
|
90
|
+
# # => counts the number of people older that 21
|
91
|
+
#
|
92
|
+
# Note: If there are a lot of records in the relation, loading all records
|
93
|
+
# could result in performance issues.
|
84
94
|
def count(column_name = nil)
|
85
95
|
if block_given?
|
86
96
|
unless column_name.nil?
|
@@ -93,6 +103,12 @@ module ActiveRecord
|
|
93
103
|
end
|
94
104
|
end
|
95
105
|
|
106
|
+
# Same as #count, but performs the query asynchronously and returns an
|
107
|
+
# ActiveRecord::Promise.
|
108
|
+
def async_count(column_name = nil)
|
109
|
+
async.count(column_name)
|
110
|
+
end
|
111
|
+
|
96
112
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
97
113
|
# no row. See #calculate for examples with options.
|
98
114
|
#
|
@@ -101,6 +117,12 @@ module ActiveRecord
|
|
101
117
|
calculate(:average, column_name)
|
102
118
|
end
|
103
119
|
|
120
|
+
# Same as #average, but performs the query asynchronously and returns an
|
121
|
+
# ActiveRecord::Promise.
|
122
|
+
def async_average(column_name)
|
123
|
+
async.average(column_name)
|
124
|
+
end
|
125
|
+
|
104
126
|
# Calculates the minimum value on a given column. The value is returned
|
105
127
|
# with the same data type of the column, or +nil+ if there's no row. See
|
106
128
|
# #calculate for examples with options.
|
@@ -110,6 +132,12 @@ module ActiveRecord
|
|
110
132
|
calculate(:minimum, column_name)
|
111
133
|
end
|
112
134
|
|
135
|
+
# Same as #minimum, but performs the query asynchronously and returns an
|
136
|
+
# ActiveRecord::Promise.
|
137
|
+
def async_minimum(column_name)
|
138
|
+
async.minimum(column_name)
|
139
|
+
end
|
140
|
+
|
113
141
|
# Calculates the maximum value on a given column. The value is returned
|
114
142
|
# with the same data type of the column, or +nil+ if there's no row. See
|
115
143
|
# #calculate for examples with options.
|
@@ -119,32 +147,42 @@ module ActiveRecord
|
|
119
147
|
calculate(:maximum, column_name)
|
120
148
|
end
|
121
149
|
|
150
|
+
# Same as #maximum, but performs the query asynchronously and returns an
|
151
|
+
# ActiveRecord::Promise.
|
152
|
+
def async_maximum(column_name)
|
153
|
+
async.maximum(column_name)
|
154
|
+
end
|
155
|
+
|
122
156
|
# Calculates the sum of values on a given column. The value is returned
|
123
157
|
# with the same data type of the column, +0+ if there's no row. See
|
124
158
|
# #calculate for examples with options.
|
125
159
|
#
|
126
160
|
# Person.sum(:age) # => 4562
|
127
|
-
|
161
|
+
#
|
162
|
+
# When given a block, loads all records in the relation, if the relation
|
163
|
+
# hasn't been loaded yet. Calls the block with each record in the relation.
|
164
|
+
# Returns the sum of +initial_value_or_column+ and the block return
|
165
|
+
# values:
|
166
|
+
#
|
167
|
+
# Person.sum { |person| person.age } # => 4562
|
168
|
+
# Person.sum(1000) { |person| person.age } # => 5562
|
169
|
+
#
|
170
|
+
# Note: If there are a lot of records in the relation, loading all records
|
171
|
+
# could result in performance issues.
|
172
|
+
def sum(initial_value_or_column = 0, &block)
|
128
173
|
if block_given?
|
129
|
-
|
130
|
-
if identity_or_column.nil? && (values.first.is_a?(Numeric) || values.first(1) == [] || values.first.respond_to?(:coerce))
|
131
|
-
identity_or_column = 0
|
132
|
-
end
|
133
|
-
|
134
|
-
if identity_or_column.nil?
|
135
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
136
|
-
Rails 7.0 has deprecated Enumerable.sum in favor of Ruby's native implementation available since 2.4.
|
137
|
-
Sum of non-numeric elements requires an initial argument.
|
138
|
-
MSG
|
139
|
-
values.inject(:+) || 0
|
140
|
-
else
|
141
|
-
values.sum(identity_or_column)
|
142
|
-
end
|
174
|
+
map(&block).sum(initial_value_or_column)
|
143
175
|
else
|
144
|
-
calculate(:sum,
|
176
|
+
calculate(:sum, initial_value_or_column)
|
145
177
|
end
|
146
178
|
end
|
147
179
|
|
180
|
+
# Same as #sum, but performs the query asynchronously and returns an
|
181
|
+
# ActiveRecord::Promise.
|
182
|
+
def async_sum(identity_or_column = nil)
|
183
|
+
async.sum(identity_or_column)
|
184
|
+
end
|
185
|
+
|
148
186
|
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
149
187
|
# #minimum, and #maximum have been added as shortcuts.
|
150
188
|
#
|
@@ -177,10 +215,23 @@ module ActiveRecord
|
|
177
215
|
# ...
|
178
216
|
# end
|
179
217
|
def calculate(operation, column_name)
|
218
|
+
operation = operation.to_s.downcase
|
219
|
+
|
220
|
+
if @none
|
221
|
+
case operation
|
222
|
+
when "count", "sum"
|
223
|
+
result = group_values.any? ? Hash.new : 0
|
224
|
+
return @async ? Promise::Complete.new(result) : result
|
225
|
+
when "average", "minimum", "maximum"
|
226
|
+
result = group_values.any? ? Hash.new : nil
|
227
|
+
return @async ? Promise::Complete.new(result) : result
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
180
231
|
if has_include?(column_name)
|
181
232
|
relation = apply_join_dependency
|
182
233
|
|
183
|
-
if operation
|
234
|
+
if operation == "count"
|
184
235
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
185
236
|
relation.distinct!
|
186
237
|
relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
@@ -229,31 +280,51 @@ module ActiveRecord
|
|
229
280
|
# # => ['0', '27761', '173']
|
230
281
|
#
|
231
282
|
# See also #ids.
|
232
|
-
#
|
233
283
|
def pluck(*column_names)
|
284
|
+
if @none
|
285
|
+
if @async
|
286
|
+
return Promise::Complete.new([])
|
287
|
+
else
|
288
|
+
return []
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
234
292
|
if loaded? && all_attributes?(column_names)
|
235
|
-
|
293
|
+
result = records.pluck(*column_names)
|
294
|
+
if @async
|
295
|
+
return Promise::Complete.new(result)
|
296
|
+
else
|
297
|
+
return result
|
298
|
+
end
|
236
299
|
end
|
237
300
|
|
238
301
|
if has_include?(column_names.first)
|
239
302
|
relation = apply_join_dependency
|
240
303
|
relation.pluck(*column_names)
|
241
304
|
else
|
242
|
-
klass.disallow_raw_sql!(column_names)
|
305
|
+
klass.disallow_raw_sql!(column_names.flatten)
|
243
306
|
columns = arel_columns(column_names)
|
244
307
|
relation = spawn
|
245
308
|
relation.select_values = columns
|
246
309
|
result = skip_query_cache_if_necessary do
|
247
310
|
if where_clause.contradiction?
|
248
|
-
ActiveRecord::Result.empty
|
311
|
+
ActiveRecord::Result.empty(async: @async)
|
249
312
|
else
|
250
|
-
klass.connection.select_all(relation.arel, "#{klass.name} Pluck")
|
313
|
+
klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
|
251
314
|
end
|
252
315
|
end
|
253
|
-
|
316
|
+
result.then do |result|
|
317
|
+
type_cast_pluck_values(result, columns)
|
318
|
+
end
|
254
319
|
end
|
255
320
|
end
|
256
321
|
|
322
|
+
# Same as #pluck, but performs the query asynchronously and returns an
|
323
|
+
# ActiveRecord::Promise.
|
324
|
+
def async_pluck(*column_names)
|
325
|
+
async.pluck(*column_names)
|
326
|
+
end
|
327
|
+
|
257
328
|
# Pick the value(s) from the named column(s) in the current relation.
|
258
329
|
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
259
330
|
# when you have a relation that's already narrowed down to a single row.
|
@@ -270,18 +341,61 @@ module ActiveRecord
|
|
270
341
|
# # => [ 'David', 'david@loudthinking.com' ]
|
271
342
|
def pick(*column_names)
|
272
343
|
if loaded? && all_attributes?(column_names)
|
273
|
-
|
344
|
+
result = records.pick(*column_names)
|
345
|
+
return @async ? Promise::Complete.new(result) : result
|
274
346
|
end
|
275
347
|
|
276
|
-
limit(1).pluck(*column_names).first
|
348
|
+
limit(1).pluck(*column_names).then(&:first)
|
349
|
+
end
|
350
|
+
|
351
|
+
# Same as #pick, but performs the query asynchronously and returns an
|
352
|
+
# ActiveRecord::Promise.
|
353
|
+
def async_pick(*column_names)
|
354
|
+
async.pick(*column_names)
|
277
355
|
end
|
278
356
|
|
279
|
-
#
|
357
|
+
# Returns the base model's ID's for the relation using the table's primary key
|
280
358
|
#
|
281
359
|
# Person.ids # SELECT people.id FROM people
|
282
|
-
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.
|
360
|
+
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
283
361
|
def ids
|
284
|
-
|
362
|
+
primary_key_array = Array(primary_key)
|
363
|
+
|
364
|
+
if loaded?
|
365
|
+
result = records.map do |record|
|
366
|
+
if primary_key_array.one?
|
367
|
+
record._read_attribute(primary_key_array.first)
|
368
|
+
else
|
369
|
+
primary_key_array.map { |column| record._read_attribute(column) }
|
370
|
+
end
|
371
|
+
end
|
372
|
+
return @async ? Promise::Complete.new(result) : result
|
373
|
+
end
|
374
|
+
|
375
|
+
if has_include?(primary_key)
|
376
|
+
relation = apply_join_dependency.group(*primary_key_array)
|
377
|
+
return relation.ids
|
378
|
+
end
|
379
|
+
|
380
|
+
columns = arel_columns(primary_key_array)
|
381
|
+
relation = spawn
|
382
|
+
relation.select_values = columns
|
383
|
+
|
384
|
+
result = if relation.where_clause.contradiction?
|
385
|
+
ActiveRecord::Result.empty
|
386
|
+
else
|
387
|
+
skip_query_cache_if_necessary do
|
388
|
+
klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
result.then { |result| type_cast_pluck_values(result, columns) }
|
393
|
+
end
|
394
|
+
|
395
|
+
# Same as #ids, but performs the query asynchronously and returns an
|
396
|
+
# ActiveRecord::Promise.
|
397
|
+
def async_ids
|
398
|
+
async.ids
|
285
399
|
end
|
286
400
|
|
287
401
|
private
|
@@ -341,6 +455,7 @@ module ActiveRecord
|
|
341
455
|
# Shortcut when limit is zero.
|
342
456
|
return 0 if limit_value == 0
|
343
457
|
|
458
|
+
relation = self
|
344
459
|
query_builder = build_count_subquery(spawn, column_name, distinct)
|
345
460
|
else
|
346
461
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
@@ -355,15 +470,23 @@ module ActiveRecord
|
|
355
470
|
query_builder = relation.arel
|
356
471
|
end
|
357
472
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
473
|
+
query_result = if relation.where_clause.contradiction?
|
474
|
+
ActiveRecord::Result.empty
|
475
|
+
else
|
476
|
+
skip_query_cache_if_necessary do
|
477
|
+
@klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
|
478
|
+
end
|
364
479
|
end
|
365
480
|
|
366
|
-
|
481
|
+
query_result.then do |result|
|
482
|
+
if operation != "count"
|
483
|
+
type = column.try(:type_caster) ||
|
484
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
485
|
+
type = type.subtype if Enum::EnumType === type
|
486
|
+
end
|
487
|
+
|
488
|
+
type_cast_calculated_value(result.cast_values.first, operation, type)
|
489
|
+
end
|
367
490
|
end
|
368
491
|
|
369
492
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
@@ -406,39 +529,40 @@ module ActiveRecord
|
|
406
529
|
relation.group_values = group_fields
|
407
530
|
relation.select_values = select_values
|
408
531
|
|
409
|
-
|
532
|
+
result = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async) }
|
533
|
+
result.then do |calculated_data|
|
534
|
+
if association
|
535
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
536
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
537
|
+
key_records = key_records.index_by(&:id)
|
538
|
+
end
|
410
539
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
540
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
541
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
542
|
+
type_for(col_name) do
|
543
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
544
|
+
end
|
545
|
+
end
|
416
546
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
547
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
548
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
549
|
+
hash[col_name] = row[i]
|
421
550
|
end
|
422
|
-
end
|
423
|
-
|
424
|
-
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
425
|
-
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
426
|
-
hash[col_name] = row[i]
|
427
551
|
end
|
428
|
-
end
|
429
552
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
553
|
+
if operation != "count"
|
554
|
+
type = column.try(:type_caster) ||
|
555
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
556
|
+
type = type.subtype if Enum::EnumType === type
|
557
|
+
end
|
435
558
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
559
|
+
hash_rows.each_with_object({}) do |row, result|
|
560
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
561
|
+
key = key.first if key.size == 1
|
562
|
+
key = key_records[key] if associated
|
440
563
|
|
441
|
-
|
564
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
565
|
+
end
|
442
566
|
end
|
443
567
|
end
|
444
568
|
|
@@ -5,6 +5,23 @@ require "active_support/core_ext/module/delegation"
|
|
5
5
|
|
6
6
|
module ActiveRecord
|
7
7
|
module Delegation # :nodoc:
|
8
|
+
class << self
|
9
|
+
def delegated_classes
|
10
|
+
[
|
11
|
+
ActiveRecord::Relation,
|
12
|
+
ActiveRecord::Associations::CollectionProxy,
|
13
|
+
ActiveRecord::AssociationRelation,
|
14
|
+
ActiveRecord::DisableJoinsAssociationRelation,
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
18
|
+
def uncacheable_methods
|
19
|
+
@uncacheable_methods ||= (
|
20
|
+
delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods
|
21
|
+
).to_set.freeze
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
8
25
|
module DelegateCache # :nodoc:
|
9
26
|
def relation_delegate_class(klass)
|
10
27
|
@relation_delegate_cache[klass]
|
@@ -12,12 +29,7 @@ module ActiveRecord
|
|
12
29
|
|
13
30
|
def initialize_relation_delegate_cache
|
14
31
|
@relation_delegate_cache = cache = {}
|
15
|
-
|
16
|
-
ActiveRecord::Relation,
|
17
|
-
ActiveRecord::Associations::CollectionProxy,
|
18
|
-
ActiveRecord::AssociationRelation,
|
19
|
-
ActiveRecord::DisableJoinsAssociationRelation
|
20
|
-
].each do |klass|
|
32
|
+
Delegation.delegated_classes.each do |klass|
|
21
33
|
delegate = Class.new(klass) {
|
22
34
|
include ClassSpecificRelation
|
23
35
|
}
|
@@ -85,12 +97,12 @@ module ActiveRecord
|
|
85
97
|
# may vary depending on the klass of a relation, so we create a subclass of Relation
|
86
98
|
# for each different klass, and the delegations are compiled into that subclass only.
|
87
99
|
|
88
|
-
delegate :to_xml, :encode_with, :length, :each, :join,
|
100
|
+
delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
|
89
101
|
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
|
90
102
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
91
103
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
92
104
|
|
93
|
-
delegate :primary_key, :connection, to: :klass
|
105
|
+
delegate :primary_key, :connection, :transaction, to: :klass
|
94
106
|
|
95
107
|
module ClassSpecificRelation # :nodoc:
|
96
108
|
extend ActiveSupport::Concern
|
@@ -104,7 +116,9 @@ module ActiveRecord
|
|
104
116
|
private
|
105
117
|
def method_missing(method, *args, &block)
|
106
118
|
if @klass.respond_to?(method)
|
107
|
-
|
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 (
|
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
|
-
|
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 =
|
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.
|
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 =
|
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
|
-
|
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
|
-
|
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
|
614
|
+
if relation.order_values.empty? || relation.has_limit_or_offset?
|
563
615
|
relation.records[-index]
|
564
616
|
else
|
565
|
-
relation.
|
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
|
-
|
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
|
@@ -9,7 +9,14 @@ module ActiveRecord
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def queries
|
12
|
-
|
12
|
+
if associated_table.join_foreign_key.is_a?(Array)
|
13
|
+
id_list = ids
|
14
|
+
id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation)
|
15
|
+
|
16
|
+
id_list.map { |ids_set| associated_table.join_foreign_key.zip(ids_set).to_h }
|
17
|
+
else
|
18
|
+
[ associated_table.join_foreign_key => ids ]
|
19
|
+
end
|
13
20
|
end
|
14
21
|
|
15
22
|
private
|
@@ -25,7 +32,7 @@ module ActiveRecord
|
|
25
32
|
when Array
|
26
33
|
value.map { |v| convert_to_id(v) }
|
27
34
|
else
|
28
|
-
convert_to_id(value)
|
35
|
+
[convert_to_id(value)]
|
29
36
|
end
|
30
37
|
end
|
31
38
|
|
@@ -50,6 +57,8 @@ module ActiveRecord
|
|
50
57
|
end
|
51
58
|
|
52
59
|
def convert_to_id(value)
|
60
|
+
return primary_key.map { |pk| value.public_send(pk) } if primary_key.is_a?(Array)
|
61
|
+
|
53
62
|
if value.respond_to?(primary_key)
|
54
63
|
value.public_send(primary_key)
|
55
64
|
else
|