Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Update documentation on :include.
  • Loading branch information
fcheung committed May 18, 2008
1 parent a49ebf6 commit c9c4f7b
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 54 deletions.
116 changes: 63 additions & 53 deletions activerecord/lib/active_record/associations.rb
Expand Up @@ -441,9 +441,9 @@ def clear_association_cache #:nodoc:
#
# == 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
Expand All @@ -462,14 +462,15 @@ def clear_association_cache #:nodoc:
#
# 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:
#
Expand All @@ -482,81 +483,91 @@ def clear_association_cache #:nodoc:
# 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
#
# ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
# 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.
Expand Down Expand Up @@ -840,7 +851,6 @@ def has_one(association_id, options = {})
# 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>).
Expand Down
2 changes: 1 addition & 1 deletion activerecord/lib/active_record/base.rb
Expand Up @@ -465,7 +465,7 @@ class << self # Class methods
# 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.
Expand Down

0 comments on commit c9c4f7b

Please sign in to comment.