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

Ticket #9640: ar_preload.diff

File ar_preload.diff, 16.5 kB (added by fcheung, 7 months ago)

Extra tests & no excessive singularisation

  • activerecord/test/associations/eager_singularization_test.rb

    old new  
    104104    return unless @have_tables 
    105105    assert_nothing_raised do 
    106106      Virus.find(:all, :include => :octopus) 
     107      Virus.find(:all, :preload => :octopus) 
    107108    end 
    108109  end 
    109110 
     
    111112    return unless @have_tables 
    112113    assert_nothing_raised do 
    113114      Octopus.find(:all, :include => :virus) 
     115      Octopus.find(:all, :preload => :virus) 
    114116    end 
    115117  end 
    116118 
     
    118120    return unless @have_tables 
    119121    assert_nothing_raised do 
    120122      Bus.find(:all, :include => :passes) 
     123      Bus.find(:all, :preload => :passes) 
    121124    end 
    122125  end 
    123126 
     
    126129    assert_nothing_raised do 
    127130      Crisis.find(:all, :include => :messes) 
    128131      Mess.find(:all, :include => :crises) 
     132      Crisis.find(:all, :preload => :messes) 
     133      Mess.find(:all, :preload => :crises) 
    129134    end 
    130135  end 
    131136 
     
    133138    return unless @have_tables 
    134139    assert_nothing_raised do 
    135140      Crisis.find(:all, :include => :successes) 
     141      Crisis.find(:all, :preload => :successes) 
    136142    end 
    137143  end 
    138144   
     
    140146    return unless @have_tables 
    141147    assert_nothing_raised do 
    142148      Crisis.find(:all, :include => :compresses) 
     149      Crisis.find(:all, :preload => :compresses) 
    143150    end 
    144151  end 
    145152end 
  • activerecord/test/fixtures/post.rb

    old new  
    66  end 
    77 
    88  belongs_to :author_with_posts, :class_name => "Author", :foreign_key => :author_id, :include => :posts 
     9  belongs_to :author_with_preloaded_posts, :class_name => "Author", :foreign_key => :author_id, :preload => :posts 
    910 
    1011  has_many   :comments, :order => "body" do 
    1112    def find_most_recent 
     
    1516 
    1617  has_one  :very_special_comment 
    1718  has_one  :very_special_comment_with_post, :class_name => "VerySpecialComment", :include => :post 
     19  has_one  :very_special_comment_with_preloaded_post, :class_name => "VerySpecialComment", :preload => :post 
    1820  has_many :special_comments 
    1921 
    2022  has_and_belongs_to_many :categories 
    2123  has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id' 
     24  has_and_belongs_to_many :categories_with_categorizations, :class_name => "Category", :join_table => "categories_posts", :join_table => "categories_posts", 
     25                          :association_foreign_key => 'category_id', :preload => :categorizations 
    2226 
     27  has_many :comments_with_post, :class_name => 'Comment', :preload => :post 
     28   
    2329  has_many :taggings, :as => :taggable 
    2430  has_many :tags, :through => :taggings, :include => :tagging do 
    2531    def add_joins_and_select 
  • activerecord/lib/active_record/associations/association_proxy.rb

    old new  
    114114            :offset  => @reflection.options[:offset], 
    115115            :joins   => @reflection.options[:joins], 
    116116            :include => @reflection.options[:include], 
     117            :preload => @reflection.options[:preload], 
    117118            :select  => @reflection.options[:select] 
    118119          ) 
    119120        end 
  • activerecord/lib/active_record/associations/belongs_to_association.rb

    old new  
    4444          @reflection.klass.find( 
    4545            @owner[@reflection.primary_key_name],  
    4646            :conditions => conditions, 
    47             :include    => @reflection.options[:include] 
     47            :include    => @reflection.options[:include], 
     48            :preload    => @reflection.options[:preload] 
    4849          ) 
    4950        end 
    5051 
  • activerecord/lib/active_record/associations/has_one_association.rb

    old new  
    5353          @reflection.klass.find(:first,  
    5454            :conditions => @finder_sql,  
    5555            :order      => @reflection.options[:order],  
    56             :include    => @reflection.options[:include] 
     56            :include    => @reflection.options[:include], 
     57            :preload    => @reflection.options[:preload] 
    5758          ) 
    5859        end 
    5960 
  • activerecord/lib/active_record/associations.rb

    old new  
    11661166            :uniq, 
    11671167            :finder_sql, :counter_sql,  
    11681168            :before_add, :after_add, :before_remove, :after_remove,  
    1169             :extend 
     1169            :extend, :preload 
    11701170          ) 
    11711171 
    11721172          options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given? 
     
    11761176 
    11771177        def create_has_one_reflection(association_id, options) 
    11781178          options.assert_valid_keys( 
    1179             :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as 
     1179            :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :preload 
    11801180          ) 
    11811181 
    11821182          create_reflection(:has_one, association_id, options, self) 
     
    11851185        def create_belongs_to_reflection(association_id, options) 
    11861186          options.assert_valid_keys( 
    11871187            :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,  
    1188             :counter_cache, :extend, :polymorphic 
     1188            :counter_cache, :extend, :polymorphic, :preload 
    11891189          ) 
    11901190           
    11911191          reflection = create_reflection(:belongs_to, association_id, options, self) 
     
    12041204            :uniq,  
    12051205            :finder_sql, :delete_sql, :insert_sql, 
    12061206            :before_add, :after_add, :before_remove, :after_remove,  
    1207             :extend 
     1207            :extend, :preload 
    12081208          ) 
    12091209 
    12101210          options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given? 
  • activerecord/lib/active_record/base.rb

    old new  
    428428      #   end 
    429429      def find(*args) 
    430430        options = args.extract_options! 
     431        associations_to_preload = options.delete(:preload) 
    431432        validate_find_options(options) 
    432433        set_readonly_option!(options) 
    433434 
    434         case args.first 
     435        results = case args.first 
    435436          when :first then find_initial(options) 
    436437          when :all   then find_every(options) 
    437438          else             find_from_ids(args, options) 
    438439        end 
     440        if associations_to_preload 
     441          preload_associations(results, associations_to_preload) 
     442        end 
     443        results 
    439444      end 
    440445       
    441446      # Works like find(:all), but requires a complete SQL string. Examples: 
     
    16631668          quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "  
    16641669          quoted_value  
    16651670        end 
     1671         
     1672        #loads the named associations for the activerecord object (or objects) given 
     1673        # 
     1674        # 
     1675        def preload_associations(top_objects, associations) 
     1676          top_objects = [top_objects].flatten.compact 
     1677          return if top_objects.empty? 
     1678          case associations 
     1679          when Array then associations.each {|association| preload_associations(top_objects, association)} 
     1680          when Symbol, String then preload_one_association(top_objects, associations.to_sym) 
     1681          when Hash then 
     1682            associations.each do |parent, child| 
     1683              raise "parent must be an association name" unless parent.is_a?( String) || parent.is_a?( Symbol) 
     1684              preload_associations(top_objects, parent) 
     1685              reflection = reflections[parent] 
     1686              parents = top_objects.map {|top_object| top_object.send(reflection.name)}.flatten 
     1687              unless parents.empty? 
     1688                parents.first.class.preload_associations(parents, child) 
     1689              end 
     1690            end 
     1691          end 
     1692           
     1693        end 
     1694         
     1695        def add_preloaded_object_to_collection(parent_object, reflection_name, associated_object) 
     1696          association_proxy = parent_object.send(reflection_name) 
     1697          association_proxy.loaded 
     1698          if associated_object.is_a? Array 
     1699            association_proxy.target.push(*associated_object) 
     1700          else 
     1701            association_proxy.target << associated_object 
     1702          end 
     1703        end 
     1704           
     1705        def preload_collection_object(id_to_object_map, reflection_name, associated_object, key) 
     1706          mapped_objects = id_to_object_map[associated_object[key].to_i] 
     1707          mapped_objects.each do |mapped_object| 
     1708            add_preloaded_object_to_collection(mapped_object, reflection_name, associated_object) 
     1709          end 
     1710        end 
     1711 
     1712        def preload_single_object(id_to_object_map, reflection_name, associated_object, key) 
     1713          mapped_objects = id_to_object_map[associated_object[key].to_i] 
     1714          mapped_objects.each do |mapped_object| 
     1715            mapped_object.send("set_#{reflection_name}_target", associated_object) 
     1716          end 
     1717        end 
     1718         
     1719         
     1720        def has_n_preload_conditions(reflection) 
     1721          options = reflection.options 
     1722          if interface = reflection.options[:as] 
     1723            conditions = "#{reflection.klass.table_name}.#{interface}_id IN (?) and #{reflection.klass.table_name}.#{interface}_type = '#{self.name}'" 
     1724          else 
     1725            foreign_key = options[:foreign_key] || reflection.active_record.to_s.foreign_key 
     1726            conditions = "#{reflection.klass.table_name}.#{foreign_key} IN (?)" 
     1727          end 
     1728        end 
     1729         
     1730        def preload_one_association(top_objects, association)  
     1731          reflection = reflections[association] 
     1732          raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection 
     1733                     
     1734          reflection_name = reflection.name 
     1735          options = reflection.options 
     1736          table_name = reflection.klass.table_name unless options[:polymorphic] 
     1737          primary_key_name = reflection.primary_key_name 
     1738          foreign_key = options[:foreign_key] || reflection.active_record.to_s.foreign_key 
     1739          if options[:as] 
     1740            foreign_key = "#{options[:as]}_id" 
     1741          end          
     1742          #it is not enough to just do  index_by &:id as there could be duplicates (see test_duplicate_middle_objects) 
     1743           
     1744          id_to_object_map = {} 
     1745          ids = [] 
     1746          top_objects.each do |top_object| 
     1747            ids << top_object.id 
     1748            mapped_objects = (id_to_object_map[top_object.id] ||= []) 
     1749            mapped_objects << top_object 
     1750          end 
     1751          ids = top_objects.uniq 
     1752           
     1753          case reflection.macro 
     1754          when :belongs_to 
     1755            if options[:polymorphic] 
     1756              polymorph_type = options[:foreign_type] 
     1757              klasses = top_objects.map {|object| object.send polymorph_type}.uniq.compact.map &:constantize 
     1758            else 
     1759              klasses = [reflection.klass] 
     1760            end 
     1761            klasses.each do |klass| 
     1762              table_name = klass.table_name 
     1763              conditions = "t0.#{self.primary_key} IN (?)" 
     1764              conditions << " AND (#{sanitize_sql options[:conditions]})" if options[:conditions] 
     1765               
     1766              joins = "INNER JOIN #{self.table_name} AS t0 ON #{table_name}.#{primary_key} = t0.#{primary_key_name}" 
     1767              if options[:polymorphic] 
     1768                joins << " AND #{polymorph_type} = '#{klass.name}'" 
     1769              end 
     1770              associated_objects = klass.find(:all, :conditions => [conditions, ids], 
     1771              :joins => joins, 
     1772              :preload => options[:preload], 
     1773              :select => "#{options[:select] || table_name+'.*'}, t0.#{self.primary_key} as _parent_object_id", 
     1774              :order => options[:order]) 
     1775              associated_objects.each do |associated_object| 
     1776                preload_single_object(id_to_object_map, reflection_name, associated_object, '_parent_object_id') 
     1777              end 
     1778            end 
     1779          when :has_one 
     1780            top_objects.each {|object| object.send("set_#{reflection.name}_target", nil)} 
     1781            conditions = has_n_preload_conditions(reflection) 
     1782            conditions << " AND (#{sanitize_sql options[:conditions]})" if options[:conditions] 
     1783            associated_objects =  reflection.klass.find(:all, :conditions => [conditions, ids], 
     1784            :select => (options[:select] || "#{table_name}.*"), 
     1785            :preload => options[:preload], 
     1786            :order => options[:order]) 
     1787            associated_objects.each do |associated_object| 
     1788              preload_single_object(id_to_object_map, reflection_name, associated_object, foreign_key) 
     1789            end 
     1790 
     1791          when :has_many 
     1792            top_objects.each {|object| object.send(reflection.name).loaded} 
     1793             
     1794            if options[:through] 
     1795              through_reflection = reflections[options[:through]]               
     1796              through_primary_key = through_reflection.primary_key_name 
     1797              top_objects.first.class.preload_one_association(top_objects, options[:through]) 
     1798              through_objects = top_objects.compact.map {|object| object.send options[:through]}.flatten 
     1799               
     1800              unless through_objects.empty? 
     1801                source = reflection.source_reflection.name 
     1802                through_objects.first.class.preload_one_association(through_objects, source) 
     1803               
     1804                through_objects.compact.each do |through_object| 
     1805                  associated_object = through_object.send(source) 
     1806                  mapped_objects = id_to_object_map[through_object[through_primary_key].to_i] 
     1807                  mapped_objects.each do |mapped_object| 
     1808                    add_preloaded_object_to_collection(mapped_object, reflection_name, associated_object) 
     1809                  end                   
     1810                end 
     1811              end 
     1812               
     1813            else 
     1814              conditions = has_n_preload_conditions(reflection) 
     1815              conditions << " AND (#{sanitize_sql options[:conditions]})" if options[:conditions] 
     1816              associated_objects =  reflection.klass.find(:all, :conditions => [conditions, ids], 
     1817                                  :select => (options[:select] || "#{table_name}.*"), 
     1818                                  :preload => options[:preload], 
     1819                                  :order => options[:order]) 
     1820              associated_objects.each do |associated_object| 
     1821                preload_collection_object(id_to_object_map, reflection_name, associated_object, foreign_key) 
     1822              end 
     1823            end 
     1824          when :has_and_belongs_to_many 
     1825            top_objects.each {|object| object.send(reflection.name).loaded} 
     1826            conditions = "t0.#{reflection.primary_key_name}  IN (?)" 
     1827            conditions << " AND (#{options[:conditions]})" if options[:conditions] 
     1828            associated_objects = reflection.klass.find(:all, :conditions => [conditions, ids], 
     1829            :preload => options[:preload], 
     1830            :joins => "INNER JOIN #{options[:join_table]} as t0 ON #{reflection.klass.table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}", 
     1831            :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as _parent_object_id", 
     1832            :order => options[:order]) 
     1833            associated_objects.each do |associated_object| 
     1834              preload_collection_object(id_to_object_map, reflection_name, associated_object, '_parent_object_id') 
     1835            end 
     1836          else 
     1837            raise "Unsupported association type #{reflection.name}!" 
     1838          end 
     1839           
     1840        end 
     1841         
    16661842    end 
    16671843 
    16681844    public