提交 5f59eac2 编写于 作者: R Ryuta Kamizono

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.
上级 8d597b54
...@@ -281,8 +281,7 @@ def aggregate_column(column_name) ...@@ -281,8 +281,7 @@ def aggregate_column(column_name)
end end
end end
def operation_over_aggregate_column(column_name, operation, distinct) def operation_over_aggregate_column(column, operation, distinct)
column = aggregate_column(column_name)
operation == "count" ? column.count(distinct) : column.send(operation) operation == "count" ? column.count(distinct) : column.send(operation)
end end
...@@ -296,7 +295,8 @@ def execute_simple_calculation(operation, column_name, distinct) #:nodoc: ...@@ -296,7 +295,8 @@ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
relation = unscope(:order).distinct!(false) 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 select_value.distinct = true if operation == "sum" && distinct
relation.select_values = [select_value] relation.select_values = [select_value]
...@@ -307,7 +307,7 @@ def execute_simple_calculation(operation, column_name, distinct) #:nodoc: ...@@ -307,7 +307,7 @@ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) } result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
type_cast_calculated_value(result.cast_values.first, operation) do |value| 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) type.deserialize(value)
else else
value value
...@@ -331,8 +331,9 @@ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: ...@@ -331,8 +331,9 @@ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
} }
group_columns = group_aliases.zip(group_fields) group_columns = group_aliases.zip(group_fields)
column = aggregate_column(column_name)
column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}") 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_value.as(column_alias)
select_values = [select_value] select_values = [select_value]
...@@ -365,8 +366,8 @@ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: ...@@ -365,8 +366,8 @@ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
end end
hash_rows = calculated_data.cast_values(key_types).map! do |row| hash_rows = calculated_data.cast_values(key_types).map! do |row|
calculated_data.columns.each_with_object({}).with_index do |(column, hash), i| calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
hash[column] = row[i] hash[col_name] = row[i]
end end
end end
...@@ -377,7 +378,7 @@ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: ...@@ -377,7 +378,7 @@ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
key = key_records[key] if associated key = key_records[key] if associated
result[key] = type_cast_calculated_value(row[column_alias], operation) do |value| 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) type.deserialize(value)
else else
value value
......
...@@ -1272,6 +1272,9 @@ def arel_column(field) ...@@ -1272,6 +1272,9 @@ def arel_column(field)
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from)) if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
arel_attribute(field) arel_attribute(field)
elsif field.match?(/\A\w+\.\w+\z/)
table, column = field.split(".")
predicate_builder.resolve_arel_attribute(table, column)
else else
yield field yield field
end end
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
require "models/edge" require "models/edge"
require "models/organization" require "models/organization"
require "models/possession" require "models/possession"
require "models/author"
require "models/topic" require "models/topic"
require "models/reply" require "models/reply"
require "models/numeric_data" require "models/numeric_data"
...@@ -22,7 +23,7 @@ ...@@ -22,7 +23,7 @@
require "support/stubs/strong_parameters" require "support/stubs/strong_parameters"
class CalculationsTest < ActiveRecord::TestCase 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 def test_should_sum_field
assert_equal 318, Account.sum(:credit_limit) assert_equal 318, Account.sum(:credit_limit)
...@@ -1098,6 +1099,29 @@ def assert_minimum_and_maximum_on_time_attributes(time_class) ...@@ -1098,6 +1099,29 @@ def assert_minimum_and_maximum_on_time_attributes(time_class)
assert_equal expected, actual assert_equal expected, actual
assert_instance_of time_class, actual[true] assert_instance_of time_class, actual[true]
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 end
private :assert_minimum_and_maximum_on_time_attributes private :assert_minimum_and_maximum_on_time_attributes
......
...@@ -174,6 +174,8 @@ def extension_method; end ...@@ -174,6 +174,8 @@ def extension_method; end
has_many :top_posts, -> { order(id: :asc) }, class_name: "Post" has_many :top_posts, -> { order(id: :asc) }, class_name: "Post"
has_many :other_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 attr_accessor :post_log
after_initialize :set_post_log after_initialize :set_post_log
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册