mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Fix type casting aggregated values on association's attributes
Follow up of #39255. Previously aggregation functions only use the model's attribute types on the relation for type cast, this will be looking up association's attribute and type caster if a column name is table name qualified. Fixes #39248.
This commit is contained in:
parent
8d597b5435
commit
5f59eac239
4 changed files with 39 additions and 9 deletions
|
@ -281,8 +281,7 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def operation_over_aggregate_column(column_name, operation, distinct)
|
||||
column = aggregate_column(column_name)
|
||||
def operation_over_aggregate_column(column, operation, distinct)
|
||||
operation == "count" ? column.count(distinct) : column.send(operation)
|
||||
end
|
||||
|
||||
|
@ -296,7 +295,8 @@ module ActiveRecord
|
|||
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
||||
relation = unscope(:order).distinct!(false)
|
||||
|
||||
select_value = operation_over_aggregate_column(column_name, operation, distinct)
|
||||
column = aggregate_column(column_name)
|
||||
select_value = operation_over_aggregate_column(column, operation, distinct)
|
||||
select_value.distinct = true if operation == "sum" && distinct
|
||||
|
||||
relation.select_values = [select_value]
|
||||
|
@ -307,7 +307,7 @@ module ActiveRecord
|
|||
result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
|
||||
|
||||
type_cast_calculated_value(result.cast_values.first, operation) do |value|
|
||||
if type = klass.attribute_types[column_name.to_s]
|
||||
if type = column.try(:type_caster) || klass.attribute_types[column_name.to_s]
|
||||
type.deserialize(value)
|
||||
else
|
||||
value
|
||||
|
@ -331,8 +331,9 @@ module ActiveRecord
|
|||
}
|
||||
group_columns = group_aliases.zip(group_fields)
|
||||
|
||||
column = aggregate_column(column_name)
|
||||
column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
|
||||
select_value = operation_over_aggregate_column(column_name, operation, distinct)
|
||||
select_value = operation_over_aggregate_column(column, operation, distinct)
|
||||
select_value.as(column_alias)
|
||||
|
||||
select_values = [select_value]
|
||||
|
@ -365,8 +366,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
||||
calculated_data.columns.each_with_object({}).with_index do |(column, hash), i|
|
||||
hash[column] = row[i]
|
||||
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
||||
hash[col_name] = row[i]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -377,7 +378,7 @@ module ActiveRecord
|
|||
key = key_records[key] if associated
|
||||
|
||||
result[key] = type_cast_calculated_value(row[column_alias], operation) do |value|
|
||||
if type ||= klass.attribute_types[column_name.to_s]
|
||||
if type ||= column.try(:type_caster) || klass.attribute_types[column_name.to_s]
|
||||
type.deserialize(value)
|
||||
else
|
||||
value
|
||||
|
|
|
@ -1272,6 +1272,9 @@ module ActiveRecord
|
|||
|
||||
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
||||
arel_attribute(field)
|
||||
elsif field.match?(/\A\w+\.\w+\z/)
|
||||
table, column = field.split(".")
|
||||
predicate_builder.resolve_arel_attribute(table, column)
|
||||
else
|
||||
yield field
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ require "models/contract"
|
|||
require "models/edge"
|
||||
require "models/organization"
|
||||
require "models/possession"
|
||||
require "models/author"
|
||||
require "models/topic"
|
||||
require "models/reply"
|
||||
require "models/numeric_data"
|
||||
|
@ -22,7 +23,7 @@ require "models/rating"
|
|||
require "support/stubs/strong_parameters"
|
||||
|
||||
class CalculationsTest < ActiveRecord::TestCase
|
||||
fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books, :posts, :comments
|
||||
fixtures :companies, :accounts, :authors, :topics, :speedometers, :minivans, :books, :posts, :comments
|
||||
|
||||
def test_should_sum_field
|
||||
assert_equal 318, Account.sum(:credit_limit)
|
||||
|
@ -1098,6 +1099,29 @@ class CalculationsTest < ActiveRecord::TestCase
|
|||
assert_equal expected, actual
|
||||
assert_instance_of time_class, actual[true]
|
||||
assert_instance_of time_class, actual[true]
|
||||
|
||||
actual = Author.joins(:topics).maximum(:"topics.written_on")
|
||||
assert_equal Time.utc(2004, 7, 15, 14, 28, 0, 9900), actual
|
||||
assert_instance_of time_class, actual
|
||||
|
||||
actual = Author.joins(:topics).minimum(:"topics.written_on")
|
||||
assert_equal Time.utc(2003, 7, 16, 14, 28, 11, 223300), actual
|
||||
assert_instance_of time_class, actual
|
||||
|
||||
expected = {
|
||||
1 => Time.utc(2003, 7, 16, 14, 28, 11, 223300),
|
||||
2 => Time.utc(2004, 7, 15, 14, 28, 0, 9900),
|
||||
}
|
||||
|
||||
actual = Author.joins(:topics).group(:id).maximum(:"topics.written_on")
|
||||
assert_equal expected, actual
|
||||
assert_instance_of time_class, actual[1]
|
||||
assert_instance_of time_class, actual[2]
|
||||
|
||||
actual = Author.joins(:topics).group(:id).minimum(:"topics.written_on")
|
||||
assert_equal expected, actual
|
||||
assert_instance_of time_class, actual[1]
|
||||
assert_instance_of time_class, actual[2]
|
||||
end
|
||||
private :assert_minimum_and_maximum_on_time_attributes
|
||||
|
||||
|
|
|
@ -174,6 +174,8 @@ class Author < ActiveRecord::Base
|
|||
has_many :top_posts, -> { order(id: :asc) }, class_name: "Post"
|
||||
has_many :other_top_posts, -> { order(id: :asc) }, class_name: "Post"
|
||||
|
||||
has_many :topics, primary_key: "name", foreign_key: "author_name"
|
||||
|
||||
attr_accessor :post_log
|
||||
after_initialize :set_post_log
|
||||
|
||||
|
|
Loading…
Reference in a new issue