Skip to content
This repository
Browse code

Document ActiveRecord::AssociationPreload.

  • Loading branch information...
commit 7a22f7abf3ea1f89f1b9b3b2d6d216fa12fd0b8e 1 parent dc996ca
Hongli Lai authored September 20, 2008
90  activerecord/lib/active_record/association_preload.rb
... ...
@@ -1,14 +1,88 @@
1 1
 module ActiveRecord
  2
+  # See ActiveRecord::AssociationPreload::ClassMethods for documentation.
2 3
   module AssociationPreload #:nodoc:
3 4
     def self.included(base)
4 5
       base.extend(ClassMethods)
5 6
     end
6 7
 
  8
+    # Implements the details of eager loading of ActiveRecord associations.
  9
+    # Application developers should not use this module directly.
  10
+    #
  11
+    # ActiveRecord::Base is extended with this module. The source code in
  12
+    # ActiveRecord::Base references methods defined in this module.
  13
+    #
  14
+    # Note that 'eager loading' and 'preloading' are actually the same thing.
  15
+    # However, there are two different eager loading strategies.
  16
+    #
  17
+    # The first one is by using table joins. This was only strategy available
  18
+    # prior to Rails 2.1. Suppose that you have an Author model with columns
  19
+    # 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
  20
+    # this strategy, ActiveRecord would try to retrieve all data for an author
  21
+    # and all of its books via a single query:
  22
+    #
  23
+    #   SELECT * FROM authors
  24
+    #   LEFT OUTER JOIN books ON authors.id = books.id
  25
+    #   WHERE authors.name = 'Ken Akamatsu'
  26
+    #
  27
+    # However, this could result in many rows that contain redundant data. After
  28
+    # having received the first row, we already have enough data to instantiate
  29
+    # the Author object. In all subsequent rows, only the data for the joined
  30
+    # 'books' table is useful; the joined 'authors' data is just redundant, and
  31
+    # processing this redundant data takes memory and CPU time. The problem
  32
+    # quickly becomes worse and worse as the level of eager loading increases
  33
+    # (i.e. if ActiveRecord is to eager load the associations' assocations as
  34
+    # well).
  35
+    #
  36
+    # The second strategy is to use multiple database queries, one for each
  37
+    # level of association. Since Rails 2.1, this is the default strategy. In
  38
+    # situations where a table join is necessary (e.g. when the +:conditions+
  39
+    # option references an association's column), it will fallback to the table
  40
+    # join strategy.
  41
+    #
  42
+    # See also ActiveRecord::Associations::ClassMethods, which explains eager
  43
+    # loading in a more high-level (application developer-friendly) manner.
7 44
     module ClassMethods
8  
-
9  
-      # Loads the named associations for the activerecord record (or records) given
10  
-      # preload_options is passed only one level deep: don't pass to the child associations when associations is a Hash
11 45
       protected
  46
+      
  47
+      # Eager loads the named associations for the given ActiveRecord record(s).
  48
+      #
  49
+      # In this description, 'association name' shall refer to the name passed
  50
+      # to an association creation method. For example, a model that specifies
  51
+      # <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
  52
+      # names +:author+ and +:buyers+.
  53
+      #
  54
+      # == Parameters
  55
+      # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
  56
+      # i.e. +records+ itself may also contain arrays of records. In any case,
  57
+      # +preload_associations+ will preload the associations all records by
  58
+      # flattening +records+.
  59
+      #
  60
+      # +associations+ specifies one or more associations that you want to
  61
+      # preload. It may be:
  62
+      # - a Symbol or a String which specifies a single association name. For
  63
+      #   example, specifiying +:books+ allows this method to preload all books
  64
+      #   for an Author.
  65
+      # - an Array which specifies multiple association names. This array
  66
+      #   is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
  67
+      #   allows this method to preload an author's avatar as well as all of his
  68
+      #   books.
  69
+      # - a Hash which specifies multiple association names, as well as
  70
+      #   association names for the to-be-preloaded association objects. For
  71
+      #   example, specifying <tt>{ :author => :avatar }</tt> will preload a
  72
+      #   book's author, as well as that author's avatar.
  73
+      #
  74
+      # +:associations+ has the same format as the +:include+ option for
  75
+      # <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
  76
+      #
  77
+      #   :books
  78
+      #   [ :books, :author ]
  79
+      #   { :author => :avatar }
  80
+      #   [ :books, { :author => :avatar } ]
  81
+      #
  82
+      # +preload_options+ contains options that will be passed to ActiveRecord#find
  83
+      # (which is called under the hood for preloading records). But it is passed
  84
+      # only one level deep in the +associations+ argument, i.e. it's not passed
  85
+      # to the child associations when +associations+ is a Hash.
12 86
       def preload_associations(records, associations, preload_options={})
13 87
         records = [records].flatten.compact.uniq
14 88
         return if records.empty?
@@ -30,6 +104,8 @@ def preload_associations(records, associations, preload_options={})
30 104
 
31 105
       private
32 106
 
  107
+      # Preloads a specific named association for the given records. This is
  108
+      # called by +preload_associations+ as its base case.
33 109
       def preload_one_association(records, association, preload_options={})
34 110
         class_to_reflection = {}
35 111
         # Not all records have the same class, so group then preload
@@ -37,6 +113,10 @@ def preload_one_association(records, association, preload_options={})
37 113
         # unnecessarily
38 114
         records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
39 115
           raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
  116
+          
  117
+          # 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
  118
+          # the following could call 'preload_belongs_to_association',
  119
+          # 'preload_has_many_association', etc.
40 120
           send("preload_#{reflection.macro}_association", records, reflection, preload_options)
41 121
         end
42 122
       end
@@ -77,6 +157,10 @@ def set_association_single_records(id_to_record_map, reflection_name, associated
77 157
         end
78 158
       end
79 159
 
  160
+      # Given a collection of ActiveRecord objects, constructs a Hash which maps
  161
+      # the objects' IDs to the relevant objects. Returns a 2-tuple
  162
+      # <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash,
  163
+      # and +ids+ is an Array of record IDs.
80 164
       def construct_id_map(records)
81 165
         id_to_record_map = {}
82 166
         ids = []

0 notes on commit 7a22f7a

Please sign in to comment.
Something went wrong with that request. Please try again.