activerecord 7.0.0 → 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 +1607 -1040
- data/MIT-LICENSE +1 -1
- data/README.rdoc +17 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -12
- data/lib/active_record/associations/collection_proxy.rb +22 -12
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +27 -17
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +20 -14
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +345 -219
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +40 -26
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +172 -69
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +110 -28
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +56 -10
- data/lib/active_record/base.rb +10 -5
- data/lib/active_record/callbacks.rb +16 -32
- 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 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
- 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 +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
- 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 +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
- 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 +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -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 +3 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +358 -57
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
- 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 +3 -1
- data/lib/active_record/connection_handling.rb +73 -96
- data/lib/active_record/core.rb +136 -148
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- 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 +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +38 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/scheme.rb +20 -23
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +114 -27
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_subscriber.rb +1 -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 +29 -8
- data/lib/active_record/fixtures.rb +121 -73
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- data/lib/active_record/integration.rb +10 -10
- data/lib/active_record/internal_metadata.rb +118 -30
- data/lib/active_record/locking/optimistic.rb +32 -18
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +39 -17
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +104 -9
- data/lib/active_record/migration/compatibility.rb +158 -64
- 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.rb +271 -117
- data/lib/active_record/model_schema.rb +82 -50
- data/lib/active_record/nested_attributes.rb +23 -3
- data/lib/active_record/normalization.rb +159 -0
- data/lib/active_record/persistence.rb +200 -47
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +16 -3
- data/lib/active_record/railtie.rb +127 -61
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +142 -143
- 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 +177 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +200 -83
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +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 +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +429 -76
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +98 -41
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +57 -16
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +65 -23
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +9 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +138 -107
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +26 -14
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/translation.rb +1 -1
- 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 +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -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 +50 -5
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +143 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- 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/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +0 -8
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +50 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -3,7 +3,49 @@
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
|
+
# = Active Record \Calculations
|
6
7
|
module Calculations
|
8
|
+
class ColumnAliasTracker # :nodoc:
|
9
|
+
def initialize(connection)
|
10
|
+
@connection = connection
|
11
|
+
@aliases = Hash.new(0)
|
12
|
+
end
|
13
|
+
|
14
|
+
def alias_for(field)
|
15
|
+
aliased_name = column_alias_for(field)
|
16
|
+
|
17
|
+
if @aliases[aliased_name] == 0
|
18
|
+
@aliases[aliased_name] = 1
|
19
|
+
aliased_name
|
20
|
+
else
|
21
|
+
# Update the count
|
22
|
+
count = @aliases[aliased_name] += 1
|
23
|
+
"#{truncate(aliased_name)}_#{count}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
# Converts the given field to the value that the database adapter returns as
|
29
|
+
# a usable column name:
|
30
|
+
#
|
31
|
+
# column_alias_for("users.id") # => "users_id"
|
32
|
+
# column_alias_for("sum(id)") # => "sum_id"
|
33
|
+
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
34
|
+
# column_alias_for("count(*)") # => "count_all"
|
35
|
+
def column_alias_for(field)
|
36
|
+
column_alias = +field
|
37
|
+
column_alias.gsub!(/\*/, "all")
|
38
|
+
column_alias.gsub!(/\W+/, " ")
|
39
|
+
column_alias.strip!
|
40
|
+
column_alias.gsub!(/ +/, "_")
|
41
|
+
@connection.table_alias_for(column_alias)
|
42
|
+
end
|
43
|
+
|
44
|
+
def truncate(name)
|
45
|
+
name.slice(0, @connection.table_alias_length - 2)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
7
49
|
# Count the records.
|
8
50
|
#
|
9
51
|
# Person.count
|
@@ -30,8 +72,7 @@ module ActiveRecord
|
|
30
72
|
# of each key would be the #count.
|
31
73
|
#
|
32
74
|
# Article.group(:status, :category).count
|
33
|
-
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
|
34
|
-
# # ["published", "business"]=>0, ["published", "technology"]=>2}
|
75
|
+
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
35
76
|
#
|
36
77
|
# If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
37
78
|
#
|
@@ -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,32 +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
|
-
if identity_or_column.nil? && (values.first.is_a?(Numeric) || values.first(1) == [])
|
90
|
-
identity_or_column = 0
|
91
|
-
end
|
92
|
-
|
93
|
-
if identity_or_column.nil?
|
94
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
95
|
-
Rails 7.0 has deprecated Enumerable.sum in favor of Ruby's native implementation available since 2.4.
|
96
|
-
Sum of non-numeric elements requires an initial argument.
|
97
|
-
MSG
|
98
|
-
values.inject(:+) || 0
|
99
|
-
else
|
100
|
-
values.sum(identity_or_column)
|
101
|
-
end
|
149
|
+
map(&block).sum(initial_value_or_column)
|
102
150
|
else
|
103
|
-
calculate(:sum,
|
151
|
+
calculate(:sum, initial_value_or_column)
|
104
152
|
end
|
105
153
|
end
|
106
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
|
+
|
107
160
|
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
108
161
|
# #minimum, and #maximum have been added as shortcuts.
|
109
162
|
#
|
@@ -136,10 +189,23 @@ module ActiveRecord
|
|
136
189
|
# ...
|
137
190
|
# end
|
138
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
|
+
|
139
205
|
if has_include?(column_name)
|
140
206
|
relation = apply_join_dependency
|
141
207
|
|
142
|
-
if operation
|
208
|
+
if operation == "count"
|
143
209
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
144
210
|
relation.distinct!
|
145
211
|
relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
@@ -155,7 +221,7 @@ module ActiveRecord
|
|
155
221
|
end
|
156
222
|
|
157
223
|
# Use #pluck as a shortcut to select one or more attributes without
|
158
|
-
# loading
|
224
|
+
# loading an entire record object per row.
|
159
225
|
#
|
160
226
|
# Person.pluck(:name)
|
161
227
|
#
|
@@ -188,31 +254,44 @@ module ActiveRecord
|
|
188
254
|
# # => ['0', '27761', '173']
|
189
255
|
#
|
190
256
|
# See also #ids.
|
191
|
-
#
|
192
257
|
def pluck(*column_names)
|
258
|
+
return [] if @none
|
259
|
+
|
193
260
|
if loaded? && all_attributes?(column_names)
|
194
|
-
|
261
|
+
result = records.pluck(*column_names)
|
262
|
+
if @async
|
263
|
+
return Promise::Complete.new(result)
|
264
|
+
else
|
265
|
+
return result
|
266
|
+
end
|
195
267
|
end
|
196
268
|
|
197
269
|
if has_include?(column_names.first)
|
198
270
|
relation = apply_join_dependency
|
199
271
|
relation.pluck(*column_names)
|
200
272
|
else
|
201
|
-
klass.disallow_raw_sql!(column_names)
|
273
|
+
klass.disallow_raw_sql!(column_names.flatten)
|
202
274
|
columns = arel_columns(column_names)
|
203
275
|
relation = spawn
|
204
276
|
relation.select_values = columns
|
205
277
|
result = skip_query_cache_if_necessary do
|
206
278
|
if where_clause.contradiction?
|
207
|
-
ActiveRecord::Result.empty
|
279
|
+
ActiveRecord::Result.empty(async: @async)
|
208
280
|
else
|
209
|
-
klass.connection.select_all(relation.arel, "#{klass.name} Pluck")
|
281
|
+
klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
|
210
282
|
end
|
211
283
|
end
|
212
|
-
|
284
|
+
result.then do |result|
|
285
|
+
type_cast_pluck_values(result, columns)
|
286
|
+
end
|
213
287
|
end
|
214
288
|
end
|
215
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
|
+
|
216
295
|
# Pick the value(s) from the named column(s) in the current relation.
|
217
296
|
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
218
297
|
# when you have a relation that's already narrowed down to a single row.
|
@@ -229,18 +308,59 @@ module ActiveRecord
|
|
229
308
|
# # => [ 'David', 'david@loudthinking.com' ]
|
230
309
|
def pick(*column_names)
|
231
310
|
if loaded? && all_attributes?(column_names)
|
232
|
-
|
311
|
+
result = records.pick(*column_names)
|
312
|
+
return @async ? Promise::Complete.new(result) : result
|
233
313
|
end
|
234
314
|
|
235
|
-
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)
|
236
321
|
end
|
237
322
|
|
238
|
-
#
|
323
|
+
# Returns the base model's ID's for the relation using the table's primary key
|
239
324
|
#
|
240
325
|
# Person.ids # SELECT people.id FROM people
|
241
|
-
# 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
|
242
327
|
def ids
|
243
|
-
|
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
|
244
364
|
end
|
245
365
|
|
246
366
|
private
|
@@ -300,6 +420,7 @@ module ActiveRecord
|
|
300
420
|
# Shortcut when limit is zero.
|
301
421
|
return 0 if limit_value == 0
|
302
422
|
|
423
|
+
relation = self
|
303
424
|
query_builder = build_count_subquery(spawn, column_name, distinct)
|
304
425
|
else
|
305
426
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
@@ -314,15 +435,23 @@ module ActiveRecord
|
|
314
435
|
query_builder = relation.arel
|
315
436
|
end
|
316
437
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
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
|
323
444
|
end
|
324
445
|
|
325
|
-
|
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)
|
454
|
+
end
|
326
455
|
end
|
327
456
|
|
328
457
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
@@ -336,21 +465,24 @@ module ActiveRecord
|
|
336
465
|
end
|
337
466
|
group_fields = arel_columns(group_fields)
|
338
467
|
|
468
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
469
|
+
|
339
470
|
group_aliases = group_fields.map { |field|
|
340
471
|
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
341
|
-
|
472
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
342
473
|
}
|
343
474
|
group_columns = group_aliases.zip(group_fields)
|
344
475
|
|
345
476
|
column = aggregate_column(column_name)
|
346
|
-
column_alias =
|
477
|
+
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
347
478
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
348
|
-
select_value.as(column_alias)
|
479
|
+
select_value.as(connection.quote_column_name(column_alias))
|
349
480
|
|
350
481
|
select_values = [select_value]
|
351
482
|
select_values += self.select_values unless having_clause.empty?
|
352
483
|
|
353
484
|
select_values.concat group_columns.map { |aliaz, field|
|
485
|
+
aliaz = connection.quote_column_name(aliaz)
|
354
486
|
if field.respond_to?(:as)
|
355
487
|
field.as(aliaz)
|
356
488
|
else
|
@@ -362,58 +494,43 @@ module ActiveRecord
|
|
362
494
|
relation.group_values = group_fields
|
363
495
|
relation.select_values = select_values
|
364
496
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
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
|
372
504
|
|
373
|
-
|
374
|
-
|
375
|
-
|
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
|
376
510
|
end
|
377
|
-
end
|
378
511
|
|
379
|
-
|
380
|
-
|
381
|
-
|
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
|
382
516
|
end
|
383
|
-
end
|
384
517
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
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
|
522
|
+
end
|
390
523
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
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
|
395
528
|
|
396
|
-
|
529
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
530
|
+
end
|
397
531
|
end
|
398
532
|
end
|
399
533
|
|
400
|
-
# Converts the given field to the value that the database adapter returns as
|
401
|
-
# a usable column name:
|
402
|
-
#
|
403
|
-
# column_alias_for("users.id") # => "users_id"
|
404
|
-
# column_alias_for("sum(id)") # => "sum_id"
|
405
|
-
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
406
|
-
# column_alias_for("count(*)") # => "count_all"
|
407
|
-
def column_alias_for(field)
|
408
|
-
column_alias = +field
|
409
|
-
column_alias.gsub!(/\*/, "all")
|
410
|
-
column_alias.gsub!(/\W+/, " ")
|
411
|
-
column_alias.strip!
|
412
|
-
column_alias.gsub!(/ +/, "_")
|
413
|
-
|
414
|
-
connection.table_alias_for(column_alias)
|
415
|
-
end
|
416
|
-
|
417
534
|
def type_for(field, &block)
|
418
535
|
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
419
536
|
@klass.type_for_attribute(field_name, &block)
|
@@ -5,6 +5,23 @@ require "active_support/core_ext/module/delegation"
|
|
5
5
|
|
6
6
|
module ActiveRecord
|
7
7
|
module Delegation # :nodoc:
|
8
|
+
class << self
|
9
|
+
def delegated_classes
|
10
|
+
[
|
11
|
+
ActiveRecord::Relation,
|
12
|
+
ActiveRecord::Associations::CollectionProxy,
|
13
|
+
ActiveRecord::AssociationRelation,
|
14
|
+
ActiveRecord::DisableJoinsAssociationRelation,
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
18
|
+
def uncacheable_methods
|
19
|
+
@uncacheable_methods ||= (
|
20
|
+
delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods
|
21
|
+
).to_set.freeze
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
8
25
|
module DelegateCache # :nodoc:
|
9
26
|
def relation_delegate_class(klass)
|
10
27
|
@relation_delegate_cache[klass]
|
@@ -12,12 +29,7 @@ module ActiveRecord
|
|
12
29
|
|
13
30
|
def initialize_relation_delegate_cache
|
14
31
|
@relation_delegate_cache = cache = {}
|
15
|
-
|
16
|
-
ActiveRecord::Relation,
|
17
|
-
ActiveRecord::Associations::CollectionProxy,
|
18
|
-
ActiveRecord::AssociationRelation,
|
19
|
-
ActiveRecord::DisableJoinsAssociationRelation
|
20
|
-
].each do |klass|
|
32
|
+
Delegation.delegated_classes.each do |klass|
|
21
33
|
delegate = Class.new(klass) {
|
22
34
|
include ClassSpecificRelation
|
23
35
|
}
|
@@ -85,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,7 +116,9 @@ module ActiveRecord
|
|
104
116
|
private
|
105
117
|
def method_missing(method, *args, &block)
|
106
118
|
if @klass.respond_to?(method)
|
107
|
-
|
119
|
+
unless Delegation.uncacheable_methods.include?(method)
|
120
|
+
@klass.generate_relation_method(method)
|
121
|
+
end
|
108
122
|
scoping { @klass.public_send(method, *args, &block) }
|
109
123
|
else
|
110
124
|
super
|
@@ -6,7 +6,9 @@ module ActiveRecord
|
|
6
6
|
module FinderMethods
|
7
7
|
ONE_AS_ONE = "1 AS one"
|
8
8
|
|
9
|
-
# Find by id - This can either be a specific id (
|
9
|
+
# Find by id - This can either be a specific id (ID), a list of ids (ID, ID, ID), or an array of ids ([ID, ID, ID]).
|
10
|
+
# `ID` refers to an "identifier". For models with a single-column primary key, `ID` will be a single value,
|
11
|
+
# and for models with a composite primary key, it will be an array of values.
|
10
12
|
# If one or more records cannot be found for the requested ids, then ActiveRecord::RecordNotFound will be raised.
|
11
13
|
# If the primary key is an integer, find by id coerces its arguments by using +to_i+.
|
12
14
|
#
|
@@ -14,10 +16,31 @@ module ActiveRecord
|
|
14
16
|
# Person.find("1") # returns the object for ID = 1
|
15
17
|
# Person.find("31-sarah") # returns the object for ID = 31
|
16
18
|
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
|
17
|
-
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
|
19
|
+
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17), or with composite primary key [7, 17]
|
18
20
|
# Person.find([1]) # returns an array for the object with ID = 1
|
19
21
|
# Person.where("administrator = 1").order("created_on DESC").find(1)
|
20
22
|
#
|
23
|
+
# ==== Find a record for a composite primary key model
|
24
|
+
# TravelRoute.primary_key = [:origin, :destination]
|
25
|
+
#
|
26
|
+
# TravelRoute.find(["Ottawa", "London"])
|
27
|
+
# => #<TravelRoute origin: "Ottawa", destination: "London">
|
28
|
+
#
|
29
|
+
# TravelRoute.find([["Paris", "Montreal"]])
|
30
|
+
# => [#<TravelRoute origin: "Paris", destination: "Montreal">]
|
31
|
+
#
|
32
|
+
# TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
|
33
|
+
# => [
|
34
|
+
# #<TravelRoute origin: "New York", destination: "Las Vegas">,
|
35
|
+
# #<TravelRoute origin: "New York", destination: "Portland">
|
36
|
+
# ]
|
37
|
+
#
|
38
|
+
# TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
|
39
|
+
# => [
|
40
|
+
# #<TravelRoute origin: "Berlin", destination: "London">,
|
41
|
+
# #<TravelRoute origin: "Barcelona", destination: "Lisbon">
|
42
|
+
# ]
|
43
|
+
#
|
21
44
|
# NOTE: The returned records are in the same order as the ids you provide.
|
22
45
|
# If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
|
23
46
|
# method and provide an explicit ActiveRecord::QueryMethods#order option.
|
@@ -324,6 +347,8 @@ module ActiveRecord
|
|
324
347
|
# Person.exists?
|
325
348
|
# Person.where(name: 'Spartacus', rating: 4).exists?
|
326
349
|
def exists?(conditions = :none)
|
350
|
+
return false if @none
|
351
|
+
|
327
352
|
if Base === conditions
|
328
353
|
raise ArgumentError, <<-MSG.squish
|
329
354
|
You are passing an instance of ActiveRecord::Base to `exists?`.
|
@@ -350,10 +375,20 @@ module ActiveRecord
|
|
350
375
|
# compared to the records in memory. If the relation is unloaded, an
|
351
376
|
# efficient existence query is performed, as in #exists?.
|
352
377
|
def include?(record)
|
378
|
+
# The existing implementation relies on receiving an Active Record instance as the input parameter named record.
|
379
|
+
# Any non-Active Record object passed to this implementation is guaranteed to return `false`.
|
380
|
+
return false unless record.is_a?(klass)
|
381
|
+
|
353
382
|
if loaded? || offset_value || limit_value || having_clause.any?
|
354
383
|
records.include?(record)
|
355
384
|
else
|
356
|
-
|
385
|
+
id = if record.class.composite_primary_key?
|
386
|
+
record.class.primary_key.zip(record.id).to_h
|
387
|
+
else
|
388
|
+
record.id
|
389
|
+
end
|
390
|
+
|
391
|
+
exists?(id)
|
357
392
|
end
|
358
393
|
end
|
359
394
|
|
@@ -442,10 +477,17 @@ module ActiveRecord
|
|
442
477
|
def find_with_ids(*ids)
|
443
478
|
raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
|
444
479
|
|
445
|
-
expects_array =
|
480
|
+
expects_array = if klass.composite_primary_key?
|
481
|
+
ids.first.first.is_a?(Array)
|
482
|
+
else
|
483
|
+
ids.first.is_a?(Array)
|
484
|
+
end
|
485
|
+
|
446
486
|
return [] if expects_array && ids.first.empty?
|
447
487
|
|
448
|
-
ids = ids.
|
488
|
+
ids = ids.first if expects_array
|
489
|
+
|
490
|
+
ids = ids.compact.uniq
|
449
491
|
|
450
492
|
model_name = @klass.name
|
451
493
|
|
@@ -469,7 +511,12 @@ module ActiveRecord
|
|
469
511
|
MSG
|
470
512
|
end
|
471
513
|
|
472
|
-
relation =
|
514
|
+
relation = if klass.composite_primary_key?
|
515
|
+
where(primary_key.zip(id).to_h)
|
516
|
+
else
|
517
|
+
where(primary_key => id)
|
518
|
+
end
|
519
|
+
|
473
520
|
record = relation.take
|
474
521
|
|
475
522
|
raise_record_not_found_exception!(id, 0, 1) unless record
|
@@ -480,7 +527,9 @@ module ActiveRecord
|
|
480
527
|
def find_some(ids)
|
481
528
|
return find_some_ordered(ids) unless order_values.present?
|
482
529
|
|
483
|
-
|
530
|
+
relation = where(primary_key => ids)
|
531
|
+
relation = relation.select(table[primary_key]) unless select_values.empty?
|
532
|
+
result = relation.to_a
|
484
533
|
|
485
534
|
expected_size =
|
486
535
|
if limit_value && ids.size > limit_value
|
@@ -504,7 +553,10 @@ module ActiveRecord
|
|
504
553
|
def find_some_ordered(ids)
|
505
554
|
ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
|
506
555
|
|
507
|
-
|
556
|
+
relation = except(:limit, :offset)
|
557
|
+
relation = relation.where(primary_key => ids)
|
558
|
+
relation = relation.select(table[primary_key]) unless select_values.empty?
|
559
|
+
result = relation.records
|
508
560
|
|
509
561
|
if result.size == ids.size
|
510
562
|
result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
|
@@ -559,10 +611,10 @@ module ActiveRecord
|
|
559
611
|
else
|
560
612
|
relation = ordered_relation
|
561
613
|
|
562
|
-
if
|
614
|
+
if relation.order_values.empty? || relation.has_limit_or_offset?
|
563
615
|
relation.records[-index]
|
564
616
|
else
|
565
|
-
relation.
|
617
|
+
relation.reverse_order.offset(index - 1).first
|
566
618
|
end
|
567
619
|
end
|
568
620
|
end
|
@@ -572,15 +624,24 @@ module ActiveRecord
|
|
572
624
|
end
|
573
625
|
|
574
626
|
def ordered_relation
|
575
|
-
if order_values.empty? && (implicit_order_column || primary_key)
|
576
|
-
|
577
|
-
order(table[implicit_order_column].asc, table[primary_key].asc)
|
578
|
-
else
|
579
|
-
order(table[implicit_order_column || primary_key].asc)
|
580
|
-
end
|
627
|
+
if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
|
628
|
+
order(_order_columns.map { |column| table[column].asc })
|
581
629
|
else
|
582
630
|
self
|
583
631
|
end
|
584
632
|
end
|
633
|
+
|
634
|
+
def _order_columns
|
635
|
+
oc = []
|
636
|
+
|
637
|
+
oc << implicit_order_column if implicit_order_column
|
638
|
+
oc << query_constraints_list if query_constraints_list
|
639
|
+
|
640
|
+
if primary_key && query_constraints_list.nil?
|
641
|
+
oc << primary_key
|
642
|
+
end
|
643
|
+
|
644
|
+
oc.flatten.uniq.compact
|
645
|
+
end
|
585
646
|
end
|
586
647
|
end
|