activerecord 7.0.8.7 → 7.2.3
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 +781 -1777
- data/MIT-LICENSE +1 -1
- data/README.rdoc +30 -30
- 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 +31 -23
- 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 +40 -9
- 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 +35 -21
- 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 +4 -3
- 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 +153 -33
- data/lib/active_record/attributes.rb +96 -71
- data/lib/active_record/autosave_association.rb +81 -39
- data/lib/active_record/base.rb +11 -7
- 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 +343 -91
- 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 +229 -64
- 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 +142 -12
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +539 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +289 -128
- 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 +60 -55
- 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 +108 -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 +153 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +371 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +374 -203
- 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 +57 -45
- 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 +51 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +298 -113
- 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 +101 -105
- data/lib/active_record/core.rb +273 -178
- data/lib/active_record/counter_cache.rb +69 -35
- data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -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 +56 -27
- 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 +46 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +48 -13
- data/lib/active_record/encryption/encryptor.rb +35 -19
- 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 +130 -28
- data/lib/active_record/errors.rb +154 -34
- 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 +48 -10
- data/lib/active_record/fixtures.rb +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +4 -4
- 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 +236 -118
- 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 +96 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +35 -10
- data/lib/active_record/railtie.rb +131 -87
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +147 -155
- 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 +270 -108
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +97 -21
- 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 +20 -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 +3 -2
- data/lib/active_record/relation/query_methods.rb +585 -109
- 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 +15 -21
- data/lib/active_record/relation.rb +592 -92
- 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 +90 -23
- 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 +33 -11
- 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 +23 -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 +108 -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 +3 -1
- 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/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- 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/delete_statement.rb +4 -2
- 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/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +7 -3
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -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 +114 -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 +56 -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)
|
|
@@ -59,28 +60,37 @@ module ActiveRecord
|
|
|
59
60
|
# Person.distinct.count(:age)
|
|
60
61
|
# # => counts the number of different age values
|
|
61
62
|
#
|
|
62
|
-
# If
|
|
63
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
|
|
63
64
|
# it returns a Hash whose keys represent the aggregated column,
|
|
64
65
|
# and the values are the respective amounts:
|
|
65
66
|
#
|
|
66
67
|
# Person.group(:city).count
|
|
67
68
|
# # => { 'Rome' => 5, 'Paris' => 3 }
|
|
68
69
|
#
|
|
69
|
-
# If
|
|
70
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
|
|
70
71
|
# keys are an array containing the individual values of each column and the value
|
|
71
|
-
# of each key would be the
|
|
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
|
-
# If
|
|
77
|
+
# If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
|
78
78
|
#
|
|
79
79
|
# Person.select(:age).count
|
|
80
80
|
# # => counts the number of different age values
|
|
81
81
|
#
|
|
82
|
-
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid
|
|
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, calls the block with each record in the relation and
|
|
86
|
+
# returns the number of records for which the block returns a truthy value.
|
|
87
|
+
#
|
|
88
|
+
# Person.count { |person| person.age > 21 }
|
|
89
|
+
# # => counts the number of people older that 21
|
|
90
|
+
#
|
|
91
|
+
# If the relation hasn't been loaded yet, calling +count+ with a block will
|
|
92
|
+
# load all records in the relation. If there are a lot of records in the
|
|
93
|
+
# relation, loading all records 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,41 @@ 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, calls the block with each record in the relation and
|
|
163
|
+
# returns the sum of +initial_value_or_column+ plus the block return values:
|
|
164
|
+
#
|
|
165
|
+
# Person.sum { |person| person.age } # => 4562
|
|
166
|
+
# Person.sum(1000) { |person| person.age } # => 5562
|
|
167
|
+
#
|
|
168
|
+
# If the relation hasn't been loaded yet, calling +sum+ with a block will
|
|
169
|
+
# load all records in the relation. If there are a lot of records in the
|
|
170
|
+
# relation, loading all records could result in performance issues.
|
|
171
|
+
def sum(initial_value_or_column = 0, &block)
|
|
128
172
|
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
|
|
173
|
+
map(&block).sum(initial_value_or_column)
|
|
143
174
|
else
|
|
144
|
-
calculate(:sum,
|
|
175
|
+
calculate(:sum, initial_value_or_column)
|
|
145
176
|
end
|
|
146
177
|
end
|
|
147
178
|
|
|
179
|
+
# Same as #sum, but performs the query asynchronously and returns an
|
|
180
|
+
# ActiveRecord::Promise.
|
|
181
|
+
def async_sum(identity_or_column = nil)
|
|
182
|
+
async.sum(identity_or_column)
|
|
183
|
+
end
|
|
184
|
+
|
|
148
185
|
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
|
149
186
|
# #minimum, and #maximum have been added as shortcuts.
|
|
150
187
|
#
|
|
@@ -177,13 +214,26 @@ module ActiveRecord
|
|
|
177
214
|
# ...
|
|
178
215
|
# end
|
|
179
216
|
def calculate(operation, column_name)
|
|
217
|
+
operation = operation.to_s.downcase
|
|
218
|
+
|
|
219
|
+
if @none
|
|
220
|
+
case operation
|
|
221
|
+
when "count", "sum"
|
|
222
|
+
result = group_values.any? ? Hash.new : 0
|
|
223
|
+
return @async ? Promise::Complete.new(result) : result
|
|
224
|
+
when "average", "minimum", "maximum"
|
|
225
|
+
result = group_values.any? ? Hash.new : nil
|
|
226
|
+
return @async ? Promise::Complete.new(result) : result
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
180
230
|
if has_include?(column_name)
|
|
181
231
|
relation = apply_join_dependency
|
|
182
232
|
|
|
183
|
-
if operation
|
|
233
|
+
if operation == "count"
|
|
184
234
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
|
185
235
|
relation.distinct!
|
|
186
|
-
relation.select_values =
|
|
236
|
+
relation.select_values = Array(klass.primary_key || table[Arel.star])
|
|
187
237
|
end
|
|
188
238
|
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
|
189
239
|
relation.order_values = [] if group_values.empty?
|
|
@@ -224,36 +274,62 @@ module ActiveRecord
|
|
|
224
274
|
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
|
225
275
|
# # => [2, 3]
|
|
226
276
|
#
|
|
277
|
+
# Comment.joins(:person).pluck(:id, person: [:id])
|
|
278
|
+
# # SELECT comments.id, people.id FROM comments INNER JOIN people on comments.person_id = people.id
|
|
279
|
+
# # => [[1, 2], [2, 2]]
|
|
280
|
+
#
|
|
227
281
|
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
|
228
282
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
|
229
283
|
# # => ['0', '27761', '173']
|
|
230
284
|
#
|
|
231
285
|
# See also #ids.
|
|
232
|
-
#
|
|
233
286
|
def pluck(*column_names)
|
|
287
|
+
if @none
|
|
288
|
+
if @async
|
|
289
|
+
return Promise::Complete.new([])
|
|
290
|
+
else
|
|
291
|
+
return []
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
234
295
|
if loaded? && all_attributes?(column_names)
|
|
235
|
-
|
|
296
|
+
result = records.pluck(*column_names)
|
|
297
|
+
if @async
|
|
298
|
+
return Promise::Complete.new(result)
|
|
299
|
+
else
|
|
300
|
+
return result
|
|
301
|
+
end
|
|
236
302
|
end
|
|
237
303
|
|
|
238
304
|
if has_include?(column_names.first)
|
|
239
305
|
relation = apply_join_dependency
|
|
240
306
|
relation.pluck(*column_names)
|
|
241
307
|
else
|
|
242
|
-
klass.disallow_raw_sql!(column_names)
|
|
243
|
-
columns = arel_columns(column_names)
|
|
308
|
+
klass.disallow_raw_sql!(flattened_args(column_names))
|
|
244
309
|
relation = spawn
|
|
310
|
+
columns = relation.arel_columns(column_names)
|
|
245
311
|
relation.select_values = columns
|
|
246
312
|
result = skip_query_cache_if_necessary do
|
|
247
313
|
if where_clause.contradiction?
|
|
248
|
-
ActiveRecord::Result.empty
|
|
314
|
+
ActiveRecord::Result.empty(async: @async)
|
|
249
315
|
else
|
|
250
|
-
klass.
|
|
316
|
+
klass.with_connection do |c|
|
|
317
|
+
c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
|
|
318
|
+
end
|
|
251
319
|
end
|
|
252
320
|
end
|
|
253
|
-
|
|
321
|
+
result.then do |result|
|
|
322
|
+
type_cast_pluck_values(result, columns)
|
|
323
|
+
end
|
|
254
324
|
end
|
|
255
325
|
end
|
|
256
326
|
|
|
327
|
+
# Same as #pluck, but performs the query asynchronously and returns an
|
|
328
|
+
# ActiveRecord::Promise.
|
|
329
|
+
def async_pluck(*column_names)
|
|
330
|
+
async.pluck(*column_names)
|
|
331
|
+
end
|
|
332
|
+
|
|
257
333
|
# Pick the value(s) from the named column(s) in the current relation.
|
|
258
334
|
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
|
259
335
|
# when you have a relation that's already narrowed down to a single row.
|
|
@@ -270,20 +346,74 @@ module ActiveRecord
|
|
|
270
346
|
# # => [ 'David', 'david@loudthinking.com' ]
|
|
271
347
|
def pick(*column_names)
|
|
272
348
|
if loaded? && all_attributes?(column_names)
|
|
273
|
-
|
|
349
|
+
result = records.pick(*column_names)
|
|
350
|
+
return @async ? Promise::Complete.new(result) : result
|
|
274
351
|
end
|
|
275
352
|
|
|
276
|
-
limit(1).pluck(*column_names).first
|
|
353
|
+
limit(1).pluck(*column_names).then(&:first)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Same as #pick, but performs the query asynchronously and returns an
|
|
357
|
+
# ActiveRecord::Promise.
|
|
358
|
+
def async_pick(*column_names)
|
|
359
|
+
async.pick(*column_names)
|
|
277
360
|
end
|
|
278
361
|
|
|
279
|
-
#
|
|
362
|
+
# Returns the base model's ID's for the relation using the table's primary key
|
|
280
363
|
#
|
|
281
364
|
# Person.ids # SELECT people.id FROM people
|
|
282
|
-
# Person.joins(:
|
|
365
|
+
# Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
|
283
366
|
def ids
|
|
284
|
-
|
|
367
|
+
primary_key_array = Array(primary_key)
|
|
368
|
+
|
|
369
|
+
if loaded?
|
|
370
|
+
result = records.map do |record|
|
|
371
|
+
if primary_key_array.one?
|
|
372
|
+
record._read_attribute(primary_key_array.first)
|
|
373
|
+
else
|
|
374
|
+
primary_key_array.map { |column| record._read_attribute(column) }
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
return @async ? Promise::Complete.new(result) : result
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
if has_include?(primary_key)
|
|
381
|
+
relation = apply_join_dependency.group(*primary_key_array)
|
|
382
|
+
return relation.ids
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
columns = arel_columns(primary_key_array)
|
|
386
|
+
relation = spawn
|
|
387
|
+
relation.select_values = columns
|
|
388
|
+
|
|
389
|
+
result = if relation.where_clause.contradiction?
|
|
390
|
+
ActiveRecord::Result.empty
|
|
391
|
+
else
|
|
392
|
+
skip_query_cache_if_necessary do
|
|
393
|
+
klass.with_connection do |c|
|
|
394
|
+
c.select_all(relation, "#{klass.name} Ids", async: @async)
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
result.then { |result| type_cast_pluck_values(result, columns) }
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Same as #ids, but performs the query asynchronously and returns an
|
|
403
|
+
# ActiveRecord::Promise.
|
|
404
|
+
def async_ids
|
|
405
|
+
async.ids
|
|
285
406
|
end
|
|
286
407
|
|
|
408
|
+
protected
|
|
409
|
+
def aggregate_column(column_name)
|
|
410
|
+
return column_name if Arel::Expressions === column_name
|
|
411
|
+
|
|
412
|
+
arel_column(column_name.to_s) do |name|
|
|
413
|
+
column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
287
417
|
private
|
|
288
418
|
def all_attributes?(column_names)
|
|
289
419
|
(column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
|
|
@@ -324,29 +454,22 @@ module ActiveRecord
|
|
|
324
454
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
325
455
|
end
|
|
326
456
|
|
|
327
|
-
def aggregate_column(column_name)
|
|
328
|
-
return column_name if Arel::Expressions === column_name
|
|
329
|
-
|
|
330
|
-
arel_column(column_name.to_s) do |name|
|
|
331
|
-
Arel.sql(column_name == :all ? "*" : name)
|
|
332
|
-
end
|
|
333
|
-
end
|
|
334
|
-
|
|
335
457
|
def operation_over_aggregate_column(column, operation, distinct)
|
|
336
458
|
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
|
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
|
|
347
470
|
relation = unscope(:order).distinct!(false)
|
|
348
471
|
|
|
349
|
-
column = aggregate_column(column_name)
|
|
472
|
+
column = relation.aggregate_column(column_name)
|
|
350
473
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
351
474
|
select_value.distinct = true if operation == "sum" && distinct
|
|
352
475
|
|
|
@@ -355,15 +478,29 @@ 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
|
+
if @async
|
|
483
|
+
FutureResult.wrap(ActiveRecord::Result.empty)
|
|
484
|
+
else
|
|
485
|
+
ActiveRecord::Result.empty
|
|
486
|
+
end
|
|
487
|
+
else
|
|
488
|
+
skip_query_cache_if_necessary do
|
|
489
|
+
@klass.with_connection do |c|
|
|
490
|
+
c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
|
|
491
|
+
end
|
|
492
|
+
end
|
|
364
493
|
end
|
|
365
494
|
|
|
366
|
-
|
|
495
|
+
query_result.then do |result|
|
|
496
|
+
if operation != "count"
|
|
497
|
+
type = column.try(:type_caster) ||
|
|
498
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
|
499
|
+
type = type.subtype if Enum::EnumType === type
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
type_cast_calculated_value(result.cast_values.first, operation, type)
|
|
503
|
+
end
|
|
367
504
|
end
|
|
368
505
|
|
|
369
506
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
|
@@ -375,70 +512,77 @@ module ActiveRecord
|
|
|
375
512
|
associated = association && association.belongs_to? # only count belongs_to associations
|
|
376
513
|
group_fields = Array(association.foreign_key) if associated
|
|
377
514
|
end
|
|
378
|
-
group_fields = arel_columns(group_fields)
|
|
379
515
|
|
|
380
|
-
|
|
516
|
+
relation = except(:group).distinct!(false)
|
|
517
|
+
group_fields = relation.arel_columns(group_fields)
|
|
381
518
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
column_alias_tracker.alias_for(field.to_s.downcase)
|
|
385
|
-
}
|
|
386
|
-
group_columns = group_aliases.zip(group_fields)
|
|
519
|
+
@klass.with_connection do |connection|
|
|
520
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
|
387
521
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
522
|
+
group_aliases = group_fields.map { |field|
|
|
523
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
|
524
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
|
525
|
+
}
|
|
526
|
+
group_columns = group_aliases.zip(group_fields)
|
|
392
527
|
|
|
393
|
-
|
|
394
|
-
|
|
528
|
+
column = relation.aggregate_column(column_name)
|
|
529
|
+
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
530
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
531
|
+
select_value.as(adapter_class.quote_column_name(column_alias))
|
|
395
532
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if field.respond_to?(:as)
|
|
399
|
-
field.as(aliaz)
|
|
400
|
-
else
|
|
401
|
-
"#{field} AS #{aliaz}"
|
|
402
|
-
end
|
|
403
|
-
}
|
|
533
|
+
select_values = [select_value]
|
|
534
|
+
select_values += self.select_values unless having_clause.empty?
|
|
404
535
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
536
|
+
select_values.concat group_columns.map { |aliaz, field|
|
|
537
|
+
aliaz = adapter_class.quote_column_name(aliaz)
|
|
538
|
+
if field.respond_to?(:as)
|
|
539
|
+
field.as(aliaz)
|
|
540
|
+
else
|
|
541
|
+
"#{field} AS #{aliaz}"
|
|
542
|
+
end
|
|
543
|
+
}
|
|
408
544
|
|
|
409
|
-
|
|
545
|
+
relation.group_values = group_fields
|
|
546
|
+
relation.select_values = select_values
|
|
410
547
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
key_records = key_records.index_by(&:id)
|
|
415
|
-
end
|
|
548
|
+
result = skip_query_cache_if_necessary do
|
|
549
|
+
connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async)
|
|
550
|
+
end
|
|
416
551
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
552
|
+
result.then do |calculated_data|
|
|
553
|
+
if association
|
|
554
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
|
555
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
|
556
|
+
key_records = key_records.index_by(&:id)
|
|
421
557
|
end
|
|
422
|
-
end
|
|
423
558
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
559
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
|
560
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
|
561
|
+
type_for(col_name) do
|
|
562
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
|
563
|
+
end
|
|
564
|
+
end
|
|
429
565
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
566
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
|
567
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
|
568
|
+
hash[col_name] = row[i]
|
|
569
|
+
end
|
|
570
|
+
end
|
|
435
571
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
572
|
+
if operation != "count"
|
|
573
|
+
type = column.try(:type_caster) ||
|
|
574
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
|
575
|
+
type = type.subtype if Enum::EnumType === type
|
|
576
|
+
end
|
|
440
577
|
|
|
441
|
-
|
|
578
|
+
hash_rows.each_with_object({}) do |row, result|
|
|
579
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
|
580
|
+
key = key.first if key.size == 1
|
|
581
|
+
key = key_records[key] if associated
|
|
582
|
+
|
|
583
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
|
584
|
+
end
|
|
585
|
+
end
|
|
442
586
|
end
|
|
443
587
|
end
|
|
444
588
|
|
|
@@ -465,7 +609,7 @@ module ActiveRecord
|
|
|
465
609
|
klass.attribute_types.fetch(name = result.columns[i]) do
|
|
466
610
|
join_dependencies ||= build_join_dependencies
|
|
467
611
|
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
|
468
|
-
result.column_types[
|
|
612
|
+
result.column_types[i] || Type.default_value
|
|
469
613
|
end
|
|
470
614
|
end
|
|
471
615
|
end
|
|
@@ -493,22 +637,40 @@ module ActiveRecord
|
|
|
493
637
|
def select_for_count
|
|
494
638
|
if select_values.present?
|
|
495
639
|
return select_values.first if select_values.one?
|
|
496
|
-
|
|
640
|
+
|
|
641
|
+
select_values.map do |field|
|
|
642
|
+
column = arel_column(field.to_s) do |attr_name|
|
|
643
|
+
Arel.sql(attr_name)
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
if column.is_a?(Arel::Nodes::SqlLiteral)
|
|
647
|
+
column
|
|
648
|
+
else
|
|
649
|
+
"#{adapter_class.quote_table_name(column.relation.name)}.#{adapter_class.quote_column_name(column.name)}"
|
|
650
|
+
end
|
|
651
|
+
end.join(", ")
|
|
497
652
|
else
|
|
498
653
|
:all
|
|
499
654
|
end
|
|
500
655
|
end
|
|
501
656
|
|
|
657
|
+
def build_count_subquery?(operation, column_name, distinct)
|
|
658
|
+
# SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
|
|
659
|
+
# multiple columns, so we need to use subquery for this.
|
|
660
|
+
operation == "count" &&
|
|
661
|
+
(((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
|
|
662
|
+
end
|
|
663
|
+
|
|
502
664
|
def build_count_subquery(relation, column_name, distinct)
|
|
503
665
|
if column_name == :all
|
|
504
666
|
column_alias = Arel.star
|
|
505
667
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
506
668
|
else
|
|
507
669
|
column_alias = Arel.sql("count_column")
|
|
508
|
-
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
|
670
|
+
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
|
509
671
|
end
|
|
510
672
|
|
|
511
|
-
subquery_alias = Arel.sql("subquery_for_count")
|
|
673
|
+
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
512
674
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
|
513
675
|
|
|
514
676
|
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:
|