activerecord 6.1.7 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1516 -1019
- data/MIT-LICENSE +1 -1
- data/README.rdoc +17 -18
- data/lib/active_record/aggregations.rb +17 -14
- data/lib/active_record/association_relation.rb +1 -11
- data/lib/active_record/associations/association.rb +50 -19
- data/lib/active_record/associations/association_scope.rb +17 -12
- data/lib/active_record/associations/belongs_to_association.rb +28 -9
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +11 -5
- data/lib/active_record/associations/builder/belongs_to.rb +40 -14
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/has_many.rb +3 -2
- data/lib/active_record/associations/builder/has_one.rb +2 -1
- data/lib/active_record/associations/builder/singular_association.rb +6 -2
- data/lib/active_record/associations/collection_association.rb +35 -31
- data/lib/active_record/associations/collection_proxy.rb +30 -15
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +28 -18
- data/lib/active_record/associations/has_many_through_association.rb +12 -7
- data/lib/active_record/associations/has_one_association.rb +20 -10
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +26 -16
- data/lib/active_record/associations/preloader/association.rb +207 -52
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +147 -0
- data/lib/active_record/associations/preloader/through_association.rb +50 -14
- data/lib/active_record/associations/preloader.rb +50 -121
- data/lib/active_record/associations/singular_association.rb +9 -3
- data/lib/active_record/associations/through_association.rb +25 -14
- data/lib/active_record/associations.rb +423 -289
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
- data/lib/active_record/attribute_methods/dirty.rb +61 -14
- data/lib/active_record/attribute_methods/primary_key.rb +78 -26
- data/lib/active_record/attribute_methods/query.rb +31 -19
- data/lib/active_record/attribute_methods/read.rb +25 -10
- data/lib/active_record/attribute_methods/serialization.rb +194 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
- data/lib/active_record/attribute_methods/write.rb +10 -13
- data/lib/active_record/attribute_methods.rb +121 -40
- data/lib/active_record/attributes.rb +27 -38
- data/lib/active_record/autosave_association.rb +61 -30
- data/lib/active_record/base.rb +25 -2
- data/lib/active_record/callbacks.rb +18 -34
- 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 -46
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -51
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -136
- data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
- data/lib/active_record/connection_adapters/abstract_adapter.rb +622 -149
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
- data/lib/active_record/connection_adapters/column.rb +13 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
- data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
- data/lib/active_record/connection_adapters/pool_config.rb +20 -11
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +18 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -52
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +381 -69
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +492 -230
- data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +65 -53
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +9 -6
- data/lib/active_record/connection_handling.rb +107 -136
- data/lib/active_record/core.rb +194 -224
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
- data/lib/active_record/database_configurations/database_config.rb +21 -12
- data/lib/active_record/database_configurations/hash_config.rb +84 -16
- data/lib/active_record/database_configurations/url_config.rb +18 -12
- data/lib/active_record/database_configurations.rb +95 -59
- data/lib/active_record/delegated_type.rb +61 -15
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +224 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +155 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +172 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_serializer.rb +92 -0
- data/lib/active_record/encryption/null_encryptor.rb +21 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
- data/lib/active_record/encryption/scheme.rb +96 -0
- data/lib/active_record/encryption.rb +56 -0
- data/lib/active_record/enum.rb +156 -62
- data/lib/active_record/errors.rb +171 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +15 -1
- 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 +70 -14
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +131 -86
- data/lib/active_record/future_result.rb +164 -0
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +81 -29
- data/lib/active_record/insert_all.rb +133 -20
- data/lib/active_record/integration.rb +11 -10
- data/lib/active_record/internal_metadata.rb +117 -33
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +36 -21
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +52 -19
- 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 +10 -10
- data/lib/active_record/middleware/database_selector.rb +23 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +108 -13
- data/lib/active_record/migration/compatibility.rb +221 -48
- 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/join_table.rb +1 -1
- data/lib/active_record/migration.rb +355 -171
- data/lib/active_record/model_schema.rb +116 -97
- data/lib/active_record/nested_attributes.rb +36 -15
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +159 -0
- data/lib/active_record/persistence.rb +405 -85
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +174 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +29 -6
- data/lib/active_record/railtie.rb +219 -43
- data/lib/active_record/railties/controller_runtime.rb +13 -9
- data/lib/active_record/railties/databases.rake +185 -249
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +41 -3
- data/lib/active_record/reflection.rb +229 -80
- data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
- data/lib/active_record/relation/batches.rb +192 -63
- data/lib/active_record/relation/calculations.rb +211 -90
- data/lib/active_record/relation/delegation.rb +27 -13
- data/lib/active_record/relation/finder_methods.rb +108 -51
- data/lib/active_record/relation/merger.rb +22 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- 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 +27 -20
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +654 -127
- data/lib/active_record/relation/record_fetch_warning.rb +7 -9
- data/lib/active_record/relation/spawn_methods.rb +20 -3
- data/lib/active_record/relation/where_clause.rb +10 -19
- data/lib/active_record/relation.rb +262 -120
- data/lib/active_record/result.rb +37 -11
- data/lib/active_record/runtime_registry.rb +18 -13
- data/lib/active_record/sanitization.rb +65 -20
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +73 -24
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +72 -15
- data/lib/active_record/scoping/named.rb +5 -13
- data/lib/active_record/scoping.rb +65 -34
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +6 -1
- data/lib/active_record/signed_id.rb +10 -8
- data/lib/active_record/store.rb +10 -10
- data/lib/active_record/suppressor.rb +13 -15
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +225 -136
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
- data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +116 -96
- data/lib/active_record/timestamp.rb +28 -17
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +48 -27
- data/lib/active_record/translation.rb +3 -3
- data/lib/active_record/type/adapter_specific_registry.rb +32 -14
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +9 -5
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +4 -4
- 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 +51 -6
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +335 -32
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/crud.rb +28 -22
- data/lib/arel/delete_manager.rb +18 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/and.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/casted.rb +1 -1
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/homogeneous_in.rb +0 -8
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +5 -0
- data/lib/arel/predications.rb +13 -3
- data/lib/arel/select_manager.rb +10 -4
- data/lib/arel/table.rb +9 -6
- data/lib/arel/tree_manager.rb +0 -12
- data/lib/arel/update_manager.rb +18 -4
- data/lib/arel/visitors/dot.rb +80 -90
- data/lib/arel/visitors/mysql.rb +16 -3
- data/lib/arel/visitors/postgresql.rb +0 -10
- data/lib/arel/visitors/to_sql.rb +139 -19
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +18 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -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
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +92 -13
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -67
@@ -3,7 +3,49 @@
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
|
+
# = Active Record \Calculations
|
6
7
|
module Calculations
|
8
|
+
class ColumnAliasTracker # :nodoc:
|
9
|
+
def initialize(connection)
|
10
|
+
@connection = connection
|
11
|
+
@aliases = Hash.new(0)
|
12
|
+
end
|
13
|
+
|
14
|
+
def alias_for(field)
|
15
|
+
aliased_name = column_alias_for(field)
|
16
|
+
|
17
|
+
if @aliases[aliased_name] == 0
|
18
|
+
@aliases[aliased_name] = 1
|
19
|
+
aliased_name
|
20
|
+
else
|
21
|
+
# Update the count
|
22
|
+
count = @aliases[aliased_name] += 1
|
23
|
+
"#{truncate(aliased_name)}_#{count}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
# Converts the given field to the value that the database adapter returns as
|
29
|
+
# a usable column name:
|
30
|
+
#
|
31
|
+
# column_alias_for("users.id") # => "users_id"
|
32
|
+
# column_alias_for("sum(id)") # => "sum_id"
|
33
|
+
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
34
|
+
# column_alias_for("count(*)") # => "count_all"
|
35
|
+
def column_alias_for(field)
|
36
|
+
column_alias = +field
|
37
|
+
column_alias.gsub!(/\*/, "all")
|
38
|
+
column_alias.gsub!(/\W+/, " ")
|
39
|
+
column_alias.strip!
|
40
|
+
column_alias.gsub!(/ +/, "_")
|
41
|
+
@connection.table_alias_for(column_alias)
|
42
|
+
end
|
43
|
+
|
44
|
+
def truncate(name)
|
45
|
+
name.slice(0, @connection.table_alias_length - 2)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
7
49
|
# Count the records.
|
8
50
|
#
|
9
51
|
# Person.count
|
@@ -30,8 +72,7 @@ module ActiveRecord
|
|
30
72
|
# of each key would be the #count.
|
31
73
|
#
|
32
74
|
# Article.group(:status, :category).count
|
33
|
-
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
|
34
|
-
# ["published", "business"]=>0, ["published", "technology"]=>2}
|
75
|
+
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
35
76
|
#
|
36
77
|
# If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
37
78
|
#
|
@@ -52,6 +93,11 @@ module ActiveRecord
|
|
52
93
|
end
|
53
94
|
end
|
54
95
|
|
96
|
+
# Same as <tt>#count</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
|
97
|
+
def async_count(column_name = nil)
|
98
|
+
async.count(column_name)
|
99
|
+
end
|
100
|
+
|
55
101
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
56
102
|
# no row. See #calculate for examples with options.
|
57
103
|
#
|
@@ -60,6 +106,11 @@ module ActiveRecord
|
|
60
106
|
calculate(:average, column_name)
|
61
107
|
end
|
62
108
|
|
109
|
+
# Same as <tt>#average</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
|
110
|
+
def async_average(column_name)
|
111
|
+
async.average(column_name)
|
112
|
+
end
|
113
|
+
|
63
114
|
# Calculates the minimum value on a given column. The value is returned
|
64
115
|
# with the same data type of the column, or +nil+ if there's no row. See
|
65
116
|
# #calculate for examples with options.
|
@@ -69,6 +120,11 @@ module ActiveRecord
|
|
69
120
|
calculate(:minimum, column_name)
|
70
121
|
end
|
71
122
|
|
123
|
+
# Same as <tt>#minimum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
|
124
|
+
def async_minimum(column_name)
|
125
|
+
async.minimum(column_name)
|
126
|
+
end
|
127
|
+
|
72
128
|
# Calculates the maximum value on a given column. The value is returned
|
73
129
|
# with the same data type of the column, or +nil+ if there's no row. See
|
74
130
|
# #calculate for examples with options.
|
@@ -78,23 +134,29 @@ module ActiveRecord
|
|
78
134
|
calculate(:maximum, column_name)
|
79
135
|
end
|
80
136
|
|
137
|
+
# Same as <tt>#maximum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
|
138
|
+
def async_maximum(column_name)
|
139
|
+
async.maximum(column_name)
|
140
|
+
end
|
141
|
+
|
81
142
|
# Calculates the sum of values on a given column. The value is returned
|
82
143
|
# with the same data type of the column, +0+ if there's no row. See
|
83
144
|
# #calculate for examples with options.
|
84
145
|
#
|
85
146
|
# Person.sum(:age) # => 4562
|
86
|
-
def sum(
|
147
|
+
def sum(initial_value_or_column = 0, &block)
|
87
148
|
if block_given?
|
88
|
-
|
89
|
-
raise ArgumentError, "Column name argument is not supported when a block is passed."
|
90
|
-
end
|
91
|
-
|
92
|
-
super()
|
149
|
+
map(&block).sum(initial_value_or_column)
|
93
150
|
else
|
94
|
-
calculate(:sum,
|
151
|
+
calculate(:sum, initial_value_or_column)
|
95
152
|
end
|
96
153
|
end
|
97
154
|
|
155
|
+
# Same as <tt>#sum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
|
156
|
+
def async_sum(identity_or_column = nil)
|
157
|
+
async.sum(identity_or_column)
|
158
|
+
end
|
159
|
+
|
98
160
|
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
99
161
|
# #minimum, and #maximum have been added as shortcuts.
|
100
162
|
#
|
@@ -127,10 +189,23 @@ module ActiveRecord
|
|
127
189
|
# ...
|
128
190
|
# end
|
129
191
|
def calculate(operation, column_name)
|
192
|
+
operation = operation.to_s.downcase
|
193
|
+
|
194
|
+
if @none
|
195
|
+
case operation
|
196
|
+
when "count", "sum"
|
197
|
+
result = group_values.any? ? Hash.new : 0
|
198
|
+
return @async ? Promise::Complete.new(result) : result
|
199
|
+
when "average", "minimum", "maximum"
|
200
|
+
result = group_values.any? ? Hash.new : nil
|
201
|
+
return @async ? Promise::Complete.new(result) : result
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
130
205
|
if has_include?(column_name)
|
131
206
|
relation = apply_join_dependency
|
132
207
|
|
133
|
-
if operation
|
208
|
+
if operation == "count"
|
134
209
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
135
210
|
relation.distinct!
|
136
211
|
relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
@@ -146,7 +221,7 @@ module ActiveRecord
|
|
146
221
|
end
|
147
222
|
|
148
223
|
# Use #pluck as a shortcut to select one or more attributes without
|
149
|
-
# loading
|
224
|
+
# loading an entire record object per row.
|
150
225
|
#
|
151
226
|
# Person.pluck(:name)
|
152
227
|
#
|
@@ -179,31 +254,44 @@ module ActiveRecord
|
|
179
254
|
# # => ['0', '27761', '173']
|
180
255
|
#
|
181
256
|
# See also #ids.
|
182
|
-
#
|
183
257
|
def pluck(*column_names)
|
258
|
+
return [] if @none
|
259
|
+
|
184
260
|
if loaded? && all_attributes?(column_names)
|
185
|
-
|
261
|
+
result = records.pluck(*column_names)
|
262
|
+
if @async
|
263
|
+
return Promise::Complete.new(result)
|
264
|
+
else
|
265
|
+
return result
|
266
|
+
end
|
186
267
|
end
|
187
268
|
|
188
269
|
if has_include?(column_names.first)
|
189
270
|
relation = apply_join_dependency
|
190
271
|
relation.pluck(*column_names)
|
191
272
|
else
|
192
|
-
klass.disallow_raw_sql!(column_names)
|
273
|
+
klass.disallow_raw_sql!(column_names.flatten)
|
193
274
|
columns = arel_columns(column_names)
|
194
275
|
relation = spawn
|
195
276
|
relation.select_values = columns
|
196
277
|
result = skip_query_cache_if_necessary do
|
197
278
|
if where_clause.contradiction?
|
198
|
-
ActiveRecord::Result.
|
279
|
+
ActiveRecord::Result.empty(async: @async)
|
199
280
|
else
|
200
|
-
klass.connection.select_all(relation.arel,
|
281
|
+
klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
|
201
282
|
end
|
202
283
|
end
|
203
|
-
|
284
|
+
result.then do |result|
|
285
|
+
type_cast_pluck_values(result, columns)
|
286
|
+
end
|
204
287
|
end
|
205
288
|
end
|
206
289
|
|
290
|
+
# Same as <tt>#pluck</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
|
291
|
+
def async_pluck(*column_names)
|
292
|
+
async.pluck(*column_names)
|
293
|
+
end
|
294
|
+
|
207
295
|
# Pick the value(s) from the named column(s) in the current relation.
|
208
296
|
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
209
297
|
# when you have a relation that's already narrowed down to a single row.
|
@@ -220,18 +308,59 @@ module ActiveRecord
|
|
220
308
|
# # => [ 'David', 'david@loudthinking.com' ]
|
221
309
|
def pick(*column_names)
|
222
310
|
if loaded? && all_attributes?(column_names)
|
223
|
-
|
311
|
+
result = records.pick(*column_names)
|
312
|
+
return @async ? Promise::Complete.new(result) : result
|
224
313
|
end
|
225
314
|
|
226
|
-
limit(1).pluck(*column_names).first
|
315
|
+
limit(1).pluck(*column_names).then(&:first)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Same as <tt>#pick</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
|
319
|
+
def async_pick(*column_names)
|
320
|
+
async.pick(*column_names)
|
227
321
|
end
|
228
322
|
|
229
|
-
#
|
323
|
+
# Returns the base model's ID's for the relation using the table's primary key
|
230
324
|
#
|
231
325
|
# Person.ids # SELECT people.id FROM people
|
232
|
-
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.
|
326
|
+
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
233
327
|
def ids
|
234
|
-
|
328
|
+
primary_key_array = Array(primary_key)
|
329
|
+
|
330
|
+
if loaded?
|
331
|
+
result = records.map do |record|
|
332
|
+
if primary_key_array.one?
|
333
|
+
record._read_attribute(primary_key_array.first)
|
334
|
+
else
|
335
|
+
primary_key_array.map { |column| record._read_attribute(column) }
|
336
|
+
end
|
337
|
+
end
|
338
|
+
return @async ? Promise::Complete.new(result) : result
|
339
|
+
end
|
340
|
+
|
341
|
+
if has_include?(primary_key)
|
342
|
+
relation = apply_join_dependency.group(*primary_key_array)
|
343
|
+
return relation.ids
|
344
|
+
end
|
345
|
+
|
346
|
+
columns = arel_columns(primary_key_array)
|
347
|
+
relation = spawn
|
348
|
+
relation.select_values = columns
|
349
|
+
|
350
|
+
result = if relation.where_clause.contradiction?
|
351
|
+
ActiveRecord::Result.empty
|
352
|
+
else
|
353
|
+
skip_query_cache_if_necessary do
|
354
|
+
klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
result.then { |result| type_cast_pluck_values(result, columns) }
|
359
|
+
end
|
360
|
+
|
361
|
+
# Same as <tt>#ids</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
|
362
|
+
def async_ids
|
363
|
+
async.ids
|
235
364
|
end
|
236
365
|
|
237
366
|
private
|
@@ -286,11 +415,12 @@ module ActiveRecord
|
|
286
415
|
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
287
416
|
end
|
288
417
|
|
289
|
-
def execute_simple_calculation(operation, column_name, distinct)
|
418
|
+
def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
|
290
419
|
if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
|
291
420
|
# Shortcut when limit is zero.
|
292
421
|
return 0 if limit_value == 0
|
293
422
|
|
423
|
+
relation = self
|
294
424
|
query_builder = build_count_subquery(spawn, column_name, distinct)
|
295
425
|
else
|
296
426
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
@@ -305,29 +435,29 @@ module ActiveRecord
|
|
305
435
|
query_builder = relation.arel
|
306
436
|
end
|
307
437
|
|
308
|
-
|
438
|
+
query_result = if relation.where_clause.contradiction?
|
439
|
+
ActiveRecord::Result.empty
|
440
|
+
else
|
441
|
+
skip_query_cache_if_necessary do
|
442
|
+
@klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
|
443
|
+
end
|
444
|
+
end
|
309
445
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
446
|
+
query_result.then do |result|
|
447
|
+
if operation != "count"
|
448
|
+
type = column.try(:type_caster) ||
|
449
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
450
|
+
type = type.subtype if Enum::EnumType === type
|
451
|
+
end
|
452
|
+
|
453
|
+
type_cast_calculated_value(result.cast_values.first, operation, type)
|
315
454
|
end
|
316
455
|
end
|
317
456
|
|
318
|
-
def execute_grouped_calculation(operation, column_name, distinct)
|
457
|
+
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
319
458
|
group_fields = group_values
|
320
459
|
group_fields = group_fields.uniq if group_fields.size > 1
|
321
460
|
|
322
|
-
unless group_fields == group_values
|
323
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
324
|
-
`#{operation}` with group by duplicated fields does no longer affect to result in Rails 7.0.
|
325
|
-
To migrate to Rails 7.0's behavior, use `uniq!(:group)` to deduplicate group fields
|
326
|
-
(`#{klass.name&.tableize || klass.table_name}.uniq!(:group).#{operation}(#{column_name.inspect})`).
|
327
|
-
MSG
|
328
|
-
group_fields = group_values
|
329
|
-
end
|
330
|
-
|
331
461
|
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
332
462
|
association = klass._reflect_on_association(group_fields.first)
|
333
463
|
associated = association && association.belongs_to? # only count belongs_to associations
|
@@ -335,21 +465,24 @@ module ActiveRecord
|
|
335
465
|
end
|
336
466
|
group_fields = arel_columns(group_fields)
|
337
467
|
|
468
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
469
|
+
|
338
470
|
group_aliases = group_fields.map { |field|
|
339
471
|
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
340
|
-
|
472
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
341
473
|
}
|
342
474
|
group_columns = group_aliases.zip(group_fields)
|
343
475
|
|
344
476
|
column = aggregate_column(column_name)
|
345
|
-
column_alias =
|
477
|
+
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
346
478
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
347
|
-
select_value.as(column_alias)
|
479
|
+
select_value.as(connection.quote_column_name(column_alias))
|
348
480
|
|
349
481
|
select_values = [select_value]
|
350
482
|
select_values += self.select_values unless having_clause.empty?
|
351
483
|
|
352
484
|
select_values.concat group_columns.map { |aliaz, field|
|
485
|
+
aliaz = connection.quote_column_name(aliaz)
|
353
486
|
if field.respond_to?(:as)
|
354
487
|
field.as(aliaz)
|
355
488
|
else
|
@@ -361,60 +494,43 @@ module ActiveRecord
|
|
361
494
|
relation.group_values = group_fields
|
362
495
|
relation.select_values = select_values
|
363
496
|
|
364
|
-
|
497
|
+
result = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async) }
|
498
|
+
result.then do |calculated_data|
|
499
|
+
if association
|
500
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
501
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
502
|
+
key_records = key_records.index_by(&:id)
|
503
|
+
end
|
365
504
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
505
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
506
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
507
|
+
type_for(col_name) do
|
508
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
509
|
+
end
|
510
|
+
end
|
371
511
|
|
372
|
-
|
373
|
-
|
374
|
-
|
512
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
513
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
514
|
+
hash[col_name] = row[i]
|
515
|
+
end
|
375
516
|
end
|
376
|
-
end
|
377
517
|
|
378
|
-
|
379
|
-
|
380
|
-
|
518
|
+
if operation != "count"
|
519
|
+
type = column.try(:type_caster) ||
|
520
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
521
|
+
type = type.subtype if Enum::EnumType === type
|
381
522
|
end
|
382
|
-
end
|
383
523
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
result[key] = type_cast_calculated_value(row[column_alias], operation) do |value|
|
391
|
-
unless type
|
392
|
-
type = column.try(:type_caster) ||
|
393
|
-
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
394
|
-
type = type.subtype if Enum::EnumType === type
|
395
|
-
end
|
396
|
-
type.deserialize(value)
|
524
|
+
hash_rows.each_with_object({}) do |row, result|
|
525
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
526
|
+
key = key.first if key.size == 1
|
527
|
+
key = key_records[key] if associated
|
528
|
+
|
529
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
397
530
|
end
|
398
531
|
end
|
399
532
|
end
|
400
533
|
|
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
534
|
def type_for(field, &block)
|
419
535
|
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
420
536
|
@klass.type_for_attribute(field_name, &block)
|
@@ -445,16 +561,21 @@ module ActiveRecord
|
|
445
561
|
result.cast_values(cast_types)
|
446
562
|
end
|
447
563
|
|
448
|
-
def type_cast_calculated_value(value, operation)
|
564
|
+
def type_cast_calculated_value(value, operation, type)
|
449
565
|
case operation
|
450
566
|
when "count"
|
451
567
|
value.to_i
|
452
568
|
when "sum"
|
453
|
-
|
569
|
+
type.deserialize(value || 0)
|
454
570
|
when "average"
|
455
|
-
|
571
|
+
case type.type
|
572
|
+
when :integer, :decimal
|
573
|
+
value&.to_d
|
574
|
+
else
|
575
|
+
type.deserialize(value)
|
576
|
+
end
|
456
577
|
else # "minimum", "maximum"
|
457
|
-
|
578
|
+
type.deserialize(value)
|
458
579
|
end
|
459
580
|
end
|
460
581
|
|
@@ -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,11 +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
|
-
].each do |klass|
|
32
|
+
Delegation.delegated_classes.each do |klass|
|
20
33
|
delegate = Class.new(klass) {
|
21
34
|
include ClassSpecificRelation
|
22
35
|
}
|
@@ -61,17 +74,16 @@ module ActiveRecord
|
|
61
74
|
return if method_defined?(method)
|
62
75
|
|
63
76
|
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
|
64
|
-
definition = RUBY_VERSION >= "2.7" ? "..." : "*args, &block"
|
65
77
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
66
|
-
def #{method}(
|
67
|
-
scoping { klass.#{method}(
|
78
|
+
def #{method}(...)
|
79
|
+
scoping { klass.#{method}(...) }
|
68
80
|
end
|
69
81
|
RUBY
|
70
82
|
else
|
71
83
|
define_method(method) do |*args, &block|
|
72
84
|
scoping { klass.public_send(method, *args, &block) }
|
73
85
|
end
|
74
|
-
ruby2_keywords(method)
|
86
|
+
ruby2_keywords(method)
|
75
87
|
end
|
76
88
|
end
|
77
89
|
end
|
@@ -85,9 +97,9 @@ module ActiveRecord
|
|
85
97
|
# may vary depending on the klass of a relation, so we create a subclass of Relation
|
86
98
|
# for each different klass, and the delegations are compiled into that subclass only.
|
87
99
|
|
88
|
-
delegate :to_xml, :encode_with, :length, :each, :join,
|
100
|
+
delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
|
89
101
|
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
|
90
|
-
:to_sentence, :to_formatted_s, :as_json,
|
102
|
+
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
91
103
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
92
104
|
|
93
105
|
delegate :primary_key, :connection, to: :klass
|
@@ -104,13 +116,15 @@ 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
|
111
125
|
end
|
112
126
|
end
|
113
|
-
ruby2_keywords(:method_missing)
|
127
|
+
ruby2_keywords(:method_missing)
|
114
128
|
end
|
115
129
|
|
116
130
|
module ClassMethods # :nodoc:
|