activerecord 7.0.8.1 → 7.2.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +642 -1925
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- 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 +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- 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 +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
- 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 +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- 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 +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- 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 +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- 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 +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
- 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 +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- 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 +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- 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/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- 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 +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -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 +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- data/lib/active_record/migration/default_strategy.rb +22 -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 +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- 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 +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -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 +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- 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 +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +106 -24
- data/lib/active_record/translation.rb +0 -2
- 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/serialized.rb +1 -3
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +9 -3
- 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 +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -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/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- 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 +59 -17
- 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,13 +215,26 @@ 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
|
-
relation.select_values =
|
237
|
+
relation.select_values = Array(klass.primary_key || table[Arel.star])
|
187
238
|
end
|
188
239
|
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
189
240
|
relation.order_values = [] if group_values.empty?
|
@@ -224,36 +275,62 @@ module ActiveRecord
|
|
224
275
|
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
225
276
|
# # => [2, 3]
|
226
277
|
#
|
278
|
+
# Comment.joins(:person).pluck(:id, person: [:id])
|
279
|
+
# # SELECT comments.id, people.id FROM comments INNER JOIN people on comments.person_id = people.id
|
280
|
+
# # => [[1, 2], [2, 2]]
|
281
|
+
#
|
227
282
|
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
228
283
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
229
284
|
# # => ['0', '27761', '173']
|
230
285
|
#
|
231
286
|
# See also #ids.
|
232
|
-
#
|
233
287
|
def pluck(*column_names)
|
288
|
+
if @none
|
289
|
+
if @async
|
290
|
+
return Promise::Complete.new([])
|
291
|
+
else
|
292
|
+
return []
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
234
296
|
if loaded? && all_attributes?(column_names)
|
235
|
-
|
297
|
+
result = records.pluck(*column_names)
|
298
|
+
if @async
|
299
|
+
return Promise::Complete.new(result)
|
300
|
+
else
|
301
|
+
return result
|
302
|
+
end
|
236
303
|
end
|
237
304
|
|
238
305
|
if has_include?(column_names.first)
|
239
306
|
relation = apply_join_dependency
|
240
307
|
relation.pluck(*column_names)
|
241
308
|
else
|
242
|
-
klass.disallow_raw_sql!(column_names)
|
243
|
-
columns = arel_columns(column_names)
|
309
|
+
klass.disallow_raw_sql!(flattened_args(column_names))
|
244
310
|
relation = spawn
|
311
|
+
columns = relation.arel_columns(column_names)
|
245
312
|
relation.select_values = columns
|
246
313
|
result = skip_query_cache_if_necessary do
|
247
314
|
if where_clause.contradiction?
|
248
|
-
ActiveRecord::Result.empty
|
315
|
+
ActiveRecord::Result.empty(async: @async)
|
249
316
|
else
|
250
|
-
klass.
|
317
|
+
klass.with_connection do |c|
|
318
|
+
c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
|
319
|
+
end
|
251
320
|
end
|
252
321
|
end
|
253
|
-
|
322
|
+
result.then do |result|
|
323
|
+
type_cast_pluck_values(result, columns)
|
324
|
+
end
|
254
325
|
end
|
255
326
|
end
|
256
327
|
|
328
|
+
# Same as #pluck, but performs the query asynchronously and returns an
|
329
|
+
# ActiveRecord::Promise.
|
330
|
+
def async_pluck(*column_names)
|
331
|
+
async.pluck(*column_names)
|
332
|
+
end
|
333
|
+
|
257
334
|
# Pick the value(s) from the named column(s) in the current relation.
|
258
335
|
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
259
336
|
# when you have a relation that's already narrowed down to a single row.
|
@@ -270,18 +347,63 @@ module ActiveRecord
|
|
270
347
|
# # => [ 'David', 'david@loudthinking.com' ]
|
271
348
|
def pick(*column_names)
|
272
349
|
if loaded? && all_attributes?(column_names)
|
273
|
-
|
350
|
+
result = records.pick(*column_names)
|
351
|
+
return @async ? Promise::Complete.new(result) : result
|
274
352
|
end
|
275
353
|
|
276
|
-
limit(1).pluck(*column_names).first
|
354
|
+
limit(1).pluck(*column_names).then(&:first)
|
277
355
|
end
|
278
356
|
|
279
|
-
#
|
357
|
+
# Same as #pick, but performs the query asynchronously and returns an
|
358
|
+
# ActiveRecord::Promise.
|
359
|
+
def async_pick(*column_names)
|
360
|
+
async.pick(*column_names)
|
361
|
+
end
|
362
|
+
|
363
|
+
# Returns the base model's ID's for the relation using the table's primary key
|
280
364
|
#
|
281
365
|
# Person.ids # SELECT people.id FROM people
|
282
|
-
# Person.joins(:
|
366
|
+
# Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
283
367
|
def ids
|
284
|
-
|
368
|
+
primary_key_array = Array(primary_key)
|
369
|
+
|
370
|
+
if loaded?
|
371
|
+
result = records.map do |record|
|
372
|
+
if primary_key_array.one?
|
373
|
+
record._read_attribute(primary_key_array.first)
|
374
|
+
else
|
375
|
+
primary_key_array.map { |column| record._read_attribute(column) }
|
376
|
+
end
|
377
|
+
end
|
378
|
+
return @async ? Promise::Complete.new(result) : result
|
379
|
+
end
|
380
|
+
|
381
|
+
if has_include?(primary_key)
|
382
|
+
relation = apply_join_dependency.group(*primary_key_array)
|
383
|
+
return relation.ids
|
384
|
+
end
|
385
|
+
|
386
|
+
columns = arel_columns(primary_key_array)
|
387
|
+
relation = spawn
|
388
|
+
relation.select_values = columns
|
389
|
+
|
390
|
+
result = if relation.where_clause.contradiction?
|
391
|
+
ActiveRecord::Result.empty
|
392
|
+
else
|
393
|
+
skip_query_cache_if_necessary do
|
394
|
+
klass.with_connection do |c|
|
395
|
+
c.select_all(relation, "#{klass.name} Ids", async: @async)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
result.then { |result| type_cast_pluck_values(result, columns) }
|
401
|
+
end
|
402
|
+
|
403
|
+
# Same as #ids, but performs the query asynchronously and returns an
|
404
|
+
# ActiveRecord::Promise.
|
405
|
+
def async_ids
|
406
|
+
async.ids
|
285
407
|
end
|
286
408
|
|
287
409
|
private
|
@@ -328,7 +450,7 @@ module ActiveRecord
|
|
328
450
|
return column_name if Arel::Expressions === column_name
|
329
451
|
|
330
452
|
arel_column(column_name.to_s) do |name|
|
331
|
-
|
453
|
+
column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
|
332
454
|
end
|
333
455
|
end
|
334
456
|
|
@@ -337,10 +459,11 @@ module ActiveRecord
|
|
337
459
|
end
|
338
460
|
|
339
461
|
def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
|
340
|
-
if operation
|
462
|
+
if build_count_subquery?(operation, column_name, distinct)
|
341
463
|
# Shortcut when limit is zero.
|
342
464
|
return 0 if limit_value == 0
|
343
465
|
|
466
|
+
relation = self
|
344
467
|
query_builder = build_count_subquery(spawn, column_name, distinct)
|
345
468
|
else
|
346
469
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
@@ -355,15 +478,25 @@ module ActiveRecord
|
|
355
478
|
query_builder = relation.arel
|
356
479
|
end
|
357
480
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
481
|
+
query_result = if relation.where_clause.contradiction?
|
482
|
+
ActiveRecord::Result.empty
|
483
|
+
else
|
484
|
+
skip_query_cache_if_necessary do
|
485
|
+
@klass.with_connection do |c|
|
486
|
+
c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
|
487
|
+
end
|
488
|
+
end
|
364
489
|
end
|
365
490
|
|
366
|
-
|
491
|
+
query_result.then do |result|
|
492
|
+
if operation != "count"
|
493
|
+
type = column.try(:type_caster) ||
|
494
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
495
|
+
type = type.subtype if Enum::EnumType === type
|
496
|
+
end
|
497
|
+
|
498
|
+
type_cast_calculated_value(result.cast_values.first, operation, type)
|
499
|
+
end
|
367
500
|
end
|
368
501
|
|
369
502
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
@@ -377,68 +510,74 @@ module ActiveRecord
|
|
377
510
|
end
|
378
511
|
group_fields = arel_columns(group_fields)
|
379
512
|
|
380
|
-
|
513
|
+
@klass.with_connection do |connection|
|
514
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
381
515
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
516
|
+
group_aliases = group_fields.map { |field|
|
517
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
518
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
519
|
+
}
|
520
|
+
group_columns = group_aliases.zip(group_fields)
|
387
521
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
522
|
+
column = aggregate_column(column_name)
|
523
|
+
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
524
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
525
|
+
select_value.as(adapter_class.quote_column_name(column_alias))
|
392
526
|
|
393
|
-
|
394
|
-
|
527
|
+
select_values = [select_value]
|
528
|
+
select_values += self.select_values unless having_clause.empty?
|
395
529
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
530
|
+
select_values.concat group_columns.map { |aliaz, field|
|
531
|
+
aliaz = adapter_class.quote_column_name(aliaz)
|
532
|
+
if field.respond_to?(:as)
|
533
|
+
field.as(aliaz)
|
534
|
+
else
|
535
|
+
"#{field} AS #{aliaz}"
|
536
|
+
end
|
537
|
+
}
|
404
538
|
|
405
|
-
|
406
|
-
|
407
|
-
|
539
|
+
relation = except(:group).distinct!(false)
|
540
|
+
relation.group_values = group_fields
|
541
|
+
relation.select_values = select_values
|
408
542
|
|
409
|
-
|
543
|
+
result = skip_query_cache_if_necessary do
|
544
|
+
connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async)
|
545
|
+
end
|
410
546
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
547
|
+
result.then do |calculated_data|
|
548
|
+
if association
|
549
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
550
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
551
|
+
key_records = key_records.index_by(&:id)
|
552
|
+
end
|
416
553
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
554
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
555
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
556
|
+
type_for(col_name) do
|
557
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
558
|
+
end
|
421
559
|
end
|
422
|
-
end
|
423
560
|
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
561
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
562
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
563
|
+
hash[col_name] = row[i]
|
564
|
+
end
|
565
|
+
end
|
429
566
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
567
|
+
if operation != "count"
|
568
|
+
type = column.try(:type_caster) ||
|
569
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
570
|
+
type = type.subtype if Enum::EnumType === type
|
571
|
+
end
|
435
572
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
573
|
+
hash_rows.each_with_object({}) do |row, result|
|
574
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
575
|
+
key = key.first if key.size == 1
|
576
|
+
key = key_records[key] if associated
|
440
577
|
|
441
|
-
|
578
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
579
|
+
end
|
580
|
+
end
|
442
581
|
end
|
443
582
|
end
|
444
583
|
|
@@ -465,7 +604,7 @@ module ActiveRecord
|
|
465
604
|
klass.attribute_types.fetch(name = result.columns[i]) do
|
466
605
|
join_dependencies ||= build_join_dependencies
|
467
606
|
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
468
|
-
result.column_types[
|
607
|
+
result.column_types[i] || Type.default_value
|
469
608
|
end
|
470
609
|
end
|
471
610
|
end
|
@@ -493,12 +632,30 @@ module ActiveRecord
|
|
493
632
|
def select_for_count
|
494
633
|
if select_values.present?
|
495
634
|
return select_values.first if select_values.one?
|
496
|
-
|
635
|
+
|
636
|
+
select_values.map do |field|
|
637
|
+
column = arel_column(field.to_s) do |attr_name|
|
638
|
+
Arel.sql(attr_name)
|
639
|
+
end
|
640
|
+
|
641
|
+
if column.is_a?(Arel::Nodes::SqlLiteral)
|
642
|
+
column
|
643
|
+
else
|
644
|
+
"#{adapter_class.quote_table_name(column.relation.name)}.#{adapter_class.quote_column_name(column.name)}"
|
645
|
+
end
|
646
|
+
end.join(", ")
|
497
647
|
else
|
498
648
|
:all
|
499
649
|
end
|
500
650
|
end
|
501
651
|
|
652
|
+
def build_count_subquery?(operation, column_name, distinct)
|
653
|
+
# SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
|
654
|
+
# multiple columns, so we need to use subquery for this.
|
655
|
+
operation == "count" &&
|
656
|
+
(((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
|
657
|
+
end
|
658
|
+
|
502
659
|
def build_count_subquery(relation, column_name, distinct)
|
503
660
|
if column_name == :all
|
504
661
|
column_alias = Arel.star
|
@@ -508,7 +665,7 @@ module ActiveRecord
|
|
508
665
|
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
509
666
|
end
|
510
667
|
|
511
|
-
subquery_alias = Arel.sql("subquery_for_count")
|
668
|
+
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
512
669
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
513
670
|
|
514
671
|
relation.build_subquery(subquery_alias, select_value)
|
@@ -1,10 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "mutex_m"
|
4
3
|
require "active_support/core_ext/module/delegation"
|
5
4
|
|
6
5
|
module ActiveRecord
|
7
6
|
module Delegation # :nodoc:
|
7
|
+
class << self
|
8
|
+
def delegated_classes
|
9
|
+
[
|
10
|
+
ActiveRecord::Relation,
|
11
|
+
ActiveRecord::Associations::CollectionProxy,
|
12
|
+
ActiveRecord::AssociationRelation,
|
13
|
+
ActiveRecord::DisableJoinsAssociationRelation,
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
def uncacheable_methods
|
18
|
+
@uncacheable_methods ||= (
|
19
|
+
delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods
|
20
|
+
).to_set.freeze
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
8
24
|
module DelegateCache # :nodoc:
|
9
25
|
def relation_delegate_class(klass)
|
10
26
|
@relation_delegate_cache[klass]
|
@@ -12,12 +28,7 @@ module ActiveRecord
|
|
12
28
|
|
13
29
|
def initialize_relation_delegate_cache
|
14
30
|
@relation_delegate_cache = cache = {}
|
15
|
-
|
16
|
-
ActiveRecord::Relation,
|
17
|
-
ActiveRecord::Associations::CollectionProxy,
|
18
|
-
ActiveRecord::AssociationRelation,
|
19
|
-
ActiveRecord::DisableJoinsAssociationRelation
|
20
|
-
].each do |klass|
|
31
|
+
Delegation.delegated_classes.each do |klass|
|
21
32
|
delegate = Class.new(klass) {
|
22
33
|
include ClassSpecificRelation
|
23
34
|
}
|
@@ -55,23 +66,22 @@ module ActiveRecord
|
|
55
66
|
end
|
56
67
|
|
57
68
|
class GeneratedRelationMethods < Module # :nodoc:
|
58
|
-
|
69
|
+
MUTEX = Mutex.new
|
59
70
|
|
60
71
|
def generate_method(method)
|
61
|
-
synchronize do
|
72
|
+
MUTEX.synchronize do
|
62
73
|
return if method_defined?(method)
|
63
74
|
|
64
|
-
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) &&
|
75
|
+
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
|
65
76
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
66
77
|
def #{method}(...)
|
67
78
|
scoping { klass.#{method}(...) }
|
68
79
|
end
|
69
80
|
RUBY
|
70
81
|
else
|
71
|
-
define_method(method) do |*args, &block|
|
72
|
-
scoping { klass.public_send(method, *args, &block) }
|
82
|
+
define_method(method) do |*args, **kwargs, &block|
|
83
|
+
scoping { klass.public_send(method, *args, **kwargs, &block) }
|
73
84
|
end
|
74
|
-
ruby2_keywords(method)
|
75
85
|
end
|
76
86
|
end
|
77
87
|
end
|
@@ -85,12 +95,12 @@ module ActiveRecord
|
|
85
95
|
# may vary depending on the klass of a relation, so we create a subclass of Relation
|
86
96
|
# for each different klass, and the delegations are compiled into that subclass only.
|
87
97
|
|
88
|
-
delegate :to_xml, :encode_with, :length, :each, :join,
|
98
|
+
delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
|
89
99
|
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
|
90
100
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
91
101
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
92
102
|
|
93
|
-
delegate :primary_key, :connection, to: :klass
|
103
|
+
delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
|
94
104
|
|
95
105
|
module ClassSpecificRelation # :nodoc:
|
96
106
|
extend ActiveSupport::Concern
|
@@ -102,15 +112,16 @@ module ActiveRecord
|
|
102
112
|
end
|
103
113
|
|
104
114
|
private
|
105
|
-
def method_missing(method,
|
115
|
+
def method_missing(method, ...)
|
106
116
|
if @klass.respond_to?(method)
|
107
|
-
|
108
|
-
|
117
|
+
unless Delegation.uncacheable_methods.include?(method)
|
118
|
+
@klass.generate_relation_method(method)
|
119
|
+
end
|
120
|
+
scoping { @klass.public_send(method, ...) }
|
109
121
|
else
|
110
122
|
super
|
111
123
|
end
|
112
124
|
end
|
113
|
-
ruby2_keywords(:method_missing)
|
114
125
|
end
|
115
126
|
|
116
127
|
module ClassMethods # :nodoc:
|