aboutsummaryrefslogblamecommitdiffstats
path: root/activerecord/lib/active_record/associations.rb
blob: f862d9e7776f6a03f99f2d86d1179b195b54d814 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
                                                      
                                                           
                                                           
                                                                       
                                                        
                                                         
                                                                 



                                                                        
                                                                           




                              
                                                                                                             


       
                                                                              










                                                                                                                                                                                       
                                                                                 




                                                                                                                               


               
                                                                                                                                                                                                                                                                                                                                                            


       











                                                                                                                                                                                                                                                                    
                                                              




                              
                                                                                    


       
                               




                                  



                                                                 
                                 
       
    

                                                                                                                                          
                                                                                                                                         









                                                                                                                             
                                                                                         
                                                                                                                  







                                                                                                                                      
                                          




























                                                                                                                                     






                                                                                                                                          
                                                                                                                                        














                                                                                                                                            
                               














                                                                                                                                      
                                                                                                                                






                                                                                                                           


                                                                                                                              
                                                                                                                                 


                                          
                             
                                            
                                                        
                                                                               
               
             





                                                                                               



                                                                                                                        
                                                      
                                                                             









                                                                  
     







                                                                                                                       
                                 






































                                                                                                                                  

















                                                                                                                                        











                                                                                                                                       












                                                                                                                                              
                                   






                                                                                                                                           
                                                        





                                                                                                                                              
                                                                       


                                                                                                                                             

                                                                                                                                              
     







                                                                                                                                                
     
                                                                                                                                       


                                                                                                                                     
                                                                                                                                
                                                                                                                                    
      















































                                                                                                                                               






































                                                                                                                                        
                                                                                                
                                                                                  
                                                                                  


                                                                                                                                                        

                                                                                                                                            

                                                                                                                              
                                                                                                                            
                                                                                                                          
                                                        

                                                                                       
                                                                                                         
                                                                                                                             
                                                                                                                                      
                                                            

                                                                                                                                          
                                                                                          

                                                                                  
                                                                                                         

                                      

                                   


                                                                                         
                                                                                                            
                                                                                       
                                                                                                       


                                                                                                       
                                                                                                                       





                                                                                                                                   

                                                                                                                                  


                                                                                                                                    
                                                                                             


                                                                                                        
                                                                                                    
                                                               

                                                                                                                     



                                                                                                                                         
                                                                                                                           

                                                                                                                                
                                                                                                           
                                                                                                                           




                                                                                                                                             
                                                                            





                                                                                                                                                 


                                                   
                                                 
                                                                                                   

                                                                        
                                          
                                                                             




                                                                        
                                                            
                                                                                    
 
                                                     
 





                                                                         

           
                                                        

         
                                                                                         
                                                                                   
                                                                               


                                                                                                                                 
                                                                                    
                                                                                                                              

                                                                                                                                
                                                                                                                               


                                                                                                                                          
                                                                                                                          
                                                                                                                                
                                           


                                                                                                                      


                                                                                                       
                                                                                                                       





                                                                                                                                  
                                                                                                                                  
                                  


                                                                                                                                   
                                                                                                                        

                        

                                                                                                                                            


                                                                                                      
                                                                       
 

                           
                                                                      
                                   
                                                                
                                    



               


                                                                              
        
                                                    

                        

                                                                                        

         
                                                                                                                          
                                                                                   
                                                                                

                                                                                                                                    
                                                                                    
                                                                                                                              
                                                                                                       
                                                                                                                               
                                                                                                                                          
       
                                                                                   



                                                                                        

                                                                                                                   


                                                                                                       
                                                                                                                       











                                                                                                                                   

                                                                                                                                 
                                                                                                                        
                                                                                                        





                                                                                          
                                                      
                                                  



                                                                                   


                              
                                                                        


                                          


                                       
                                                                         
                                                                                                       
                   
                 


               


                                                                                   


                              
                                                                        


                                          


                                       
                                                                         



                             
      
                          

                                                                                          
           

                                  



                                                          
                      
                                                                                                                             



                                              
                                                                                                                               


                                              

         



                                                                                                                    



                                                                                                                               
       
                                                           
                                                                                  
                                                                                                       






                                                                                                                                                          
                                                                                                        

                                                                                                                                                         

                                                                                                                              


                                                                                                                  

                                                                                                   



                                                                                                        
                                                          
                                            

                                         






                                                                                                  
                                                                                                                      








                                                                                                                              

                                                                                                                                   








                                                                                                                                                             
                                                                                                     
                                                                                                                           




                                                                                                                                             


                                           
                                                                                  

                                                                          

                                                                                                                 
                                                                           



                                                                                                   
 

                                                                         
                                                                        


                                                                 
                                    



                         
                                                           

                        



                                                               


             









                                                                   

                                                                             
                                                            



                                                                         

                                         
                                                                         
                  
                                                                 





                          

                                                                      
                               
                                                                         
               
 
                                          
 
                                 
                                                                       
                
                                                               
                        
               
 

                       
 
                                                                    
                                 
                                                                       
                                       
                                                                     
             

           

                                                                         
                                                            

                                                                      
                                                    

                                                                         
               
 
                                              
 

                       
           
 




                                                                            
                                                    

                                                                         
               


                                          
 

                                                                                          
             

           

                                                                             

           
                                                                    

                                                                                    







                                                                       
                 
               
             
 
                              
                                                                    


                                                                       
            




                                                                                    
                 



                                                                                                              
                


                                                                                                           
           
 

                                                                                            

                                                                
                                                                           

                               

                                                                         

               




                                                                          

             

                                                 




                                                                                                                                  
           

                                                





                                                                                                                                  
           
 











                                                                                                                                           






                                                                                                 



                                                                                      
                                                                                                                       
                         
                                                                                                                                                                  


                           
                                                                                                             





























                                                                                                                         


                                                                            
                                   


                                                                    



                                                                                               
                                                                     



                                                              
                                                                                                                       

           
                                                                    











                                                                                                          






                                                                                          




                                                                                           




                                                                                                 





                                                                                                                                               
                                                          
                                                                                                            

           








                                                                                                               
                                                     
                                
                                                                                      


                                                 
        
                                                                                      
                              







                                                                                                             
                                                                                              
          


                                                                                                                                                                                  
 
                                                                                                      






                                                      
 
                                                                                     

                                                                                                                            
                                                                                              
 

                                                           
                                                                                                                                                    
 
                                                                  
 
                                                                                                      
 

                                  
 

                                                                                     
                                                                                         

                                

             
 
                                                             
                                   
                                                                                    
                                                        
                                                            
           
 
                                                                                   
                              





                                                                                                                 
                                                                                                
                                           
             
          
                                                           
                                                                  
                                         



                                              
                                                                        


                                                                              


                                              







                                                                     
 



                                                                                             


                                                                                                                              

           
                                                                








                                                                                             
           
 


                                              
 








                                                                                                   

                            
                                                          
 

                                                                



                                                 

                                                                                     

























































































                                                                                                                             
                                                    
                                                                                                                    
 
                                                      

                                            
                                    





































                                                                                                    
                                                                                                                                
                                                                                                     

                                                                     




                                                               



                                                                      

                                                                          
 
                                                                                                                                         


                                                                      

                                                                            


                                                                                                                                                    
                 

                                                                    
                                                                                                                                
                                                                                                                                                                          
                                                                                   


                                                                                                                                                                


                                                                           


                                
                                          
                                             


                                                                                    

                                                                                                 

                                                                                 
                                                                                                                        

                                        

                                                                                      
                                                                                                                                                                         



                                                                                                       

                                                                                                        
                                                                           
                                                                        
                                                                                                                                 
                                                                                        
                                                                                                                                                       
                         
                          






























                                                                                                                                   
                           
                         
                    
                                                                                 

                                                                               


                                                                              
                                                                         
                       





                                                                                
                                                                             
                       
                        
                                                                                                      

                                                                 
                                                        


                                                                     
                                

                                                                                            
                                                                                               


                     
                       
                                            
                                    
                                                                                                                    
                                                                                  
                                                                                                                                 
                  
               

                     










                                                                                                 



                                                          

             
       
     
   
