Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #2597: counting_and_limiting_for_named_associations.patch

File counting_and_limiting_for_named_associations.patch, 12.2 kB (added by jeremy@jthopple.com, 3 years ago)
  • activerecord/test/base_test.rb

    old new  
    10511051                        "LEFT JOIN comments ON posts.id=comments.post_id") 
    10521052    end 
    10531053    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 
    10541061  end 
    10551062   
    10561063  def test_clear_association_cache_stored      
  • activerecord/test/associations_go_eager_test.rb

    old new  
    120120  end 
    121121 
    122122  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 
    126128  end 
    127129 
    128130  def test_eager_with_has_many_and_limit_with_no_results 
     
    141143  end 
    142144 
    143145  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 
    151159  end 
    152160 
    153161  def test_eager_association_loading_with_habtm 
  • activerecord/lib/active_record/associations.rb

    old new  
    776776            end 
    777777          end 
    778778        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 
    779785 
    780786        def find_with_associations(options = {}) 
    781787          reflections  = reflect_on_included_associations(options[:include]) 
     
    988994            "#{name} Load Including Associations" 
    989995          ) 
    990996        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] 
    9911002 
     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 
    9921012        def construct_finder_sql_with_included_associations(options, schema_abbreviations, reflections) 
    9931013          sql = "SELECT #{column_aliases(schema_abbreviations)} FROM #{table_name} " 
    9941014          sql << reflections.collect { |reflection| association_join(reflection) }.to_s 
    9951015          sql << "#{options[:joins]} " if options[:joins] 
    996  
     1016  
    9971017          add_conditions!(sql, options[:conditions]) 
    9981018          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] 
    10001020 
    10011021          sql << "ORDER BY #{options[:order]} " if options[:order] 
    1002  
     1022  
    10031023          add_limit!(sql, options) if using_limitable_reflections?(reflections) 
    1004  
     1024  
    10051025          return sanitize_sql(sql) 
    10061026        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? 
    10101030            sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) " 
    10111031          end 
    10121032        end 
    1013  
    1014         def select_limited_ids_list(options
     1033  
     1034        def select_limited_ids_list(options, reflections
    10151035          connection.select_values( 
    1016             construct_finder_sql_for_association_limiting(options), 
     1036            construct_finder_sql_for_association_limiting(options, reflections), 
    10171037            "#{name} Load IDs For Limited Eager Loading" 
    10181038          ).collect { |id| connection.quote(id) }.join(", ") 
    10191039        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} " 
    10231045           
    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           
    10251051          add_conditions!(sql, options[:conditions]) 
    10261052          sql << "ORDER BY #{options[:order]} " if options[:order] 
    10271053          add_limit!(sql, options) 
  • activerecord/lib/active_record/base.rb

    old new  
    502502        connection.delete(sql, "#{name} Delete all") 
    503503      end 
    504504 
    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          throw "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) 
    508552        sql  = "SELECT COUNT(*) FROM #{table_name} " 
    509         sql << " #{joins} " if joins 
    510         add_conditions!(sql, conditions
    511         count_by_sql(sql) 
     553        sql << " #{options[:joins]} " if options[:joins] 
     554        add_conditions!(sql, options[:conditions]
     555        sql 
    512556      end 
    513557 
    514558      # 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  
    163163    # Returns the total number of items in the collection to be paginated for 
    164164    # the +model+ and given +conditions+. Override this method to implement a 
    165165    # 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]) 
    168169    end 
    169    
     170     
    170171    # Returns a collection of items for the given +model+ and +options[conditions]+, 
    171172    # ordered by +options[order]+, for the current page in the given +paginator+. 
    172173    # Override this method to implement a custom finder. 
     
    185186    def paginator_and_collection_for(collection_id, options) #:nodoc: 
    186187      klass = options[:class_name].constantize 
    187188      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) 
    191190      paginator = Paginator.new(self, count, options[:per_page], page) 
    192191      collection = find_collection_for_pagination(klass, options, paginator) 
    193        
     192     
    194193      return paginator, collection  
    195194    end 
    196195