<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,5 +1,7 @@
 *1.15.3* (March 12th, 2007)
 
+* Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool] 
+
 * Consistently quote primary key column names.  #7763 [toolmantim]
 
 * Fixtures: fix YAML ordered map support.  #2665 [Manuel Holtgrewe, nfbuckley]</diff>
      <filename>activerecord/CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -20,7 +20,13 @@ module ActiveRecord
       super(&quot;Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.&quot;)
     end
   end
-
+  
+  class HasManyThroughAssociationPointlessSourceTypeError &lt; ActiveRecordError #:nodoc:
+    def initialize(owner_class_name, reflection, source_reflection)
+      super(&quot;Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic.  Try removing :source_type on your association.&quot;)
+    end
+  end
+  
   class HasManyThroughSourceAssociationNotFoundError &lt; ActiveRecordError #:nodoc:
     def initialize(reflection)
       through_reflection      = reflection.through_reflection
@@ -529,6 +535,8 @@ module ActiveRecord
       # * &lt;tt&gt;:source&lt;/tt&gt;: Specifies the source association name used by &lt;tt&gt;has_many :through&lt;/tt&gt; queries.  Only use it if the name cannot be 
       #   inferred from the association.  &lt;tt&gt;has_many :subscribers, :through =&gt; :subscriptions&lt;/tt&gt; will look for either +:subscribers+ or
       #   +:subscriber+ on +Subscription+, unless a +:source+ is given.
+      # * &lt;tt&gt;:source_type&lt;/tt&gt;: Specifies type of the source association used by &lt;tt&gt;has_many :through&lt;/tt&gt; queries where the source association
+      #   is a polymorphic belongs_to.
       # * &lt;tt&gt;:uniq&lt;/tt&gt; - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.
       #
       # Option examples:
@@ -1087,7 +1095,7 @@ module ActiveRecord
             :class_name, :table_name, :foreign_key,
             :exclusively_dependent, :dependent,
             :select, :conditions, :include, :order, :group, :limit, :offset,
-            :as, :through, :source,
+            :as, :through, :source, :source_type,
             :uniq,
             :finder_sql, :counter_sql, 
             :before_add, :after_add, :before_remove, :after_remove, 
@@ -1491,57 +1499,65 @@ module ActiveRecord
                   case
                     when reflection.macro == :has_many &amp;&amp; reflection.options[:through]
                       through_conditions = through_reflection.options[:conditions] ? &quot;AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}&quot; : ''
+
+                      jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
+                      first_key = second_key = as_extra = nil
+                      
                       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'
