2
rails 3.2

Invoice.sum(:amount)

Accurately returns the sum of all values in the amount column.

If I do the following:

invoices = Invoice.all

And then:

invoices.sum(:amount)

I get the following error:

NoMethodError: undefined method `+'

Is there a way to do this?

3 Answers 3

3

If you are certain that there is not a single amount == nil then what you tried, should work. If you have a single nil column this would result in NoMethodError: undefined method '+' for nil. In this case you can either do invoices.map(&:amount).compact.sum or invoices.sum { |i| i.amount || 0 }

Sign up to request clarification or add additional context in comments.

1 Comment

I have no nil values and I still get the error message
3

This is because #sum is an ActiveRecord method. What it is essentially doing is building the aggregation into your database query like so (in SQL):

SELECT SUM(name_of_column_to_sum) FROM table_name;

If you want to return the values and store them in a variable (#all),

SELECT * FROM table_name;

and THEN sum them, you are no longer working with an ActiveRecord query, but rather an ActiveRecord relation (an array-like object that is returned from the ActiveRecord query). So, at that point you would need to use vanilla Ruby to sum up. You could use #map and then #sum, but it would be faster and cleaner to just use #inject, like so:

invoices.inject(0){ |sum, invoice| sum + invoice.amount }

2 Comments

Used this to get rid of N+1 query's I was have. Thank you for posting this BTW. Was wondering if you know if this is a proper way to get rid of the N+1? Looking at my logs it does do that but I'm unaware if it is the "proper way".
@Chrismisballs the "proper" way is often different depending on your use case, however, a good rule of thumb is this.. Avoid loading things into memory that you don't have to. In this case, .all is building a whole bunch of activerecord objects in memory just to iterate over one value. It's smarter to use the .sum activerecord method in this case than to use .inject. For more on that check out this post
1

You have to use map function, like this:

invoices.map(&:amount).sum

2 Comments

This is iterating twice over the same array, which is not optimal. It will work, but also does not address the initial question which is 'why' doesn't invoices.sum(:amount) work. It's because the OP is mixing up activerecord and vanilla ruby. See my answer for details.
@Gustavo Fe If I need sum two colums like invoices.map(&:amount + &:fees ).sum is it possible?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.