Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added documentation for new Base.find API and eager association loading

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1209 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit 515886a5650bfa25e1c450dd31fd4922434a161c 1 parent e9681eb
@dhh dhh authored
View
22 activerecord/CHANGELOG
@@ -1,5 +1,27 @@
*SVN*
+* Added eager loading of associations as a way to solve the N+1 problem more gracefully without piggy-back queries. Example:
+
+ for post in Post.find(:all, :limit => 100)
+ puts "Post: " + post.title
+ puts "Written by: " + post.author.name
+ puts "Last comment on: " + post.comments.first.created_on
+ end
+
+ This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as:
+
+ for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
+
+ ...and the number of database queries needed is now 1.
+
+* Added new unified Base.find API and deprecated the use of find_first and find_all. See the documentation for Base.find. Examples:
+
+ Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
+ Person.find(1, 5, 6, :conditions => "administrator = 1", :order => "created_on DESC")
+ Person.find(:first, :order => "created_on DESC", :offset => 5)
+ Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
+ Person.find(:all, :offset => 10, :limit => 10)
+
* Fixed PostgreSQL usage of fixtures with regards to public schemas and table names with dots #962 [gnuman1@gmail.com]
* Fixed that fixtures were being deleted in the same order as inserts causing FK errors #890 [andrew.john.peters@gmail.com]
View
35 activerecord/lib/active_record/associations.rb
@@ -108,6 +108,41 @@ def clear_association_cache #:nodoc:
# project.milestones(true).size # fetches milestones from the database
# project.milestones # uses the milestone cache
#
+ # == 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
+ # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each needs to display their author
+ # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
+ #
+ # class Post < ActiveRecord::Base
+ # belongs_to :author
+ # has_many :comments
+ # end
+ #
+ # Consider the following loop using the class above:
+ #
+ # for post in Post.find(:all, :limit => 100)
+ # puts "Post: " + post.title
+ # puts "Written by: " + post.author.name
+ # puts "Last comment on: " + post.comments.first.created_on
+ # end
+ #
+ # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
+ #
+ # for post in Post.find(:all, :limit => 100, :include => :author)
+ #
+ # This references the name of the belongs_to association that also used the :author symbol, so the find will now weave in a join something
+ # like this: LEFT OUTER JOIN authors ON authors.id = posts.author_id. Doing so will cut down the number of queries from 201 to 101.
+ #
+ # We can improve upon the situation further by referencing both associations in the finder with:
+ #
+ # for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
+ #
+ # That'll add another join along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id. And we'll be down to 1 query.
+ # But that shouldn't fool you to think that you can pull out huge amounts of data with no performance penalty just because you've reduced
+ # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So its no
+ # catch-all for performance problems, but its a great way to cut down on the number of queries in a situation as the one described above.
+ #
# == Modules
#
# By default, associations will look for objects within the current module scope. Consider:
View
27 activerecord/lib/active_record/base.rb
@@ -299,18 +299,31 @@ class << self # Class methods
#
# All approaches accepts an option hash as their last parameter. The options are:
#
- # * <tt>:conditions</tt>:
- # * <tt>:order</tt>:
- # * <tt>:limit</tt>:
- # * <tt>:offset</tt>:
- # * <tt>:joins</tt>:
- # * <tt>:include</tt>:
+ # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
+ # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
+ # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
+ # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
+ # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
+ # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
+ # to already defined associations. See eager loading under Associations.
#
- # Examples:
+ # Examples for find by id:
# Person.find(1) # returns the object for ID = 1
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
# Person.find([1]) # returns an array for objects the object with ID = 1
+ # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
+ #
+ # Examples for find first:
+ # Person.find(:first) # returns the first object fetched by SELECT * FROM people
+ # Person.find(:first, :conditions => [ "user_name = ?", user_name])
+ # Person.find(:first, :order => "created_on DESC", :offset => 5)
+ #
+ # Examples for find all:
+ # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
+ # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
+ # Person.find(:all, :offset => 10, :limit => 10)
+ # Person.find(:all, :include => [ :account, :friends ])
def find(*args)
options = extract_options_from_args!(args)
Please sign in to comment.
Something went wrong with that request. Please try again.