-
-                        &quot; LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) &quot;  % [
-                          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_value(parent.active_record.base_class.name)] +
-                        &quot; LEFT OUTER JOIN %s ON %s.%s = %s.%s &quot; % [table_name_and_alias,
-                          aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
+                        jt_foreign_key = through_reflection.options[:as].to_s + '_id'
+                        jt_as_extra = &quot; AND %s.%s = %s&quot; % [
+                            aliased_join_table_name, reflection.active_record.connection.quote_column_name(through_reflection.options[:as].to_s + '_type'), 
+                            klass.quote_value(parent.active_record.base_class.name)
                         ]
                       else
-                        if source_reflection.macro == :has_many &amp;&amp; source_reflection.options[:as]
-                          &quot; LEFT OUTER JOIN %s ON %s.%s = %s.%s &quot;  % [
-                            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] +
-                          &quot; LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s &quot; % [
-                            table_name_and_alias,
-                            aliased_table_name, &quot;#{source_reflection.options[:as]}_id&quot;, 
-                            aliased_join_table_name, options[:foreign_key] || primary_key,
-                            aliased_table_name, &quot;#{source_reflection.options[:as]}_type&quot;, 
+                        jt_foreign_key = through_reflection.primary_key_name
+                      end
+                      
+                      case source_reflection.macro
+                      when :has_many
+                        if source_reflection.options[:as]
+                          first_key   = &quot;#{source_reflection.options[:as]}_id&quot;
+                          second_key  = options[:foreign_key] || primary_key
+                          as_extra    = &quot; AND %s.%s = %s&quot; % [
+                            aliased_table_name, reflection.active_record.connection.quote_column_name(&quot;#{source_reflection.options[:as]}_type&quot;), 
                             klass.quote_value(source_reflection.active_record.base_class.name)
                           ]
                         else
-                          case source_reflection.macro
-                            when :belongs_to
-                              first_key  = primary_key
-                              second_key = source_reflection.options[:foreign_key] || klass.to_s.classify.foreign_key
-                              extra      = nil
-                            when :has_many
-                              first_key  = through_reflection.klass.base_class.to_s.classify.foreign_key
-                              second_key = options[:foreign_key] || primary_key
-                              extra      = through_reflection.klass.descends_from_active_record? ? nil :
-                                &quot; AND %s.%s = %s&quot; % [
-                                  aliased_join_table_name,
-                                  reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
-                                  through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
-                          end
-                          &quot; LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) &quot;  % [
-                            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, extra] +
-                          &quot; LEFT OUTER JOIN %s ON (%s.%s = %s.%s) &quot; % [
-                            table_name_and_alias,
-                            aliased_table_name, first_key, 
-                            aliased_join_table_name, second_key
+                          first_key   = through_reflection.klass.base_class.to_s.classify.foreign_key
+                          second_key  = options[:foreign_key] || primary_key
+                        end
+                        
+                        unless through_reflection.klass.descends_from_active_record?
+                          jt_sti_extra = &quot; AND %s.%s = %s&quot; % [
+                            aliased_join_table_name,
+                            reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
+                            through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
+                        end
+                      when :belongs_to
+                        first_key = primary_key
+                        if reflection.options[:source_type]
+                          second_key = source_reflection.association_foreign_key
+                          jt_source_extra = &quot; AND %s.%s = %s&quot; % [
+                              aliased_join_table_name, reflection.active_record.connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
+                              klass.quote_value(reflection.options[:source_type])
                           ]
+                        else
+                          second_key = source_reflection.options[:foreign_key] || klass.to_s.classify.foreign_key
                         end
                       end
+                      
+                      &quot; LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s%s%s) &quot; % [
+                        table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
+                        parent.aliased_table_name, reflection.active_record.connection.quote_column_name(parent.primary_key),
+                        aliased_join_table_name, reflection.active_record.connection.quote_column_name(jt_foreign_key), 
+                        jt_as_extra, jt_source_extra, jt_sti_extra
+                      ] +
+                      &quot; LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) &quot; % [
+                        table_name_and_alias, 
+                        aliased_table_name, reflection.active_record.connection.quote_column_name(first_key),
+                        aliased_join_table_name, reflection.active_record.connection.quote_column_name(second_key),
+                        as_extra
+                      ]
                     
                     when reflection.macro == :has_many &amp;&amp; reflection.options[:as]
                       &quot; LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s&quot; % [
@@ -1588,6 +1604,7 @@ module ActiveRecord
             end
             
             protected
+
               def pluralize(table_name)
                 ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
               end</diff>
      <filename>activerecord/lib/active_record/associations.rb</filename>
    </modified>
    <modified>
      <diff>@@ -138,7 +138,11 @@ module ActiveRecord
 
         # Construct attributes for :through pointing to owner and associate.
         def construct_join_attributes(associate)
-          construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name =&gt; associate.id)
+          returning construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name =&gt; associate.id) do |join_attributes|
+            if @reflection.options[:source_type]
+              join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] =&gt; associate.class.base_class.name.to_s)
+            end
+          end
         end
 
         # Associate attributes pointing to owner, quoted.
@@ -176,6 +180,12 @@ module ActiveRecord
           if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
             reflection_primary_key = @reflection.klass.primary_key
             source_primary_key     = @reflection.source_reflection.primary_key_name
