Ticket #2597: counting_and_limiting_for_named_associations.3.patch
| File counting_and_limiting_for_named_associations.3.patch, 13.8 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/connection_adapters/sqlite_adapter.rb
old new 98 98 def supports_migrations? #:nodoc: 99 99 true 100 100 end 101 102 def supports_count_distinct? #:nodoc: 103 false 104 end 101 105 102 106 def native_database_types #:nodoc: 103 107 { -
activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
old new 38 38 def supports_migrations? 39 39 false 40 40 end 41 42 # Does this adapter support using DISTINCT within COUNT? This is +true+ 43 # for all adapters except sqlite. 44 def supports_count_distinct? 45 true 46 end 41 47 42 48 # Should primary key values be selected from their corresponding 43 49 # sequence before the insert statement? If true, next_sequence_value -
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 return count_by_sql(construct_counter_sql_with_included_associations(options, reflections)) 783 end 779 784 780 785 def find_with_associations(options = {}) 781 786 reflections = reflect_on_included_associations(options[:include]) … … 988 993 "#{name} Load Including Associations" 989 994 ) 990 995 end 996 997 def construct_counter_sql_with_included_associations(options, reflections) 998 sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})" 999 1000 # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. 1001 if !Base.connection.supports_count_distinct? 1002 sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{table_name}.#{primary_key}" 1003 end 1004 1005 sql << " FROM #{table_name} " 1006 sql << reflections.collect { |reflection| association_join(reflection) }.to_s 1007 sql << "#{options[:joins]} " if options[:joins] 991 1008 1009 add_conditions!(sql, options[:conditions]) 1010 add_sti_conditions!(sql, reflections) 1011 add_limited_ids_condition!(sql, options, reflections) if !using_limitable_reflections?(reflections) && options[:limit] 1012 1013 add_limit!(sql, options) if using_limitable_reflections?(reflections) 1014 1015 if !Base.connection.supports_count_distinct? 1016 sql << ")" 1017 end 1018 1019 return sanitize_sql(sql) 1020 end 1021 992 1022 def construct_finder_sql_with_included_associations(options, schema_abbreviations, reflections) 993 1023 sql = "SELECT #{column_aliases(schema_abbreviations)} FROM #{table_name} " 994 1024 sql << reflections.collect { |reflection| association_join(reflection) }.to_s 995 1025 sql << "#{options[:joins]} " if options[:joins] 996 1026 997 1027 add_conditions!(sql, options[:conditions]) 998 1028 add_sti_conditions!(sql, reflections) 999 add_limited_ids_condition!(sql, options ) if !using_limitable_reflections?(reflections) && options[:limit]1029 add_limited_ids_condition!(sql, options, reflections) if !using_limitable_reflections?(reflections) && options[:limit] 1000 1030 1001 1031 sql << "ORDER BY #{options[:order]} " if options[:order] 1002 1032 1003 1033 add_limit!(sql, options) if using_limitable_reflections?(reflections) 1004 1034 1005 1035 return sanitize_sql(sql) 1006 1036 end 1007 1008 def add_limited_ids_condition!(sql, options )1009 unless (id_list = select_limited_ids_list(options )).empty?1037 1038 def add_limited_ids_condition!(sql, options, reflections) 1039 unless (id_list = select_limited_ids_list(options, reflections)).empty? 1010 1040 sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) " 1011 1041 end 1012 1042 end 1013 1014 def select_limited_ids_list(options )1043 1044 def select_limited_ids_list(options, reflections) 1015 1045 connection.select_values( 1016 construct_finder_sql_for_association_limiting(options ),1046 construct_finder_sql_for_association_limiting(options, reflections), 1017 1047 "#{name} Load IDs For Limited Eager Loading" 1018 1048 ).collect { |id| connection.quote(id) }.join(", ") 1019 1049 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) 1050 1051 def construct_finder_sql_for_association_limiting(options, reflections) 1052 sql = "SELECT " 1053 sql << "DISTINCT #{table_name}." if include_eager_conditions?(options) 1054 sql << "#{primary_key} FROM #{table_name} " 1023 1055 1024 sql = "SELECT #{primary_key} FROM #{table_name} " 1056 if include_eager_conditions?(options) 1057 sql << reflections.collect { |reflection| association_join(reflection) }.to_s 1058 sql << "#{options[:joins]} " if options[:joins] 1059 end 1060 1025 1061 add_conditions!(sql, options[:conditions]) 1026 1062 sql << "ORDER BY #{options[:order]} " if options[:order] 1027 1063 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