require 'active_record/associations/association_proxy'
require 'active_record/associations/association_collection'
require 'active_record/associations/belongs_to_association'
require 'active_record/associations/belongs_to_polymorphic_association'
require 'active_record/associations/has_one_association'
require 'active_record/associations/has_many_association'
require 'active_record/associations/has_many_through_association'
require 'active_record/associations/has_and_belongs_to_many_association'
require 'active_record/deprecated_associations'

module ActiveRecord
  class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
    def initialize(reflection)
      @reflection = reflection
    end
    
    def message
      "Could not find the association #{@reflection.options[:through].inspect} in model #{@reflection.klass}"
    end
  end

  class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
    def initialize(owner_class_name, reflection, source_reflection)
      @owner_class_name  = owner_class_name
      @reflection        = reflection
      @source_reflection = source_reflection
    end
    
    def message
      "Cannot have a has_many :through association '#{@owner_class_name}##{@reflection.name}' on the polymorphic object '#{@source_reflection.class_name}##{@source_reflection.name}'."
    end
  end

  class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
    def initialize(reflection)
      @reflection              = reflection
      @through_reflection      = reflection.through_reflection
      @source_reflection_names = reflection.source_reflection_names
      @source_associations     = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
    end
    
    def message
      "Could not find the source association(s) #{@source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{@through_reflection.klass}.  Try 'has_many #{@reflection.name.inspect}, :through => #{@through_reflection.name.inspect}, :source => <name>'.  Is it one of #{@source_associations.to_sentence :connector => 'or'}?"
    end
  end

  class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc
    def initialize(reflection)
      @reflection         = reflection
      @through_reflection = reflection.through_reflection
      @source_reflection  = reflection.source_reflection
    end
    
    def message
      "Invalid source reflection macro :#{@source_reflection.macro}#{" :through" if @source_reflection.options[:through]} for has_many #{@reflection.name.inspect}, :through => #{@through_reflection.name.inspect}.  Use :source to specify the source reflection."
    end
  end

  class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
    def initialize(reflection)
      @reflection = reflection
    end
    
    def message
      "Can not eagerly load the polymorphic association #{@reflection.name.inspect}"
    end
  end

  module Associations # :nodoc:
    def self.append_features(base)
      super
      base.extend(ClassMethods)
    end

    # Clears out the association cache 
    def clear_association_cache #:nodoc:
      self.class.reflect_on_all_associations.to_a.each do |assoc|
        instance_variable_set "@#{assoc.name}", nil
      end unless self.new_record?
    end
    
    # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like 
    # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are 
    # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr* 
    # methods. Example:
    #
    #   class Project < ActiveRecord::Base
    #     belongs_to              :portfolio
    #     has_one                 :project_manager 
    #     has_many                :milestones
    #     has_and_belongs_to_many :categories
    #   end
    #
    # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
    # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
    # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
    # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
    #   <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find_all(conditions),</tt>
    #   <tt>Project#milestones.build, Project#milestones.create</tt>
    # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
    #   <tt>Project#categories.delete(category1)</tt>
    #
    # == Example
    #
    # link:files/examples/associations.png
    #
    # == Is it belongs_to or has_one?
    #
    # Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class
    # saying belongs_to. Example:
    #
    #   class Post < ActiveRecord::Base
    #     has_one :author
    #   end
    #
    #   class Author < ActiveRecord::Base
    #     belongs_to :post
    #   end
    #
    # The tables for these classes could look something like:
    #
    #   CREATE TABLE posts (
    #     id int(11) NOT NULL auto_increment,
    #     title varchar default NULL,
    #     PRIMARY KEY  (id)
    #   )
    #
    #   CREATE TABLE authors (
    #     id int(11) NOT NULL auto_increment,
    #     post_id int(11) default NULL,
    #     name varchar default NULL,
    #     PRIMARY KEY  (id)
    #   )
    #
    # == Unsaved objects and associations
    #
    # You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
    # aware of, mostly involving the saving of associated objects.
    #
    # === One-to-one associations
    #
    # * Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in
    #   order to update their primary keys - except if the parent object is unsaved (new_record? == true).
    # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
    #   is cancelled.
    # * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
    # * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
    #   not save the parent either.
    #
    # === Collections
    #
    # * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
    #   (the owner of the collection) is not yet stored in the database.
    # * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
    # * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
    # * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
    #
    # === Association callbacks
    #
    # Similiar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
    # trigged when you add an object to or removing an object from a association collection. Example:
    #
    #   class Project
    #     has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
    #
    #     def evaluate_velocity(developer)
    #       ...
    #     end
    #   end 
    #
    # It's possible to stack callbacks by passing them as an array. Example:
    # 
    #   class Project
    #     has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
    #   end
    #
    # Possible callbacks are: before_add, after_add, before_remove and after_remove.
    #
    # Should any of the before_add callbacks throw an exception, the object does not get added to the collection. Same with
    # the before_remove callbacks, if an exception is thrown the object doesn't get removed.
    #
    # === Association extensions
    #
    # The proxy objects that controls the access to associations can be extended through anonymous modules. This is especially
    # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
    # Example:
    #
    #   class Account < ActiveRecord::Base
    #     has_many :people do
    #       def find_or_create_by_name(name)
    #         first_name, last_name = name.split(" ", 2)
    #         find_or_create_by_first_name_and_last_name(first_name, last_name)
    #       end
    #     end
    #   end
    #
    #   person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
    #   person.first_name # => "David"
    #   person.last_name  # => "Heinemeier Hansson"
    #
    # If you need to share the same extensions between many associations, you can use a named extension module. Example:
    #
    #   module FindOrCreateByNameExtension
    #     def find_or_create_by_name(name)
    #       first_name, last_name = name.split(" ", 2)
    #       find_or_create_by_first_name_and_last_name(first_name, last_name)
    #     end
    #   end
    #
    #   class Account < ActiveRecord::Base
    #     has_many :people, :extend => FindOrCreateByNameExtension
    #   end
    #
    #   class Company < ActiveRecord::Base
    #     has_many :people, :extend => FindOrCreateByNameExtension
    #   end
    #
    # If you need to use multiple named extension modules, you can specify an array of modules with the :extend option.
    # In the case of name conflicts between methods in the modules, methods in modules later in the array supercede
    # those earlier in the array. Example:
    #
    #   class Account < ActiveRecord::Base
    #     has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
    #   end
    #
    # === Association Join Models
    # 
    # Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data.  This
    # operates similarly to a <tt>has_and_belongs_to_many</tt> association.  The advantage is that you're able to add validations,
    # callbacks, and extra attributes on the join model.  Consider the following schema:
    # 
    #   class Author < ActiveRecord::Base
    #     has_many :authorships
    #     has_many :books, :through => :authorships
    #   end
    # 
    #   class Authorship < ActiveRecord::Base
    #     belongs_to :author
    #     belongs_to :book
    #   end
    # 
    #   @author = Author.find :first
    #   @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
    #   @author.books                              # selects all books by using the Authorship join model
    # 
    # You can also go through a has_many association on the join model:
    # 
    #   class Firm < ActiveRecord::Base
    #     has_many   :clients
    #     has_many   :invoices, :through => :clients
    #   end
    #   
    #   class Client < ActiveRecord::Base
    #     belongs_to :firm
    #     has_many   :invoices
    #   end
    #   
    #   class Invoice < ActiveRecord::Base
    #     belongs_to :client
    #   end
    #
    #   @firm = Firm.find :first
    #   @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
    #   @firm.invoices                                   # selects all invoices by going through the Client join model.
    #
    # === Polymorphic Associations
    # 
    # Polymorphic associations on models are not restricted on what types of models they can be associated with.  Rather, they 
    # specify an interface that a has_many association must adhere to.
    # 
    #   class Asset < ActiveRecord::Base
    #     belongs_to :attachable, :polymorphic => true
    #   end
    # 
    #   class Post < ActiveRecord::Base
    #     has_many :assets, :as => :attachable         # The <tt>:as</tt> option specifies the polymorphic interface to use.
    #   end
    #
    #   @asset.attachable = @post
    # 
    # This works by using a type column in addition to a foreign key to specify the associated record.  In the Asset example, you'd need
    # an attachable_id integer column and an attachable_type string column.
    #
    # == Caching
    #
    # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
    # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without 
    # worrying too much about performance at the first go. Example:
    #
    #   project.milestones             # fetches milestones from the database
    #   project.milestones.size        # uses the milestone cache
    #   project.milestones.empty?      # uses the milestone cache
    #   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)
    #     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, :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, :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 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.
    #
    # Please note that limited eager loading with has_many and has_and_belongs_to_many associations is not compatible with describing conditions
    # on these eager tables. This will work:
    #
    #   Post.find(:all, :include => :comments, :conditions => "posts.title = 'magic forest'", :limit => 2)
    #
    # ...but this will not (and an ArgumentError will be raised):
    #
    #   Post.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%'", :limit => 2)
    #
    # Also have in mind that since the eager loading is pulling from multiple tables, you'll have to disambiguate any column references
    # in both conditions and orders. So :order => "posts.id DESC" will work while :order => "id DESC" will not. This may require that
    # you alter the :order and :conditions on the association definitions themselves.
    #
    # It's currently not possible to use eager loading on multiple associations from the same table. Eager loading will not pull
    # additional attributes on join tables, so "rich associations" with has_and_belongs_to_many is not a good fit for eager loading.
    # 
    # == 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 #{reflection_name}_#{parent_table_name}.  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
    # 
    # 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
    # 
    # Has and Belongs to Many join tables use the same idea, but add a _join 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
    # 
    # If you wish to specify your own custom joins using a :joins 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 ...
    #                              INNER JOIN comments ...
    # 
    # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
    # 
    # == Modules
    #
    # By default, associations will look for objects within the current module scope. Consider:
    #
    #   module MyApplication
    #     module Business
    #       class Firm < ActiveRecord::Base
    #          has_many :clients
    #        end
    #
    #       class Company < ActiveRecord::Base; end
    #     end
    #   end
    #
    # When Firm#clients is called, it'll in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
    # with a class in another module scope this can be done by specifying the complete class name, such as:
    #
    #   module MyApplication
    #     module Business
    #       class Firm < ActiveRecord::Base; end
    #     end
    #
    #     module Billing
    #       class Account < ActiveRecord::Base
    #         belongs_to :firm, :class_name => "MyApplication::Business::Firm"
    #       end
    #     end
    #   end
    #
    # == Type safety with ActiveRecord::AssociationTypeMismatch
    #
    # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
    # get a ActiveRecord::AssociationTypeMismatch.
    #
    # == Options
    #
    # All of the association macros can be specialized through options which makes more complex cases than the simple and guessable ones
    # possible.
    module ClassMethods
      # Adds the following methods for retrieval and query of collections of associated objects.
      # +collection+ is replaced with the symbol passed as the first argument, so 
      # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
      # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
      #   An empty array is returned if none are found.
      # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
      # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.  
      #   This will also destroy the objects if they're declared as belongs_to and dependent on this model.
      # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
      # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
      # * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
      #   are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:dependent => :delete_all</tt>,
      #   and sets their foreign keys to NULL otherwise.
      # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
      # * <tt>collection.size</tt> - returns the number of associated objects.
      # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
      # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key but has not yet been saved. *Note:* This only works if an 
      #   associated object already exists, not if it's nil!
      # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
      #   *Note:* This only works if an associated object already exists, not if it's nil!
      #
      # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
      # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
      # * <tt>Firm#clients<<</tt>
      # * <tt>Firm#clients.delete</tt>
      # * <tt>Firm#clients=</tt>
      # * <tt>Firm#client_ids=</tt>
      # * <tt>Firm#clients.clear</tt>
      # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
      # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
      # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
      # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
      # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
      # The declaration can also include an options hash to specialize the behavior of the association.
      # 
      # Options are:
      # * <tt>:class_name</tt>  - specify the class name of the association. Use it only if that name can't be inferred
      #   from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but
      #   if the real class name is +SpecialProduct+, you'll have to specify it with this option.
      # * <tt>:conditions</tt>  - specify the conditions that the associated objects must meet in order to be included as a "WHERE"
      #   sql fragment, such as "price > 5 AND name LIKE 'B%'".
      # * <tt>:order</tt>       - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment,
      #   such as "last_name, first_name DESC"
      # * <tt>:group</tt>       - specify the attribute by which the associated objects are returned as a "GROUP BY" sql fragment,
      #   such as "category"      
      # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
      #   of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_many association will use "person_id"
      #   as the default foreign_key.
      # * <tt>:dependent</tt>   - if set to :destroy all the associated objects are destroyed
      #   alongside this object by calling their destroy method.  If set to :delete_all all associated
      #   objects are deleted *without* calling their destroy method.  If set to :nullify all associated
      #   objects' foreign keys are set to NULL *without* calling their save callbacks.
      #   NOTE: :dependent => true is deprecated and has been replaced with :dependent => :destroy. 
      #   May not be set if :exclusively_dependent is also set.
      # * <tt>:exclusively_dependent</tt>   - Deprecated; equivalent to :dependent => :delete_all. If set to true all
      #   the associated object are deleted in one SQL statement without having their
      #   before_destroy callback run. This should only be used on associations that depend solely on this class and don't need to do any
      #   clean-up in before_destroy. The upside is that it's much faster, especially if there's a counter_cache involved.
      #   May not be set if :dependent is also set.
      # * <tt>:finder_sql</tt>  - specify a complete SQL statement to fetch the association. This is a good way to go for complex
      #   associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
      # * <tt>:counter_sql</tt>  - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
      #   specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
      # * <tt>:extend</tt>  - specify a named module for extending the proxy, see "Association extensions".
      # * <tt>:include</tt>  - specify second-order associations that should be eager loaded when the collection is loaded.
      # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
      # * <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>: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.
      # * <tt>:as</tt>: Specifies a polymorphic interface (See #belongs_to).
      # * <tt>:through</tt>: Specifies a Join Model to perform the query through.  Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> 
      #   are ignored, as the association uses the source reflection.  You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
      #   or <tt>has_many</tt> association.
      # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries.  Only use it if the name cannot be 
      #   inferred from the association.  <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
      #   +:subscriber+ on +Subscription+, unless a +:source+ is given.
      #
      # Option examples:
      #   has_many :comments, :order => "posted_on"
      #   has_many :comments, :include => :author
      #   has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
      #   has_many :tracks, :order => "position", :dependent => :destroy
      #   has_many :comments, :dependent => :nullify
      #   has_many :tags, :as => :taggable
      #   has_many :subscribers, :through => :subscriptions, :source => :user
      #   has_many :subscribers, :class_name => "Person", :finder_sql =>
      #       'SELECT DISTINCT people.* ' +
      #       'FROM people p, post_subscriptions ps ' +
      #       'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
      #       'ORDER BY p.first_name'
      def has_many(association_id, options = {}, &extension)
        reflection = create_has_many_reflection(association_id, options, &extension)

        configure_dependency_for_has_many(reflection)

        if options[:through]
          collection_reader_method(reflection, HasManyThroughAssociation)
        else
          add_multiple_associated_save_callbacks(reflection.name)
          add_association_callbacks(reflection.name, reflection.options)
          collection_accessor_methods(reflection, HasManyAssociation)
        end

        add_deprecated_api_for_has_many(reflection.name)
      end

      # Adds the following methods for retrieval and query of a single associated object.
      # +association+ is replaced with the symbol passed as the first argument, so 
      # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
      # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
      # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key, 
      #   and saves the associate object.
      # * <tt>association.nil?</tt> - returns true if there is no associated object.
      # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key but has not yet been saved. Note: This ONLY works if
      #   an association already exists. It will NOT work if the association is nil.
      # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
      #
      # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
      # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
      # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
      # * <tt>Account#beneficiary.nil?</tt>
      # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
      # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
      #
      # The declaration can also include an options hash to specialize the behavior of the association.
      # 
      # Options are:
      # * <tt>:class_name</tt>  - specify the class name of the association. Use it only if that name can't be inferred
      #   from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but
      #   if the real class name is +Person+, you'll have to specify it with this option.
      # * <tt>:conditions</tt>  - specify the conditions that the associated object must meet in order to be included as a "WHERE"
      #   sql fragment, such as "rank = 5".
      # * <tt>:order</tt>       - specify the order from which the associated object will be picked at the top. Specified as
      #    an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
      # * <tt>:dependent</tt>   - if set to :destroy (or true) all the associated objects are destroyed when this object is. Also,
      #   association is assigned.
      # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
      #   of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
      #   as the default foreign_key.
      # * <tt>:include</tt>  - specify second-order associations that should be eager loaded when this object is loaded.
      #
      # Option examples:
      #   has_one :credit_card, :dependent => :destroy  # destroys the associated credit card
      #   has_one :credit_card, :dependent => :nullify  # updates the associated records foriegn key value to null rather than destroying it
      #   has_one :last_comment, :class_name => "Comment", :order => "posted_on"
      #   has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
      def has_one(association_id, options = {})
        reflection = create_has_one_reflection(association_id, options)

        module_eval do
          after_save <<-EOF
            association = instance_variable_get("@#{reflection.name}")
            unless association.nil?
              association["#{reflection.primary_key_name}"] = id
              association.save(true)
            end
          EOF
        end
      
        association_accessor_methods(reflection, HasOneAssociation)
        association_constructor_method(:build,  reflection, HasOneAssociation)
        association_constructor_method(:create, reflection, HasOneAssociation)
        
        configure_dependency_for_has_one(reflection)

        # deprecated api
        deprecated_has_association_method(reflection.name)
        deprecated_association_comparison_method(reflection.name, reflection.class_name)
      end

      # Adds the following methods for retrieval and query for a single associated object that this object holds an id to.
      # +association+ is replaced with the symbol passed as the first argument, so 
      # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
      # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
      # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
      # * <tt>association.nil?</tt> - returns true if there is no associated object.
      # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key but has not yet been saved.
      # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
      #
      # Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
      # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
      # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
      # * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
      # * <tt>Post#author.nil?</tt>
      # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
      # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
      # The declaration can also include an options hash to specialize the behavior of the association.
      # 
      # Options are:
      # * <tt>:class_name</tt>  - specify the class name of the association. Use it only if that name can't be inferred
      #   from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but
      #   if the real class name is +Person+, you'll have to specify it with this option.
      # * <tt>:conditions</tt>  - specify the conditions that the associated object must meet in order to be included as a "WHERE"
      #   sql fragment, such as "authorized = 1".
      # * <tt>:order</tt>       - specify the order from which the associated object will be picked at the top. Specified as
      #   an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
      # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
      #   of the associated class in lower-case and "_id" suffixed. So a +Person+ class that makes a belongs_to association to a
      #   +Boss+ class will use "boss_id" as the default foreign_key.
      # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through use of increment_counter 
      #   and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it's
      #   destroyed. This requires that a column named "#{table_name}_count" (such as comments_count for a belonging Comment class)
      #   is used on the associate class (such as a Post class). You can also specify a custom counter cache column by given that
      #   name instead of a true/false value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
      # * <tt>:include</tt>  - specify second-order associations that should be eager loaded when this object is loaded.
      # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing true.
      #
      # Option examples:
      #   belongs_to :firm, :foreign_key => "client_of"
      #   belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
      #   belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id", 
      #              :conditions => 'discounts > #{payments_count}'
      #   belongs_to :attachable, :polymorphic => true
      def belongs_to(association_id, options = {})
        reflection = create_belongs_to_reflection(association_id, options)
        
        if reflection.options[:polymorphic]
          association_accessor_methods(reflection, BelongsToPolymorphicAssociation)

          module_eval do
            before_save <<-EOF
              association = instance_variable_get("@#{reflection.name}")
              if !association.nil? 
                if association.new_record?
                  association.save(true)
                end
                
                if association.updated?
                  self["#{reflection.primary_key_name}"] = association.id
                  self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
                end
              end
            EOF
          end
        else
          association_accessor_methods(reflection, BelongsToAssociation)
          association_constructor_method(:build,  reflection, BelongsToAssociation)
          association_constructor_method(:create, reflection, BelongsToAssociation)

          module_eval do
            before_save <<-EOF
              association = instance_variable_get("@#{reflection.name}")
              if !association.nil? 
                if association.new_record?
                  association.save(true)
                end
                
                if association.updated?
                  self["#{reflection.primary_key_name}"] = association.id
                end
              end            
            EOF
          end
      
          # deprecated api
          deprecated_has_association_method(reflection.name)
          deprecated_association_comparison_method(reflection.name, reflection.class_name)
        end

        if options[:counter_cache]
          cache_column = options[:counter_cache] == true ?
            "#{self.to_s.underscore.pluralize}_count" :
            options[:counter_cache]

          module_eval(
            "after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
            " unless #{reflection.name}.nil?'"
          )

          module_eval(
            "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
            " unless #{reflection.name}.nil?'"
          )          
        end
      end

      # Associates two classes via an intermediate join table.  Unless the join table is explicitly specified as
      # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
      # will give the default join table name of "developers_projects" because "D" outranks "P".
      #
      # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
      # has_and_belongs_to_many associations. Records returned from join tables with additional attributes will be marked as
      # ReadOnly (because we can't save changes to the additional attrbutes). It's strongly recommended that you upgrade any
      # associations with attributes to a real join model (see introduction).
      #
      # Adds the following methods for retrieval and query.
      # +collection+ is replaced with the symbol passed as the first argument, so 
      # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
      # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
      #   An empty array is returned if none is found.
      # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table 
      #   (collection.push and collection.concat are aliases to this method).
      # * <tt>collection.push_with_attributes(object, join_attributes)</tt> - adds one to the collection by creating an association in the join table that
      #   also holds the attributes from <tt>join_attributes</tt> (should be a hash with the column names as keys). This can be used to have additional
      #   attributes on the join, which will be injected into the associated objects when they are retrieved through the collection.
      #   (collection.concat_with_attributes is an alias to this method). This method is now deprecated.
      # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.  
      #   This does not destroy the objects.
      # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
      # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
      # * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
      # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
      # * <tt>collection.size</tt> - returns the number of associated objects.
      # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
      #   meets the condition that it has to be associated with this object.
      #
      # Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
      # * <tt>Developer#projects</tt>
      # * <tt>Developer#projects<<</tt>
      # * <tt>Developer#projects.push_with_attributes</tt>
      # * <tt>Developer#projects.delete</tt>
      # * <tt>Developer#projects=</tt>
      # * <tt>Developer#project_ids=</tt>
      # * <tt>Developer#projects.clear</tt>
      # * <tt>Developer#projects.empty?</tt>
      # * <tt>Developer#projects.size</tt>
      # * <tt>Developer#projects.find(id)</tt>
      # The declaration may include an options hash to specialize the behavior of the association.
      # 
      # Options are:
      # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
      #   from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the 
      #   +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option.
      # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want.
      #   WARNING: If you're overwriting the table name of either class, the table_name method MUST be declared underneath any
      #   has_and_belongs_to_many declaration in order to work.
      # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
      #   of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_and_belongs_to_many association
      #   will use "person_id" as the default foreign_key.
      # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is
      #   guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is +Project+,
      #   the has_and_belongs_to_many association will use "project_id" as the default association foreign_key.
      # * <tt>:conditions</tt>  - specify the conditions that the associated object must meet in order to be included as a "WHERE"
      #   sql fragment, such as "authorized = 1".
      # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, such as "last_name, first_name DESC"
      # * <tt>:uniq</tt> - if set to true, duplicate associated objects will be ignored by accessors and query methods
      # * <tt>:finder_sql</tt> - overwrite the default generated SQL used to fetch the association with a manual one
      # * <tt>:delete_sql</tt> - overwrite the default generated SQL used to remove links between the associated 
      #   classes with a manual one
      # * <tt>:insert_sql</tt> - overwrite the default generated SQL used to add links between the associated classes
      #   with a manual one
      # * <tt>:extend</tt>  - anonymous module for extending the proxy, see "Association extensions".
      # * <tt>:include</tt>  - specify second-order associations that should be eager loaded when the collection is loaded.
      # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
      # * <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>: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.
      #
      # Option examples:
      #   has_and_belongs_to_many :projects
      #   has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
      #   has_and_belongs_to_many :nations, :class_name => "Country"
      #   has_and_belongs_to_many :categories, :join_table => "prods_cats"
      #   has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql => 
      #   'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
      def has_and_belongs_to_many(association_id, options = {}, &extension)
        reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
        
        add_multiple_associated_save_callbacks(reflection.name)
        collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)

        # Don't use a before_destroy callback since users' before_destroy
        # callbacks will be executed after the association is wiped out.
        old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
        class_eval <<-end_eval
          alias_method :#{old_method}, :destroy_without_callbacks
          def destroy_without_callbacks
            #{reflection.name}.clear
            #{old_method}
          end
        end_eval

        add_association_callbacks(reflection.name, options)
        
        # deprecated api
        deprecated_collection_count_method(reflection.name)
        deprecated_add_association_relation(reflection.name)
        deprecated_remove_association_relation(reflection.name)
        deprecated_has_collection_method(reflection.name)
      end

      private
        def join_table_name(first_table_name, second_table_name)
          if first_table_name < second_table_name
            join_table = "#{first_table_name}_#{second_table_name}"
          else
            join_table = "#{second_table_name}_#{first_table_name}"
          end

          table_name_prefix + join_table + table_name_suffix
        end
        
        def association_accessor_methods(reflection, association_proxy_class)
          define_method(reflection.name) do |*params|
            force_reload = params.first unless params.empty?
            association = instance_variable_get("@#{reflection.name}")

            if association.nil? || force_reload
              association = association_proxy_class.new(self, reflection)
              retval = association.reload
              unless retval.nil?
                instance_variable_set("@#{reflection.name}", association)
              else
                instance_variable_set("@#{reflection.name}", nil)
                return nil
              end
            end
            association
          end

          define_method("#{reflection.name}=") do |new_value|
            association = instance_variable_get("@#{reflection.name}")
            if association.nil?
              association = association_proxy_class.new(self, reflection)
            end

            association.replace(new_value)

            unless new_value.nil?
              instance_variable_set("@#{reflection.name}", association)
            else
              instance_variable_set("@#{reflection.name}", nil)
              return nil
            end

            association
          end

          define_method("set_#{reflection.name}_target") do |target|
            return if target.nil?
            association = association_proxy_class.new(self, reflection)
            association.target = target
            instance_variable_set("@#{reflection.name}", association)
          end
        end

        def collection_reader_method(reflection, association_proxy_class)
          define_method(reflection.name) do |*params|
            force_reload = params.first unless params.empty?
            association = instance_variable_get("@#{reflection.name}")

            unless association.respond_to?(:loaded?)
              association = association_proxy_class.new(self, reflection)
              instance_variable_set("@#{reflection.name}", association)
            end

            association.reload if force_reload

            association
          end
        end

        def collection_accessor_methods(reflection, association_proxy_class)
          collection_reader_method(reflection, association_proxy_class)

          define_method("#{reflection.name}=") do |new_value|
            association = instance_variable_get("@#{reflection.name}")
            unless association.respond_to?(:loaded?)
              association = association_proxy_class.new(self, reflection)
              instance_variable_set("@#{reflection.name}", association)
            end
            association.replace(new_value)
            association
          end

          define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
            send("#{reflection.name}=", reflection.class_name.constantize.find(new_value))
          end
        end

        def require_association_class(class_name)
          require_association(Inflector.underscore(class_name)) if class_name
        end

        def add_multiple_associated_save_callbacks(association_name)
          method_name = "validate_associated_records_for_#{association_name}".to_sym
          define_method(method_name) do
            association = instance_variable_get("@#{association_name}")
            if association.respond_to?(:loaded?)
              if new_record?
                association
              else
                association.select { |record| record.new_record? }
              end.each do |record|
                errors.add "#{association_name}" unless record.valid?
              end
            end
          end

          validate method_name
          before_save("@new_record_before_save = new_record?; true")

          after_callback = <<-end_eval
            association = instance_variable_get("@#{association_name}")
            
            if association.respond_to?(:loaded?)
              if @new_record_before_save
                records_to_save = association
              else
                records_to_save = association.select { |record| record.new_record? }
              end
              records_to_save.each { |record| association.send(:insert_record, record) }
              association.send(:construct_sql)   # reconstruct the SQL queries now that we know the owner's id
            end
          end_eval
                
          # Doesn't use after_save as that would save associations added in after_create/after_update twice
          after_create(after_callback)
          after_update(after_callback)
        end

        def association_constructor_method(constructor, reflection, association_proxy_class)
          define_method("#{constructor}_#{reflection.name}") do |*params|
            attributees      = params.first unless params.empty?
            replace_existing = params[1].nil? ? true : params[1]
            association      = instance_variable_get("@#{reflection.name}")

            if association.nil?
              association = association_proxy_class.new(self, reflection)
              instance_variable_set("@#{reflection.name}", association)
            end

            if association_proxy_class == HasOneAssociation
              association.send(constructor, attributees, replace_existing)
            else
              association.send(constructor, attributees)
            end
          end
        end
        
        def count_with_associations(options = {})
          catch :invalid_query do
            join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
            return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency))
          end
          0
        end

        def find_with_associations(options = {})
          catch :invalid_query do
            join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
            rows = select_all_rows(options, join_dependency)
            return join_dependency.instantiate(rows)
          end
          []
        end

        def configure_dependency_for_has_many(reflection)
          if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
            raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options.  You may specify one or the other.'
          end

          if reflection.options[:exclusively_dependent]
            reflection.options[:dependent] = :delete_all
            #warn "The :exclusively_dependent option is deprecated.  Please use :dependent => :delete_all instead.")
          end

          # See HasManyAssociation#delete_records.  Dependent associations
          # delete children, otherwise foreign key is set to NULL.

          # Add polymorphic type if the :as option is present
          dependent_conditions = %(#{reflection.primary_key_name} = \#{record.quoted_id})
          if reflection.options[:as]
            dependent_conditions += " AND #{reflection.options[:as]}_type = '#{base_class.name}'"
          end

          case reflection.options[:dependent]
            when :destroy, true  
              module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
            when :delete_all
              module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
            when :nullify
              module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL),  %(#{dependent_conditions})) }"
            when nil, false
              # pass
            else
              raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify' 
          end
        end
        
        def configure_dependency_for_has_one(reflection)
          case reflection.options[:dependent]
            when :destroy, true
              module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
            when :nullify
              module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'"
            when nil, false
              # pass
            else
              raise ArgumentError, "The :dependent option expects either :destroy or :nullify."
          end
        end
        
        
        def add_deprecated_api_for_has_many(association_name)
          deprecated_collection_count_method(association_name)
          deprecated_add_association_relation(association_name)
          deprecated_remove_association_relation(association_name)
          deprecated_has_collection_method(association_name)
          deprecated_find_in_collection_method(association_name)
          deprecated_find_all_in_collection_method(association_name)
          deprecated_collection_create_method(association_name)
          deprecated_collection_build_method(association_name)
        end

        def create_has_many_reflection(association_id, options, &extension)
          options.assert_valid_keys(
            :class_name, :table_name, :foreign_key,
            :exclusively_dependent, :dependent,
            :select, :conditions, :include, :order, :group, :limit, :offset,
            :as, :through, :source,
            :finder_sql, :counter_sql, 
            :before_add, :after_add, :before_remove, :after_remove, 
            :extend
          )

          options[:extend] = create_extension_module(association_id, extension) if block_given?

          create_reflection(:has_many, association_id, options, self)
        end

        def create_has_one_reflection(association_id, options)
          options.assert_valid_keys(
            :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as
          )

          create_reflection(:has_one, association_id, options, self)
        end

        def create_belongs_to_reflection(association_id, options)
          options.assert_valid_keys(
            :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, 
            :counter_cache, :extend, :polymorphic
          )
          
          reflection = create_reflection(:belongs_to, association_id, options, self)

          if options[:polymorphic]
            reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
          end

          reflection
        end
        
        def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
          options.assert_valid_keys(
            :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, 
            :select, :conditions, :include, :order, :group, :limit, :offset,
            :finder_sql, :delete_sql, :insert_sql, :uniq, 
            :before_add, :after_add, :before_remove, :after_remove, 
            :extend
          )

          options[:extend] = create_extension_module(association_id, extension) if block_given?

          reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)

          reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
          
          reflection
        end

        def reflect_on_included_associations(associations)
          [ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
        end

        def guard_against_unlimitable_reflections(reflections, options)
          if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections)
            raise(
              ConfigurationError, 
              "You can not use offset and limit together with has_many or has_and_belongs_to_many associations"
            )
          end
        end

        def select_all_rows(options, join_dependency)
          connection.select_all(
            construct_finder_sql_with_included_associations(options, join_dependency),
            "#{name} Load Including Associations"
          )
        end
        
        def construct_counter_sql_with_included_associations(options, join_dependency)
          scope = scope(:find)
          sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})"
          
          # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
          if !Base.connection.supports_count_distinct?
            sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{table_name}.#{primary_key}"
          end
          
          sql << " FROM #{table_name} "
          sql << join_dependency.join_associations.collect{|join| join.association_join }.join
          
          add_joins!(sql, options, scope)
          add_conditions!(sql, options[:conditions], scope)
          add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])

          add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)

          if !Base.connection.supports_count_distinct?
            sql << ")"
          end

          return sanitize_sql(sql)          
        end

        def construct_finder_sql_with_included_associations(options, join_dependency)
          scope = scope(:find)
          sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || table_name} "
          sql << join_dependency.join_associations.collect{|join| join.association_join }.join
 
          add_joins!(sql, options, scope)
          add_conditions!(sql, options[:conditions], scope)
          add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]

          sql << "ORDER BY #{options[:order]} " if options[:order]
 
          add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
 
          return sanitize_sql(sql)
        end
 
        def add_limited_ids_condition!(sql, options, join_dependency)
          unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
            sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) "
          else
            throw :invalid_query
          end
        end
 
        def select_limited_ids_list(options, join_dependency)
          connection.select_values(
            construct_finder_sql_for_association_limiting(options, join_dependency),
            "#{name} Load IDs For Limited Eager Loading"
          ).collect { |id| connection.quote(id) }.join(", ")
        end
 
        def construct_finder_sql_for_association_limiting(options, join_dependency)
          scope = scope(:find)
          #sql = "SELECT DISTINCT #{table_name}.#{primary_key} FROM #{table_name} "
          sql = "SELECT "
          sql << "DISTINCT #{table_name}." if include_eager_conditions?(options) || include_eager_order?(options)
          sql << "#{primary_key} FROM #{table_name} "
          
          if include_eager_conditions?(options) || include_eager_order?(options)
            sql << join_dependency.join_associations.collect{|join| join.association_join }.join
            add_joins!(sql, options, scope)
          end
          
          add_conditions!(sql, options[:conditions], scope)
          sql << "ORDER BY #{options[:order]} " if options[:order]
          add_limit!(sql, options, scope)
          return sanitize_sql(sql)
        end

        def include_eager_conditions?(options)
          conditions = scope(:find, :conditions) || options[:conditions]
          return false unless conditions
          conditions = conditions.first if conditions.is_a?(Array)
          conditions.scan(/(\w+)\.\w+/).flatten.any? do |condition_table_name|
            condition_table_name != table_name
          end
        end
        
        def include_eager_order?(options)
          order = options[:order]
          return false unless order
          order.scan(/(\w+)\.\w+/).flatten.any? do |order_table_name|
            order_table_name != table_name
          end
        end

        def using_limitable_reflections?(reflections)
          reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
        end

        def column_aliases(join_dependency)
          join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
              "#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
        end

        def add_association_callbacks(association_name, options)
          callbacks = %w(before_add after_add before_remove after_remove)
          callbacks.each do |callback_name|
            full_callback_name = "#{callback_name.to_s}_for_#{association_name.to_s}"
            defined_callbacks = options[callback_name.to_sym]
            if options.has_key?(callback_name.to_sym)
              class_inheritable_reader full_callback_name.to_sym
              write_inheritable_array(full_callback_name.to_sym, [defined_callbacks].flatten)
            end
          end
        end

        def condition_word(sql)
          sql =~ /where/i ? " AND " : "WHERE "
        end

        def create_extension_module(association_id, extension)
          extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"

          silence_warnings do
            Object.const_set(extension_module_name, Module.new(&extension))
          end
          
          extension_module_name.constantize
        end

        class JoinDependency
          attr_reader :joins, :reflections, :table_aliases

          def initialize(base, associations, joins)
            @joins                 = [JoinBase.new(base, joins)]
            @associations          = associations
            @reflections           = []
            @base_records_hash     = {}
            @base_records_in_order = []
            @table_aliases         = Hash.new { |aliases, table| aliases[table] = 0 }
            @table_aliases[base.table_name] = 1
            build(associations)
          end

          def join_associations
            @joins[1..-1].to_a
          end

          def join_base
            @joins[0]
          end

          def instantiate(rows)
            rows.each_with_index do |row, i|
              primary_id = join_base.record_id(row)
              unless @base_records_hash[primary_id]
                @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
              end
              construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
            end
            return @base_records_in_order
          end

          def aliased_table_names_for(table_name)
            joins.select{|join| join.table_name == table_name }.collect{|join| join.aliased_table_name}
          end

          protected
            def build(associations, parent = nil)
              parent ||= @joins.last
              case associations
                when Symbol, String
                  reflection = parent.reflections[associations.to_s.intern] or
                  raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
                  @reflections << reflection
                  @joins << JoinAssociation.new(reflection, self, parent)
                when Array
                  associations.each do |association|
                    build(association, parent)
                  end
                when Hash
                  associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
                    build(name, parent)
                    build(associations[name])
                  end
                else
                  raise ConfigurationError, associations.inspect
              end
            end

            def construct(parent, associations, joins, row)
              case associations
                when Symbol, String
                  while (join = joins.shift).reflection.name.to_s != associations.to_s
                    raise ConfigurationError, "Not Enough Associations" if joins.empty?
                  end
                  construct_association(parent, join, row)
                when Array
                  associations.each do |association|
                    construct(parent, association, joins, row)
                  end
                when Hash
                  associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
                    association = construct_association(parent, joins.shift, row)
                    construct(association, associations[name], joins, row) if association
                  end
                else
                  raise ConfigurationError, associations.inspect
              end
            end

            def construct_association(record, join, row)
              case join.reflection.macro
                when :has_many, :has_and_belongs_to_many
                  collection = record.send(join.reflection.name)
                  collection.loaded
    
                  return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
                  association = join.instantiate(row)
                  collection.target.push(association) unless collection.target.include?(association)
                when :has_one, :belongs_to
                  return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
                  association = join.instantiate(row)
                  record.send("set_#{join.reflection.name}_target", association)
                else
                  raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
              end
              return association
            end

          class JoinBase
            attr_reader :active_record, :table_joins
            delegate    :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record

            def initialize(active_record, joins = nil)
              @active_record = active_record
              @cached_record = {}
              @table_joins   = joins
            end

            def aliased_prefix
              "t0"
            end

            def aliased_primary_key
              "#{ aliased_prefix }_r0"
            end

            def aliased_table_name
              active_record.table_name
            end

            def column_names_with_alias
              unless @column_names_with_alias
                @column_names_with_alias = []
                ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
                  @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
                end
              end
              return @column_names_with_alias
            end

            def extract_record(row)
              column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
            end

            def record_id(row)
              row[aliased_primary_key]
            end

            def instantiate(row)
              @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
            end
          end

          class JoinAssociation < JoinBase
            attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
            delegate    :options, :klass, :through_reflection, :source_reflection, :to => :reflection

            def initialize(reflection, join_dependency, parent = nil)
              reflection.check_validity!
              if reflection.options[:polymorphic]
                raise EagerLoadPolymorphicError.new(reflection)
              end

              super(reflection.klass)
              @parent             = parent
              @reflection         = reflection
              @aliased_prefix     = "t#{ join_dependency.joins.size }"
              @aliased_table_name = table_name # start with the table name
              @parent_table_name  = parent.active_record.table_name

              if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{aliased_table_name.downcase}\son}
                join_dependency.table_aliases[aliased_table_name] += 1
              end
              
              unless join_dependency.table_aliases[aliased_table_name].zero?
                # if the table name has been used, then use an alias
                @aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}"
                table_index = join_dependency.table_aliases[aliased_table_name]
                @aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
              end
              join_dependency.table_aliases[aliased_table_name] += 1
              
              if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
                @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name
                unless join_dependency.table_aliases[aliased_join_table_name].zero?
                  @aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join"
                  table_index = join_dependency.table_aliases[aliased_join_table_name]
                  @aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
                end
                join_dependency.table_aliases[aliased_join_table_name] += 1
              end
            end

            def association_join
              join = case reflection.macro
                when :has_and_belongs_to_many
                  " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                     table_alias_for(options[:join_table], aliased_join_table_name),
                     aliased_join_table_name,
                     options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
                     reflection.active_record.table_name, reflection.active_record.primary_key] +
                  " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                     table_name_and_alias, aliased_table_name, klass.primary_key,
                     aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
                     ]
                when :has_many, :has_one
                  case
                    when reflection.macro == :has_many && reflection.options[:through]
                      through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
                      if through_reflection.options[:as] # has_many :through against a polymorphic join
                        polymorphic_foreign_key  = through_reflection.options[:as].to_s + '_id'
                        polymorphic_foreign_type = through_reflection.options[:as].to_s + '_type'

                        " LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) "  % [
                          table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
                          aliased_join_table_name, polymorphic_foreign_key,
                          parent.aliased_table_name, parent.primary_key,
                          aliased_join_table_name, polymorphic_foreign_type, klass.quote(parent.active_record.base_class.name)] +
                        " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
                          aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
                        ]
                      else
                        if source_reflection.macro == :has_many && source_reflection.options[:as]
                          " LEFT OUTER JOIN %s ON %s.%s = %s.%s "  % [
                            table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
                            through_reflection.primary_key_name,
                            parent.aliased_table_name, parent.primary_key] +
                          " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
                            table_name_and_alias,
                            aliased_table_name, "#{source_reflection.options[:as]}_id", 
                            aliased_join_table_name, options[:foreign_key] || primary_key,
                            aliased_table_name, "#{source_reflection.options[:as]}_type", 
                            klass.quote(source_reflection.active_record.base_class.name)
                          ]
                        else
                          case source_reflection.macro
                            when :belongs_to
                              first_key  = primary_key
                              second_key = options[:foreign_key] || klass.to_s.classify.foreign_key
                            when :has_many
                              first_key  = through_reflection.klass.to_s.classify.foreign_key
                              second_key = options[:foreign_key] || primary_key
                          end
                          
                          " LEFT OUTER JOIN %s ON %s.%s = %s.%s "  % [
                            table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
                            through_reflection.primary_key_name,
                            parent.aliased_table_name, parent.primary_key] +
                          " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                            table_name_and_alias,
                            aliased_table_name, first_key, 
                            aliased_join_table_name, second_key
                          ]
                        end
                      end
                    
                    when reflection.macro == :has_many && reflection.options[:as]
                      " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [
                        table_name_and_alias,
                        aliased_table_name, "#{reflection.options[:as]}_id",
                        parent.aliased_table_name, parent.primary_key,
                        aliased_table_name, "#{reflection.options[:as]}_type",
                        klass.quote(parent.active_record.base_class.name)
                      ]
                    when reflection.macro == :has_one && reflection.options[:as]
                      " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
                        table_name_and_alias,
                        aliased_table_name, "#{reflection.options[:as]}_id",
                        parent.aliased_table_name, parent.primary_key,
                        aliased_table_name, "#{reflection.options[:as]}_type",
                        klass.quote(reflection.active_record.base_class.name)
                      ]
                    else
                      foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
                      " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                        table_name_and_alias,
                        aliased_table_name, foreign_key,
                        parent.aliased_table_name, parent.primary_key
                      ]
                  end
                when :belongs_to
                  " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                     table_name_and_alias, aliased_table_name, reflection.klass.primary_key,
                     parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key
                    ]
                else
                  ""
              end || ''
              join << %(AND %s.%s = %s ) % [
                aliased_table_name, 
                reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column), 
                klass.quote(klass.name)] unless klass.descends_from_active_record?
              join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions]
              join
            end
            
            protected
              def pluralize(table_name)
                ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
              end
              
              def table_alias_for(table_name, table_alias)
                "#{table_name} #{table_alias if table_name != table_alias}".strip
              end

              def table_name_and_alias
                table_alias_for table_name, @aliased_table_name
              end

              def interpolate_sql(sql)
                instance_eval("%@#{sql.gsub('@', '\@')}@")
              end
          end
        end
    end
  end
end