+            if @reflection.options[:source_type]
+              polymorphic_join = &quot;AND %s.%s = %s&quot; % [
+                @reflection.through_reflection.table_name, &quot;#{@reflection.source_reflection.options[:foreign_type]}&quot;,
+                @owner.class.quote_value(@reflection.options[:source_type])
+              ]
+            end
           else
             reflection_primary_key = @reflection.source_reflection.primary_key_name
             source_primary_key     = @reflection.klass.primary_key</diff>
      <filename>activerecord/lib/active_record/associations/has_many_through_association.rb</filename>
    </modified>
    <modified>
      <diff>@@ -186,8 +186,12 @@ module ActiveRecord
           if source_reflection.nil?
             raise HasManyThroughSourceAssociationNotFoundError.new(self)
           end
+
+          if options[:source_type] &amp;&amp; source_reflection.options[:polymorphic].nil?
+            raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
+          end
           
-          if source_reflection.options[:polymorphic]
+          if source_reflection.options[:polymorphic] &amp;&amp; options[:source_type].nil?
             raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
           end
           
@@ -205,7 +209,7 @@ module ActiveRecord
             if options[:class_name]
               options[:class_name]
             elsif through_reflection # get the class_name of the belongs_to association of the through reflection
-              source_reflection.class_name
+              options[:source_type] || source_reflection.class_name
             else
               class_name = name.to_s.camelize
               class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)</diff>
      <filename>activerecord/lib/active_record/reflection.rb</filename>
    </modified>
    <modified>
      <diff>@@ -300,6 +300,18 @@ class AssociationsJoinModelTest &lt; Test::Unit::TestCase
       assert_equal [posts(:welcome), posts(:thinking)], tags(:general).taggings.find(:all, :include =&gt; :taggable)
     end
   end
+  
+  def test_has_many_polymorphic_with_source_type
+    assert_equal [posts(:welcome), posts(:thinking)], tags(:general).tagged_posts
+  end
+
+  def test_eager_has_many_polymorphic_with_source_type
+    tag_with_include = Tag.find(tags(:general).id, :include =&gt; :tagged_posts)
+    desired = [posts(:welcome), posts(:thinking)]
+    assert_no_queries do
+      assert_equal desired, tag_with_include.tagged_posts
+    end
+  end
 
   def test_has_many_through_has_many_find_all
     assert_equal comments(:greetings), authors(:david).comments.find(:all, :order =&gt; 'comments.id').first</diff>
      <filename>activerecord/test/associations/join_model_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -2,4 +2,6 @@ class Tag &lt; ActiveRecord::Base
   has_many :taggings
   has_many :taggables, :through =&gt; :taggings
   has_one  :tagging
+
+  has_many :tagged_posts, :through =&gt; :taggings, :source =&gt; :taggable, :source_type =&gt; 'Post'
 end
\ No newline at end of file</diff>
      <filename>activerecord/test/fixtures/tag.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>736cca87dc092ee850296f191bab990cb2fc80e9</id>
    </parent>
  </parents>
  <author>
    <name>Rick Olson</name>
    <email>technoweenie@gmail.com</email>
  </author>
  <url>http://github.com/rubyruy/rails/commit/30549718ac827f3ae8c7afc8dc9a6bf639e4e65c</url>
  <id>30549718ac827f3ae8c7afc8dc9a6bf639e4e65c</id>
  <committed-date>2007-03-12T22:32:55-07:00</committed-date>
  <authored-date>2007-03-12T22:32:55-07:00</authored-date>
  <message>apply [6408] to stable

git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-stable@6410 5ecf4fe2-1ee6-0310-87b1-e25e094e27de</message>
  <tree>ac427c1deb9993a10f6cf26e0da4e6cb0da309d3</tree>
  <committer>
    <name>Rick Olson</name>
    <email>technoweenie@gmail.com</email>
  </committer>
</commit>
