Skip to content

Commit 44af2ef

Browse files
Ernie Millerjeremy
authored andcommitted
Refactored AssociationCollection#count for uniformity and Ruby 1.8.7 support.
[#831 state:resolved] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
1 parent ce4d138 commit 44af2ef

File tree

6 files changed

+49
-36
lines changed

6 files changed

+49
-36
lines changed

activerecord/lib/active_record/associations.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,9 @@ def belongs_to(association_id, options = {})
11641164
# If true, duplicate associated objects will be ignored by accessors and query methods.
11651165
# [:finder_sql]
11661166
# Overwrite the default generated SQL statement used to fetch the association with a manual statement
1167+
# [:counter_sql]
1168+
# Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
1169+
# specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
11671170
# [:delete_sql]
11681171
# Overwrite the default generated SQL statement used to remove links between the associated
11691172
# classes with a manual statement.

activerecord/lib/active_record/associations/association_collection.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,35 @@ def sum(*args)
128128
end
129129
end
130130

131+
# Count all records using SQL. If the +:counter_sql+ option is set for the association, it will
132+
# be used for the query. If no +:counter_sql+ was supplied, but +:finder_sql+ was set, the
133+
# descendant's +construct_sql+ method will have set :counter_sql automatically.
134+
# Otherwise, construct options and pass them with scope to the target class's +count+.
135+
def count(*args)
136+
if @reflection.options[:counter_sql]
137+
@reflection.klass.count_by_sql(@counter_sql)
138+
else
139+
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
140+
if @reflection.options[:uniq]
141+
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
142+
column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
143+
options.merge!(:distinct => true)
144+
end
145+
146+
value = @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
147+
148+
limit = @reflection.options[:limit]
149+
offset = @reflection.options[:offset]
150+
151+
if limit || offset
152+
[ [value - offset.to_i, 0].max, limit.to_i ].min
153+
else
154+
value
155+
end
156+
end
157+
end
158+
159+
131160
# Remove +records+ from this association. Does not destroy +records+.
132161
def delete(*records)
133162
records = flatten_deeper(records)

activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ def construct_sql
7878
end
7979

8080
@join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
81+
82+
if @reflection.options[:counter_sql]
83+
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
84+
elsif @reflection.options[:finder_sql]
85+
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
86+
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
87+
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
88+
else
89+
@counter_sql = @finder_sql
90+
end
8191
end
8292

8393
def construct_scope

activerecord/lib/active_record/associations/has_many_association.rb

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,6 @@
11
module ActiveRecord
22
module Associations
33
class HasManyAssociation < AssociationCollection #:nodoc:
4-
# Count the number of associated records. All arguments are optional.
5-
def count(*args)
6-
if @reflection.options[:counter_sql]
7-
@reflection.klass.count_by_sql(@counter_sql)
8-
elsif @reflection.options[:finder_sql]
9-
@reflection.klass.count_by_sql(@finder_sql)
10-
else
11-
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
12-
options[:conditions] = options[:conditions].blank? ?
13-
@finder_sql :
14-
@finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
15-
options[:include] ||= @reflection.options[:include]
16-
17-
value = @reflection.klass.count(column_name, options)
18-
19-
limit = @reflection.options[:limit]
20-
offset = @reflection.options[:offset]
21-
22-
if limit || offset
23-
[ [value - offset.to_i, 0].max, limit.to_i ].min
24-
else
25-
value
26-
end
27-
end
28-
end
29-
304
protected
315
def owner_quoted_id
326
if @reflection.options[:primary_key]

activerecord/lib/active_record/associations/has_many_through_association.rb

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,6 @@ def size
3131
return count
3232
end
3333

34-
def count(*args)
35-
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
36-
if @reflection.options[:uniq]
37-
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL statement.
38-
column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
39-
options.merge!(:distinct => true)
40-
end
41-
@reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
42-
end
43-
4434
protected
4535
def construct_find_options!(options)
4636
options[:select] = construct_select(options[:select])

activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,4 +703,11 @@ def test_dynamic_find_should_respect_association_include
703703
# due to Unknown column 'authors.id'
704704
assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog')
705705
end
706+
707+
def test_counting_on_habtm_association_and_not_array
708+
david = Developer.find(1)
709+
# Extra parameter just to make sure we aren't falling back to
710+
# Array#count in Ruby >=1.8.7, which would raise an ArgumentError
711+
assert_nothing_raised { david.projects.count(:all, :conditions => '1=1') }
712+
end
706713
end

0 commit comments

Comments
 (0)