From 0bb85ed9ffa9808926b46e8f7e59cab5b85ac19f Mon Sep 17 00:00:00 2001
From: Ernie Miller <ernie@metautonomo.us>
Date: Wed, 20 Oct 2010 22:54:43 -0400
Subject: Fix issues when including the same association multiple times and
 mixing joins/includes together.

---
 activerecord/lib/active_record/associations.rb | 48 ++++++++++++++++++++------
 1 file changed, 37 insertions(+), 11 deletions(-)

(limited to 'activerecord/lib/active_record')

diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 59d328f207..b8cd2aa31e 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1837,7 +1837,7 @@ module ActiveRecord
 
           def initialize(base, associations, joins)
             @join_parts            = [JoinBase.new(base, joins)]
-            @associations          = associations
+            @associations          = {}
             @reflections           = []
             @base_records_hash     = {}
             @base_records_in_order = []
@@ -1917,30 +1917,57 @@ module ActiveRecord
 
           protected
 
+            def cache_joined_association(association)
+              associations = []
+              parent = association.parent
+              while parent != join_base
+                associations.unshift(parent.reflection.name)
+                parent = parent.parent
+              end
+              ref = @associations
+              associations.each do |key|
+                ref = ref[key]
+              end
+              ref[association.reflection.name] ||= {}
+            end
+
             def build(associations, parent = nil, join_type = Arel::InnerJoin)
               parent ||= join_parts.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
-                  join_association = build_join_association(reflection, parent)
-                  join_association.join_type = join_type
-                  @join_parts << join_association
+                  unless join_association = find_join_association(reflection, parent)
+                    @reflections << reflection
+                    join_association = build_join_association(reflection, parent)
+                    join_association.join_type = join_type
+                    @join_parts << join_association
+                    cache_joined_association(join_association)
+                  end
+                  join_association
                 when Array
                   associations.each do |association|
                     build(association, parent, join_type)
                   end
                 when Hash
                   associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
-                    build(name, parent, join_type)
-                    build(associations[name], nil, join_type)
+                    join_association = build(name, parent, join_type)
+                    build(associations[name], join_association, join_type)
                   end
                 else
                   raise ConfigurationError, associations.inspect
               end
             end
 
+            def find_join_association(name_or_reflection, parent)
+              case name_or_reflection
+              when Symbol, String
+                join_associations.detect {|j| (j.reflection.name == name_or_reflection.to_s.intern) && (j.parent == parent)}
+              else
+                join_associations.detect {|j| (j.reflection == name_or_reflection) && (j.parent == parent)}
+              end
+            end
+
             def remove_uniq_by_reflection(reflection, records)
               if reflection && reflection.collection?
                 records.each { |record| record.send(reflection.name).target.uniq! }
@@ -2013,7 +2040,7 @@ module ActiveRecord
             end
 
           # A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited
-          # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which 
+          # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
           # everything else is being joined onto. A JoinAssociation represents an association which
           # is joining to the base. A JoinAssociation may result in more than one actual join
           # operations (for example a has_and_belongs_to_many JoinAssociation would result in
@@ -2092,8 +2119,7 @@ module ActiveRecord
 
             def ==(other)
               other.class == self.class &&
-              other.active_record == active_record &&
-              other.table_joins == table_joins
+              other.active_record == active_record
             end
 
             def aliased_prefix
@@ -2114,7 +2140,7 @@ module ActiveRecord
             attr_reader :reflection
 
             # The JoinDependency object which this JoinAssociation exists within. This is mainly
-            # relevant for generating aliases which do not conflict with other joins which are 
+            # relevant for generating aliases which do not conflict with other joins which are
             # part of the query.
             attr_reader :join_dependency
 
-- 
cgit v1.2.3