Ticket #2597: counting_and_limiting_for_named_associations.2.patch
| File counting_and_limiting_for_named_associations.2.patch, 12.2 kB (added by jeremy@jthopple.com, 3 years ago) |
|---|
-
activerecord/test/base_test.rb
old new 1051 1051 "LEFT JOIN comments ON posts.id=comments.post_id") 1052 1052 end 1053 1053 assert_equal res, res2 1054 1055 res3 = res + 1 1056 assert_nothing_raised do 1057 res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", 1058 :joins => "LEFT JOIN comments ON posts.id=comments.post_id") 1059 end 1060 assert_equal res, res3 1054 1061 end 1055 1062 1056 1063 def test_clear_association_cache_stored -
activerecord/test/associations_go_eager_test.rb
old new 120 120 end 121 121 122 122 def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers 123 assert_raises(ArgumentError) do 124 posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) 125 end 123 posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) 124 assert_equal 2, posts.size 125 126 count = Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) 127 assert_equal count, posts.size 126 128 end 127 129 128 130 def test_eager_with_has_many_and_limit_with_no_results … … 141 143 end 142 144 143 145 def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers 144 assert_raises(ArgumentError) do 145 posts = authors(:david).posts.find(:all, 146 :include => :comments, 147 :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'", 148 :limit => 2 149 ) 150 end 146 posts = authors(:david).posts.find(:all, 147 :include => :comments, 148 :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'", 149 :limit => 2 150 ) 151 assert_equal 2, posts.size 152 153 count = Post.count( 154 :include => [ :comments, :author ], 155 :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')", 156 :limit => 2 157 ) 158 assert_equal count, posts.size 151 159 end 152 160 153 161 def test_eager_association_loading_with_habtm -
activerecord/lib/active_record/associations.rb
old new 776 776 end 777 777 end 778 778 end 779 780 def count_with_associations(options = {}) 781 reflections = reflect_on_included_associations(options[:include]) 782 primary_key = "#{table_name}.#{self.primary_key}" 783 return count_by_sql(construct_counter_sql_with_included_associations(options, primary_key, reflections)) 784 end 779 785 780 786 def find_with_associations(options = {}) 781 787 reflections = reflect_on_included_associations(options[:include]) … … 988 994 "#{name} Load Including Associations" 989 995 ) 990 996 end 997 998 def construct_counter_sql_with_included_associations(options, primary_key, reflections) 999 sql = "SELECT COUNT(DISTINCT #{primary_key}) FROM #{table_name} " 1000 sql << reflections.collect { |reflection| association_join(reflection) }.to_s 1001 sql << "#{options[:joins]} " if options[:joins] 991 1002 1003 add_conditions!(sql, options[:conditions]) 1004 add_sti_conditions!(sql, reflections) 1005 add_limited_ids_condition!(sql, options, reflections) if !using_limitable_reflections?(reflections) && options[:limit] 1006 1007 add_limit!(sql, options) if using_limitable_reflections?(reflections) 1008 1009 return sanitize_sql(sql) 1010 end 1011 992 1012 def construct_finder_sql_with_included_associations(options, schema_abbreviations, reflections) 993 1013 sql = "SELECT #{column_aliases(schema_abbreviations)} FROM #{table_name} " 994 1014 sql << reflections.collect { |reflection| association_join(reflection) }.to_s 995 1015 sql << "#{options[:joins]} " if options[:joins] 996 1016 997 1017 add_conditions!(sql, options[:conditions]) 998 1018 add_sti_conditions!(sql, reflections) 999 add_limited_ids_condition!(sql, options ) if !using_limitable_reflections?(reflections) && options[:limit]1019 add_limited_ids_condition!(sql, options, reflections) if !using_limitable_reflections?(reflections) && options[:limit] 1000 1020 1001 1021 sql << "ORDER BY #{options[:order]} " if options[:order] 1002 1022 1003 1023 add_limit!(sql, options) if using_limitable_reflections?(reflections) 1004 1024 1005 1025 return sanitize_sql(sql) 1006 1026 end 1007 1008 def add_limited_ids_condition!(sql, options )1009 unless (id_list = select_limited_ids_list(options )).empty?1027 1028 def add_limited_ids_condition!(sql, options, reflections) 1029 unless (id_list = select_limited_ids_list(options, reflections)).empty? 1010 1030 sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) " 1011 1031 end 1012 1032 end 1013 1014 def select_limited_ids_list(options )1033 1034 def select_limited_ids_list(options, reflections) 1015 1035 connection.select_values( 1016 construct_finder_sql_for_association_limiting(options ),1036 construct_finder_sql_for_association_limiting(options, reflections), 1017 1037 "#{name} Load IDs For Limited Eager Loading" 1018 1038 ).collect { |id| connection.quote(id) }.join(", ") 1019 1039 end 1020 1021 def construct_finder_sql_for_association_limiting(options) 1022 raise(ArgumentError, "Limited eager loads and conditions on the eager tables is incompatible") if include_eager_conditions?(options) 1040 1041 def construct_finder_sql_for_association_limiting(options, reflections) 1042 sql = "SELECT " 1043 sql << "DISTINCT #{table_name}." if include_eager_conditions?(options) 1044 sql << "#{primary_key} FROM #{table_name} " 1023 1045 1024 sql = "SELECT #{primary_key} FROM #{table_name} " 1046 if include_eager_conditions?(options) 1047 sql << reflections.collect { |reflection| association_join(reflection) }.to_s 1048 sql << "#{options[:joins]} " if options[:joins] 1049 end 1050 1025 1051 add_conditions!(sql, options[:conditions]) 1026 1052 sql << "ORDER BY #{options[:order]} " if options[:order] 1027 1053 add_limit!(sql, options) -
activerecord/lib/active_record/base.rb
old new 502 502 connection.delete(sql, "#{name} Delete all") 503 503 end 504 504 505 # Returns the number of records that meet the +conditions+. Zero is returned if no records match. Example: 506 # Product.count "sales > 1" 507 def count(conditions = nil, joins = nil) 505 # Count operates using three different approaches. 506 # 507 # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model. 508 # * Count by conditions or joins: For backwards compatibility, you can pass in +conditions+ and +joins+ as individual parameters. 509 # * Count using options will find the row count matched by the options used. 510 # 511 # The last approach, count using options, accepts an option hash as the only parameter. The options are: 512 # 513 # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro. 514 # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed). 515 # The records will be returned read-only since they will have attributes that do not correspond to the table's columns. 516 # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer 517 # to already defined associations. When using named associations count returns the number DISTINCT items for the model you're counting. 518 # See eager loading under Associations. 519 # 520 # Examples for counting all: 521 # Person.count # returns the total count of all people 522 # 523 # Examples for count by +conditions+ and +joins+ (for backwards compatibility): 524 # Person.count("age > 26") # returns the number of people older than 26 525 # Person.find("age > 26 AND job.salary > 60000", "LEFT JOIN jobs on jobs.person_id = person.id") # returns the total number of rows matching the conditions and joins fetched by SELECT COUNT(*). 526 # 527 # Examples for count with options: 528 # Person.count(:conditions => "age > 26") 529 # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN. 530 # Person.count(:conditions => "age > 26 AND job.salary > 60000", :joins => "LEFT JOIN jobs on jobs.person_id = person.id") # finds the number of rows matching the conditions and joins. 531 def count(*args) 532 options = {} 533 534 #For backwards compatibility, we need to handle both count(conditions=nil, joins=nil) or count(options={}). 535 if args.size >= 0 and args.size <= 2 536 if args.first.is_a?(Hash) 537 options = args.first 538 #should we verify the options hash??? 539 else 540 #Handle legacy paramter options: def count(conditions=nil, joins=nil) 541 options.merge!(:conditions => args[0]) if args.length > 0 542 options.merge!(:joins => args[1]) if args.length > 1 543 end 544 else 545 raise(ArgumentError, "Unexpected parameters passed to count(*args): expected either count(conditions=nil, joins=nil) or count(options={})") 546 end 547 548 options[:include] ? count_with_associations(options) : count_by_sql(construct_counter_sql(options)) 549 end 550 551 def construct_counter_sql(options) 508 552 sql = "SELECT COUNT(*) FROM #{table_name} " 509 sql << " #{ joins} " if joins510 add_conditions!(sql, conditions)511 count_by_sql(sql)553 sql << " #{options[:joins]} " if options[:joins] 554 add_conditions!(sql, options[:conditions]) 555 sql 512 556 end 513 557 514 558 # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. -
actionpack/lib/action_controller/pagination.rb
old new 163 163 # Returns the total number of items in the collection to be paginated for 164 164 # the +model+ and given +conditions+. Override this method to implement a 165 165 # custom counter. 166 def count_collection_for_pagination(model, conditions, joins) 167 model.count(conditions,joins) 166 def count_collection_for_pagination(model, options) 167 model.count(:conditions => options[:conditions], 168 :joins => options[:join] || options[:joins], :include => options[:include]) 168 169 end 169 170 170 171 # Returns a collection of items for the given +model+ and +options[conditions]+, 171 172 # ordered by +options[order]+, for the current page in the given +paginator+. 172 173 # Override this method to implement a custom finder. … … 185 186 def paginator_and_collection_for(collection_id, options) #:nodoc: 186 187 klass = options[:class_name].constantize 187 188 page = @params[options[:parameter]] 188 count = count_collection_for_pagination(klass, options[:conditions], 189 options[:join] || options[:joins]) 190 189 count = count_collection_for_pagination(klass, options) 191 190 paginator = Paginator.new(self, count, options[:per_page], page) 192 191 collection = find_collection_for_pagination(klass, options, paginator) 193 192 194 193 return paginator, collection 195 194 end 196 195