Skip to content
This repository

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

Closed
wants to merge 3 commits into from

6 participants

chrisfinne Aaron Patterson Isaac Sanders Rafael Mendonça França Eugene Gilburg Carlos Antonio da Silva
chrisfinne

also added tests and documentation about sum allowing a block.

I've been burned too many times by thinking I'm working with an Array. If we can sum on ActiveRecord::Relations like we do with Arrays, why not count with a block as well?

persons = Person.where(:gender=>'male')
persons.count{|person| false} # should equal 0, but will equal persons.size
activerecord/lib/active_record/relation/calculations.rb
((12 lines not shown))
56 63 def count(column_name = nil, options = {})
57   - column_name, options = nil, column_name if column_name.is_a?(Hash)
58   - calculate(:count, column_name, options)
  64 + if block_given?
  65 + self.to_a.count {|*block_args| yield(*block_args)}
1
Aaron Patterson Owner

Can you change this to just take one argument? Normal array count only takes one arg, so I'd rather not be splatting back and forth in here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Aaron Patterson
Owner

Hi, can you just make the change to *args, and I'll apply this. Thanks.

chrisfinne

Just pushed an update to this branch to use *args

Someone just fixed something so that Account.count{false}==0 now works. Changed the test and added an example to the doc.

Removed an inaccurate documentation Note about Person.count(:all) not working.

Added a ArgumentError to pass the existing test test_count_with_too_many_parameters_raises

Eugene Gilburg egilburg commented on the diff
activerecord/lib/active_record/relation/calculations.rb
@@ -51,11 +52,25 @@ module Calculations
51 52 # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
52 53 # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
53 54 #
54   - # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
55   - # Use Person.count instead.
56   - def count(column_name = nil, options = {})
57   - column_name, options = nil, column_name if column_name.is_a?(Hash)
58   - calculate(:count, column_name, options)
  55 + # Examples for count with a block:
1

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
chrisfinne

@tenderlove let me know if there's anything changes needed, e.g. cleaning up the docs as @egilburg suggests

Isaac Sanders

Is this still an issue?

chrisfinne

It seems to have dropped off the radar. The patch hasn't been applied.

Isaac Sanders

@tenderlove Do you still want this?

Carlos Antonio da Silva carlosantoniodasilva referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Carlos Antonio da Silva carlosantoniodasilva referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Carlos Antonio da Silva carlosantoniodasilva referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Rafael Mendonça França
Owner

Closed by 560aa33

Rafael Mendonça França
Owner

Oooops, it is not in the rails repository yet.

Rafael Mendonça França rafaelfranca reopened this
Isaac Sanders

@rafaelfranca It appears to be...

Rafael Mendonça França
Owner

@carlosantoniodasilva told me that it is not. This is a crazy behavior of Github. It is only in the @carlosantoniodasilva's fork

Carlos Antonio da Silva carlosantoniodasilva referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
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.
33 activerecord/lib/active_record/relation/calculations.rb
@@ -3,12 +3,13 @@
3 3
4 4 module ActiveRecord
5 5 module Calculations
6   - # Count operates using three different approaches.
  6 + # Count operates using four different approaches.
7 7 #
8 8 # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
9 9 # * Count using column: By passing a column name to count, it will return a count of all the
10 10 # rows for the model with supplied column present.
11 11 # * Count using options will find the row count matched by the options used.
  12 + # * Count using a block will acts as Array#count
12 13 #
13 14 # The third approach, count using options, accepts an option hash as the only parameter. The options are:
14 15 #
@@ -51,11 +52,25 @@ module Calculations
51 52 # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
52 53 # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
53 54 #
54   - # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
55   - # Use Person.count instead.
56   - def count(column_name = nil, options = {})
57   - column_name, options = nil, column_name if column_name.is_a?(Hash)
58   - calculate(:count, column_name, options)
  55 + # Examples for count with a block:
  56 + # Person.where(:conditions => "age > 26").count{|person| person.gender=='female' }
  57 + #
  58 + # Person.count{|person| person.age > 26 && person.gender=='female' }
  59 + def count(*args)
  60 + if block_given?
  61 + self.to_a.count {|*block_args| yield(*block_args)}
  62 + else
  63 + raise ArgumentError, "Too many arguments. No more than 2 are allowed." if args.size > 2
  64 + if args.blank?
  65 + column_name, options = nil, {}
  66 + elsif args.first.is_a?(Hash)
  67 + column_name, options = nil, args.first
  68 + else
  69 + column_name = args.shift
  70 + options = args.shift || {}
  71 + end
  72 + calculate(:count, column_name, options)
  73 + end
59 74 end
60 75
61 76 # Calculates the average value on a given column. Returns +nil+ if there's
@@ -84,6 +99,12 @@ def maximum(column_name, options = {})
84 99 calculate(:maximum, column_name, options)
85 100 end
86 101
  102 + # Sum operates using two different approaches:
  103 + # * Sum using a block will act as Array#sum
  104 + #
  105 + # Person.where('age > 100').sum{|person| person.age - 100} # => 11
  106 + #
  107 + # * Sum as a Calculation on a given column.
87 108 # Calculates the sum of values on a given column. The value is returned
88 109 # with the same data type of the column, 0 if there's no row. See
89 110 # +calculate+ for examples with options.
17 activerecord/test/cases/calculations_test.rb
@@ -398,6 +398,23 @@ def test_count_with_from_option
398 398 assert_equal Company.count(:type, :conditions => {:type => "Firm"}),
399 399 Company.count(:type, :conditions => {:type => "Firm"}, :from => 'companies')
400 400 end
  401 +
  402 + def test_count_with_block_acts_as_array
  403 + accounts = Account.where('id > 0')
  404 + assert_equal Account.count, accounts.count{ true }
  405 + assert_equal 0, accounts.count{ false }
  406 + assert_equal Account.where('credit_limit > 50').size, accounts.count{|account| account.credit_limit > 50}
  407 + assert_equal Account.where('credit_limit > 50').size, Account.count{|account| account.credit_limit > 50}
  408 + assert_equal Account.count, Account.count{true}
  409 + assert_equal 0, Account.count{false}
  410 + end
  411 +
  412 + def test_sum_with_block_acts_as_array
  413 + accounts = Account.where('id > 0')
  414 + assert_equal Account.sum(:credit_limit), accounts.sum{|account| account.credit_limit }
  415 + assert_equal Account.sum(:credit_limit) + Account.count, accounts.sum{|account| account.credit_limit + 1 }
  416 + assert_equal 0, accounts.sum{|account| 0 }
  417 + end
401 418
402 419 def test_sum_with_from_option
403 420 assert_equal Account.sum(:credit_limit), Account.sum(:credit_limit, :from => 'accounts')

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.