aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorFrederick Cheung <frederick.cheung@gmail.com>2008-05-18 20:02:37 +0100
committerFrederick Cheung <frederick.cheung@gmail.com>2008-05-18 20:24:48 +0100
commitc9c4f7bababda23f33693bdca35d1bb37fdfcd15 (patch)
treee3bd60fce7fbafc4c1eb0d8d6a99a6191ea658f6 /activerecord/lib
parenta49ebf6d7909c344d2fe570cb82c97fa271db03e (diff)
downloadrails-c9c4f7bababda23f33693bdca35d1bb37fdfcd15.tar.gz
rails-c9c4f7bababda23f33693bdca35d1bb37fdfcd15.tar.bz2
rails-c9c4f7bababda23f33693bdca35d1bb37fdfcd15.zip
Update documentation on :include.
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations.rb116
-rwxr-xr-xactiverecord/lib/active_record/base.rb2
2 files changed, 64 insertions, 54 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 95caf68692..902deeb35c 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -441,9 +441,9 @@ module ActiveRecord
#
# == Eager loading of associations
#
- # Eager loading is a way to find objects of a certain class and a number of named associations along with it in a single SQL call. This is
+ # Eager loading is a way to find objects of a certain class and a number of named associations. This is
# one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each need to display their author
- # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
+ # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 2. Example:
#
# class Post < ActiveRecord::Base
# belongs_to :author
@@ -462,14 +462,15 @@ module ActiveRecord
#
# for post in Post.find(:all, :include => :author)
#
- # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> symbol, so the find will now weave in a join something
- # like this: <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Doing so will cut down the number of queries from 201 to 101.
+ # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> symbol. After loading the posts, find
+ # will collect the +author_id+ from each one and load all the referenced authors with one query. Doing so will cut down the number of queries from 201 to 102.
#
# We can improve upon the situation further by referencing both associations in the finder with:
#
# for post in Post.find(:all, :include => [ :author, :comments ])
#
- # That'll add another join along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt>. And we'll be down to 1 query.
+ # This will load all comments with a single query. This reduces the total number of queries to 3. More generally the number of queries
+ # will be 1 plus the number of associations named (except if some of the associations are polymorphic belongs_to - see below).
#
# To include a deep hierarchy of associations, use a hash:
#
@@ -482,36 +483,46 @@ module ActiveRecord
# the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
# catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
#
- # Since the eager loading pulls from multiple tables, you'll have to disambiguate any column references in both conditions and orders. So
- # <tt>:order => "posts.id DESC"</tt> will work while <tt>:order => "id DESC"</tt> will not. Because eager loading generates the +SELECT+ statement too, the
- # <tt>:select</tt> option is ignored.
+ # Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case
+ # Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example
+ #
+ # Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
#
- # You can use eager loading on multiple associations from the same table, but you cannot use those associations in orders and conditions
- # as there is currently not any way to disambiguate them. Eager loading will not pull additional attributes on join tables, so "rich
- # associations" with +has_and_belongs_to_many+ are not a good fit for eager loading.
+ # will result in a single sql query with joins along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
+ # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions like this can have unintended consequences.
+ # In the above example posts with no approved comments are not returned at all, because the conditions apply to the sql statement as a whole
+ # and not just to the association. You must disambiguate column references for this fallback to happen, for example
+ # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
+ #
+ # If you do want eagerload only some members of an association it is usually more natural to :include an association
+ # which has conditions defined on it:
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
+ # end
+ #
+ # Post.find(:all, :include => :approved_comments)
+ #
+ # will load posts and eager load the approved_comments association, which contains only those comments that have been approved.
#
# When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
# before the actual model exists.
#
- # Eager loading is not supported with polymorphic associations up to (and including)
- # version 2.0.2. Given
+ # Eager loading is supported with polymorphic associations.
#
# class Address < ActiveRecord::Base
# belongs_to :addressable, :polymorphic => true
# end
#
- # a call that tries to eager load the addressable model
- #
- # Address.find(:all, :include => :addressable) # INVALID
- #
- # will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent model's type
- # is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that early query.
+ # A call that tries to eager load the addressable model
#
- # In versions greater than 2.0.2 eager loading in polymorphic associations is supported
- # thanks to a change in the overall preloading strategy.
+ # Address.find(:all, :include => :addressable)
#
- # It does work the other way around though: if the <tt>User</tt> model is <tt>addressable</tt> you can eager load
- # their addresses with <tt>:include</tt> just fine, every piece needed to construct the query is known beforehand.
+ # will execute one query to load the addresses and load the addressables with one query per addressable type.
+ # For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of
+ # addressable types to load is determined on the back of the addresses loaded. This is not supported if ActiveRecord has to fallback
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
+ # model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query.
#
# == Table Aliasing
#
@@ -519,44 +530,44 @@ module ActiveRecord
# the standard table name is used. The second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. Indexes are appended
# for any more successive uses of the table name.
#
- # Post.find :all, :include => :comments
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ...
- # Post.find :all, :include => :special_comments # STI
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... AND comments.type = 'SpecialComment'
- # Post.find :all, :include => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... LEFT OUTER JOIN comments special_comments_posts
+ # Post.find :all, :joins => :comments
+ # # => SELECT ... FROM posts INNER JOIN comments ON ...
+ # Post.find :all, :joins => :special_comments # STI
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
+ # Post.find :all, :joins => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
#
# Acts as tree example:
#
- # TreeMixin.find :all, :include => :children
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
- # TreeMixin.find :all, :include => {:children => :parent} # using cascading eager includes
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
- # LEFT OUTER JOIN parents_mixins ...
- # TreeMixin.find :all, :include => {:children => {:parent => :children}}
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
- # LEFT OUTER JOIN parents_mixins ...
- # LEFT OUTER JOIN mixins childrens_mixins_2
+ # TreeMixin.find :all, :joins => :children
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # TreeMixin.find :all, :joins => {:children => :parent}
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # INNER JOIN parents_mixins ...
+ # TreeMixin.find :all, :joins => {:children => {:parent => :children}}
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # INNER JOIN parents_mixins ...
+ # INNER JOIN mixins childrens_mixins_2
#
# Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
#
- # Post.find :all, :include => :categories
- # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
- # Post.find :all, :include => {:categories => :posts}
- # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
- # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
- # Post.find :all, :include => {:categories => {:posts => :categories}}
- # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
- # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
- # LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts
+ # Post.find :all, :joins => :categories
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # Post.find :all, :joins => {:categories => :posts}
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
+ # Post.find :all, :joins => {:categories => {:posts => :categories}}
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
+ # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
#
# If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table names will take precedence over the eager associations:
#
- # Post.find :all, :include => :comments, :joins => "inner join comments ..."
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ...
- # Post.find :all, :include => [:comments, :special_comments], :joins => "inner join comments ..."
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ...
- # LEFT OUTER JOIN comments special_comments_posts ...
+ # Post.find :all, :joins => :comments, :joins => "inner join comments ..."
+ # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
+ # Post.find :all, :joins => [:comments, :special_comments], :joins => "inner join comments ..."
+ # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
+ # INNER JOIN comments special_comments_posts ...
# INNER JOIN comments ...
#
# Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
@@ -840,7 +851,6 @@ module ActiveRecord
# this results in a counter with +NULL+ value, which will never increment.
# Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
# * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
- # Not allowed if the association is polymorphic.
# * <tt>:polymorphic</tt> - Specify this association is a polymorphic association by passing +true+.
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 00083a50fd..c51743af16 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -465,7 +465,7 @@ module ActiveRecord #:nodoc:
# or named associations in the same form used for the <tt>:include</tt> option, which will perform an INNER JOIN on the associated table(s).
# If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
# Pass <tt>:readonly => false</tt> to override.
- # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
+ # * <tt>:include</tt>: Names associations that should be loaded alongside. The symbols named refer
# to already defined associations. See eager loading under Associations.
# * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
# include the joined columns.