public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Refactored AssociationCollection#count for uniformity and Ruby 1.8.7 support.

[#831 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
Ernie Miller (author)
Thu Aug 28 11:01:42 -0700 2008
jeremy (committer)
Thu Aug 28 11:58:25 -0700 2008
commit  44af2efa2c7391681968c827ca47201a0a02e974
tree    a25ede2a88799c281468fcdaa4321d6048cee240
parent  ce4d13861dc54a1ac7fbe411327b9a2427f95366
...
1164
1165
1166
 
 
 
1167
1168
1169
...
1164
1165
1166
1167
1168
1169
1170
1171
1172
0
@@ -1164,6 +1164,9 @@ module ActiveRecord
0
       #   If true, duplicate associated objects will be ignored by accessors and query methods.
0
       # [:finder_sql]
0
       #   Overwrite the default generated SQL statement used to fetch the association with a manual statement
0
+      # [:counter_sql]
0
+      #   Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
0
+      #   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>.
0
       # [:delete_sql]
0
       #   Overwrite the default generated SQL statement used to remove links between the associated
0
       #   classes with a manual statement.
...
128
129
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
132
133
...
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
0
@@ -128,6 +128,35 @@ module ActiveRecord
0
         end
0
       end
0
 
0
+      # Count all records using SQL. If the +:counter_sql+ option is set for the association, it will
0
+      # be used for the query. If no +:counter_sql+ was supplied, but +:finder_sql+ was set, the
0
+      # descendant's +construct_sql+ method will have set :counter_sql automatically.
0
+      # Otherwise, construct options and pass them with scope to the target class's +count+.
0
+      def count(*args)
0
+        if @reflection.options[:counter_sql]
0
+          @reflection.klass.count_by_sql(@counter_sql)
0
+        else
0
+          column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
0
+          if @reflection.options[:uniq]
0
+            # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
0
+            column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
0
+            options.merge!(:distinct => true)
0
+          end
0
+
0
+          value = @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
0
+
0
+          limit  = @reflection.options[:limit]
0
+          offset = @reflection.options[:offset]
0
+
0
+          if limit || offset
0
+            [ [value - offset.to_i, 0].max, limit.to_i ].min
0
+          else
0
+            value
0
+          end
0
+        end
0
+      end
0
+
0
+
0
       # Remove +records+ from this association.  Does not destroy +records+.
0
       def delete(*records)
0
         records = flatten_deeper(records)
...
78
79
80
 
 
 
 
 
 
 
 
 
 
81
82
83
...
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
0
@@ -78,6 +78,16 @@ module ActiveRecord
0
           end
0
 
0
           @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}"
0
+
0
+          if @reflection.options[:counter_sql]
0
+            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
0
+          elsif @reflection.options[:finder_sql]
0
+            # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
0
+            @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
0
+            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
0
+          else
0
+            @counter_sql = @finder_sql
0
+          end
0
         end
0
 
0
         def construct_scope
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
...
1
2
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
5
6
0
@@ -1,32 +1,6 @@
0
 module ActiveRecord
0
   module Associations
0
     class HasManyAssociation < AssociationCollection #:nodoc:
0
-      # Count the number of associated records. All arguments are optional.
0
-      def count(*args)
0
-        if @reflection.options[:counter_sql]
0
-          @reflection.klass.count_by_sql(@counter_sql)
0
-        elsif @reflection.options[:finder_sql]
0
-          @reflection.klass.count_by_sql(@finder_sql)
0
-        else
0
-          column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)          
0
-          options[:conditions] = options[:conditions].blank? ?
0
-            @finder_sql :
0
-            @finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
0
-          options[:include] ||= @reflection.options[:include]
0
-
0
-          value = @reflection.klass.count(column_name, options)
0
-
0
-          limit  = @reflection.options[:limit]
0
-          offset = @reflection.options[:offset]
0
-
0
-          if limit || offset
0
-            [ [value - offset.to_i, 0].max, limit.to_i ].min
0
-          else
0
-            value
0
-          end
0
-        end
0
-      end
0
-
0
       protected
0
         def owner_quoted_id
0
           if @reflection.options[:primary_key]
...
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
...
31
32
33
 
 
 
 
 
 
 
 
 
 
34
35
36
0
@@ -31,16 +31,6 @@ module ActiveRecord
0
         return count
0
       end
0
       
0
-      def count(*args)
0
-        column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
0
-        if @reflection.options[:uniq]
0
-          # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL statement.
0
-          column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
0
-          options.merge!(:distinct => true) 
0
-        end
0
-        @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) } 
0
-      end
0
-
0
       protected
0
         def construct_find_options!(options)
0
           options[:select]  = construct_select(options[:select])
...
703
704
705
 
 
 
 
 
 
 
706
...
703
704
705
706
707
708
709
710
711
712
713
0
@@ -703,4 +703,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
0
     # due to Unknown column 'authors.id'
0
     assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog')
0
   end
0
+
0
+  def test_counting_on_habtm_association_and_not_array
0
+    david = Developer.find(1)
0
+    # Extra parameter just to make sure we aren't falling back to
0
+    # Array#count in Ruby >=1.8.7, which would raise an ArgumentError
0
+    assert_nothing_raised { david.projects.count(:all, :conditions => '1=1') }
0
+  end
0
 end

Comments