Allow blocks for count with ActiveRecord::Relation like we do with sum #4003

Closed
wants to merge 3 commits into
from
View
33 activerecord/lib/active_record/relation/calculations.rb
@@ -3,12 +3,13 @@
module ActiveRecord
module Calculations
- # Count operates using three different approaches.
+ # Count operates using four different approaches.
#
# * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
# * Count using column: By passing a column name to count, it will return a count of all the
# rows for the model with supplied column present.
# * Count using options will find the row count matched by the options used.
+ # * Count using a block will acts as Array#count
#
# The third approach, count using options, accepts an option hash as the only parameter. The options are:
#
@@ -51,11 +52,25 @@ module Calculations
# Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
# Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
#
- # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
- # Use Person.count instead.
- def count(column_name = nil, options = {})
- column_name, options = nil, column_name if column_name.is_a?(Hash)
- calculate(:count, column_name, options)
+ # Examples for count with a block:
@egilburg
egilburg added a line comment Dec 18, 2011

This (and other comments here) seem to use old :conditions => syntax? Isn't new one simply Person.where("age > 26"), etc?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ # Person.where(:conditions => "age > 26").count{|person| person.gender=='female' }
+ #
+ # Person.count{|person| person.age > 26 && person.gender=='female' }
+ def count(*args)
+ if block_given?
+ self.to_a.count {|*block_args| yield(*block_args)}
+ else
+ raise ArgumentError, "Too many arguments. No more than 2 are allowed." if args.size > 2
+ if args.blank?
+ column_name, options = nil, {}
+ elsif args.first.is_a?(Hash)
+ column_name, options = nil, args.first
+ else
+ column_name = args.shift
+ options = args.shift || {}
+ end
+ calculate(:count, column_name, options)
+ end
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -84,6 +99,12 @@ def maximum(column_name, options = {})
calculate(:maximum, column_name, options)
end
+ # Sum operates using two different approaches:
+ # * Sum using a block will act as Array#sum
+ #
+ # Person.where('age > 100').sum{|person| person.age - 100} # => 11
+ #
+ # * Sum as a Calculation on a given column.
# Calculates the sum of values on a given column. The value is returned
# with the same data type of the column, 0 if there's no row. See
# +calculate+ for examples with options.
View
17 activerecord/test/cases/calculations_test.rb
@@ -398,6 +398,23 @@ def test_count_with_from_option
assert_equal Company.count(:type, :conditions => {:type => "Firm"}),
Company.count(:type, :conditions => {:type => "Firm"}, :from => 'companies')
end
+
+ def test_count_with_block_acts_as_array
+ accounts = Account.where('id > 0')
+ assert_equal Account.count, accounts.count{ true }
+ assert_equal 0, accounts.count{ false }
+ assert_equal Account.where('credit_limit > 50').size, accounts.count{|account| account.credit_limit > 50}
+ assert_equal Account.where('credit_limit > 50').size, Account.count{|account| account.credit_limit > 50}
+ assert_equal Account.count, Account.count{true}
+ assert_equal 0, Account.count{false}
+ end
+
+ def test_sum_with_block_acts_as_array
+ accounts = Account.where('id > 0')
+ assert_equal Account.sum(:credit_limit), accounts.sum{|account| account.credit_limit }
+ assert_equal Account.sum(:credit_limit) + Account.count, accounts.sum{|account| account.credit_limit + 1 }
+ assert_equal 0, accounts.sum{|account| 0 }
+ end
def test_sum_with_from_option
assert_equal Account.sum(:credit_limit), Account.sum(:credit_limit, :from => 'accounts')