Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

ActiveRecord: sum expression returns string '0' for no records, fixed #7439

Merged
merged 1 commit into from

6 participants

@refractalize

The bug:

Account.where('1 = 2').sum("2 * credit_limit") => '0'

That is, sum with an expression (not a column name) on a condition or collection that returns no records returns the string "0", whereas it should return an integer, 0.

@pixeltrix
Owner

There was a reason it returned a string - calling to_d on a Fixnum in Ruby 1.8 would give a NoMethodError. This is no longer the case in Ruby 1.9 so it's probably okay to change.

The relevant Lighthouse tickets are 1066 and 4633 - to quote myself from 1066:

The problem with type casting to float for decimal columns and then back to decimal is that you may introduce floating point precision errors. My view would be that if we are doing a straightforward sum on a column then typecast to that column's type otherwise return a string and leave it to the developer to cast to whatever they want. This would also apply to average as well.

@jfeaver

I ran the tests, everything passes, and the code looks legit.

The test matches the bug and matches the style of the tests around it.

@refractalize

I haven't had a chance to test this under 1.8.7 yet, but having read through the code it appears as though it handles a missing to_d by casting to string first, see here in connection_adapters/column.rb:

def value_to_decimal(value)
  # Using .class is faster than .is_a? and
  # subclasses of BigDecimal will be handled
  # in the else clause
  if value.class == BigDecimal
    value
  elsif value.respond_to?(:to_d)
    value.to_d
  else
    value.to_s.to_d
  end
end

BTW: is there an easy way to run the tests under 1.8.7? I had trouble running bundler (due to use of new hash syntax in rails/Gemfile)...

@steveklabnik
Collaborator

I haven't had a chance to test this under 1.8.7 yet,

You don't need to. We don't support 1.8.7 on master.

@carlosantoniodasilva

@refractalize the change looks fine, but it needs a changelog entry, can you please add that and squash everything into one commit if necessary? Thanks.

@refractalize

@carlosantoniodasilva - took a while to get around to the changelog entry, all done now.

@tenderlove tenderlove merged commit 0a78417 into rails:master
@carlosantoniodasilva

@refractalize awesome, thanks, and sorry for the delay :)

@refractalize

Nice one, thanks all!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
5 activerecord/CHANGELOG.md
@@ -906,3 +906,8 @@
*Aaron Patterson*
Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activerecord/CHANGELOG.md) for previous changes.
+
+* Fix bug where sum(expression) returns string '0' for no matching records
+ Fixes #7439
+
+ *Tim Macfarlane*
View
2  activerecord/lib/active_record/relation/calculations.rb
@@ -349,7 +349,7 @@ def column_for(field)
def type_cast_calculated_value(value, column, operation = nil)
case operation
when 'count' then value.to_i
- when 'sum' then type_cast_using_column(value || '0', column)
+ when 'sum' then type_cast_using_column(value || 0, column)
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
else type_cast_using_column(value, column)
end
View
4 activerecord/test/cases/calculations_test.rb
@@ -378,6 +378,10 @@ def test_should_sum_expression
end
end
+ def test_sum_expression_returns_zero_when_no_records_to_sum
+ assert_equal 0, Account.where('1 = 2').sum("2 * credit_limit")
+ end
+
def test_count_with_from_option
assert_equal Company.count(:all), Company.from('companies').count(:all)
assert_equal Account.where("credit_limit = 50").count(:all),
Something went wrong with that request. Please try again.