diff options
6 files changed, 84 insertions, 30 deletions
| diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 41f882743c..2a72fa95c9 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -2180,6 +2180,7 @@ module ActiveRecord                        # to represent the join table)                        table, join_table = table +                      # TODO: Can join_key just be reflection.primary_key_name ?                        join_key         = reflection.options[:foreign_key] ||                                           reflection.active_record.to_s.foreign_key                        join_foreign_key = reflection.active_record.primary_key @@ -2192,18 +2193,37 @@ module ActiveRecord                        # We've done the first join now, so update the foreign_table for the second                        foreign_table = join_table +                      # TODO: Can foreign_key be reflection.association_foreign_key?                        key         = reflection.klass.primary_key                        foreign_key = reflection.options[:association_foreign_key] ||                                      reflection.klass.to_s.foreign_key                    end -                elsif reflection.source_reflection.macro == :belongs_to -                  key         = reflection.klass.primary_key -                  foreign_key = reflection.source_reflection.primary_key_name -                   -                  conditions << source_type_conditions(reflection, foreign_table)                  else -                  key         = reflection.source_reflection.primary_key_name -                  foreign_key = reflection.source_reflection.klass.primary_key +                  case reflection.source_reflection.macro +                    when :belongs_to +                      key         = reflection.klass.primary_key +                      foreign_key = reflection.source_reflection.primary_key_name +                       +                      conditions << source_type_conditions(reflection, foreign_table) +                    when :has_many, :has_one +                      key         = reflection.source_reflection.primary_key_name +                      foreign_key = reflection.source_reflection.klass.primary_key +                    when :has_and_belongs_to_many +                      table, join_table = table +                       +                      join_key         = reflection.source_reflection.primary_key_name +                      join_foreign_key = reflection.source_reflection.klass.primary_key +                       +                      relation = relation.join(join_table, join_type).on( +                        join_table[join_key]. +                          eq(foreign_table[join_foreign_key]) +                      ) +                       +                      foreign_table = join_table +                       +                      key         = reflection.klass.primary_key +                      foreign_key = reflection.source_reflection.association_foreign_key +                  end                  end                  conditions << table[key].eq(foreign_table[foreign_key]) @@ -2269,14 +2289,19 @@ module ActiveRecord                  # For habtm, we have two Arel::Table instances related to a single reflection, so                  # we just store them as a pair in the array. -                if reflection.macro == :has_and_belongs_to_many +                if reflection.macro == :has_and_belongs_to_many || +                     (reflection.source_reflection && +                      reflection.source_reflection.macro == :has_and_belongs_to_many) +                   +                  join_table_name = (reflection.source_reflection || reflection).options[:join_table] +                                      aliased_join_table_name = alias_tracker.aliased_name_for( -                    reflection.options[:join_table], +                    join_table_name,                      table_alias_for(reflection, true)                    )                    join_table = Arel::Table.new( -                    reflection.options[:join_table], :engine => arel_engine, +                    join_table_name, :engine => arel_engine,                      :as => aliased_join_table_name                    ) diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb index d73f35c2db..6cc2fe2559 100644 --- a/activerecord/lib/active_record/associations/through_association_scope.rb +++ b/activerecord/lib/active_record/associations/through_association_scope.rb @@ -65,6 +65,7 @@ module ActiveRecord          # Iterate over each pair in the through reflection chain, joining them together          @reflection.through_reflection_chain.each_cons(2) do |left, right|            polymorphic_join  = nil +          left_table, right_table = table_aliases[left], table_aliases[right]            if left.source_reflection.nil?              # TODO: Perhaps need to pay attention to left.options[:primary_key] and @@ -114,20 +115,31 @@ module ActiveRecord                    ]                  end                when :has_and_belongs_to_many -                raise NotImplementedError +                join_table, left_table = left_table +                 +                left_primary_key = left.klass.primary_key +                join_primary_key = left.source_reflection.association_foreign_key +                 +                joins << "INNER JOIN %s ON %s.%s = %s.%s" % [ +                  table_name_and_alias( +                    quote_table_name(left.source_reflection.options[:join_table]), +                    join_table +                  ), +                  left_table, left_primary_key, +                  join_table, join_primary_key +                ] +                 +                left_table = join_table +                 +                left_primary_key  = left.source_reflection.primary_key_name +                right_primary_key = right.klass.primary_key              end            end -          if right.quoted_table_name == table_aliases[right] -            table = right.quoted_table_name -          else -            table = "#{right.quoted_table_name} #{table_aliases[right]}" -          end -                      joins << "INNER JOIN %s ON %s.%s = %s.%s %s" % [ -            table, -            table_aliases[left],  left_primary_key, -            table_aliases[right], right_primary_key, +            table_name_and_alias(right.quoted_table_name, right_table), +            left_table,  left_primary_key, +            right_table, right_primary_key,              polymorphic_join            ]          end @@ -147,13 +159,16 @@ module ActiveRecord                table_alias_for(reflection, reflection != @reflection)              )) -            if reflection.macro == :has_and_belongs_to_many +            if reflection.macro == :has_and_belongs_to_many || +                 (reflection.source_reflection && +                  reflection.source_reflection.macro == :has_and_belongs_to_many) +                              join_table_alias = quote_table_name(alias_tracker.aliased_name_for( -                reflection.options[:join_table], +                (reflection.source_reflection || reflection).options[:join_table],                  table_alias_for(reflection, true)                )) -              aliases[reflection] = [table_alias, join_table_alias] +              aliases[reflection] = [join_table_alias, table_alias]              else                aliases[reflection] = table_alias              end @@ -173,6 +188,10 @@ module ActiveRecord        def quote_table_name(table_name)          @reflection.klass.connection.quote_table_name(table_name)        end +       +      def table_name_and_alias(table_name, table_alias) +        "#{table_name} #{table_alias if table_alias != table_name}".strip +      end        # Construct attributes for associate pointing to owner.        def construct_owner_attributes(reflection) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index e67cbcc1a8..c6777d0cb3 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -719,7 +719,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase    def test_find_scoped_grouped      assert_equal 5, categories(:general).posts_grouped_by_title.size -    assert_equal 2, categories(:technology).posts_grouped_by_title.size +    assert_equal 1, categories(:technology).posts_grouped_by_title.size    end    def test_find_scoped_grouped_having diff --git a/activerecord/test/cases/associations/nested_has_many_through_associations_test.rb b/activerecord/test/cases/associations/nested_has_many_through_associations_test.rb index 835a573978..964112b006 100644 --- a/activerecord/test/cases/associations/nested_has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_has_many_through_associations_test.rb @@ -159,10 +159,15 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase    # has_many through    # Source: has_and_belongs_to_many    # Through: has_many -  # TODO: Enable and implement this, and finish off the test -  # def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection -  #   assert_equal [categories(:general), categories(:technology)], authors(:bob).post_categories -  # end +  def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection +    assert_equal [categories(:general), categories(:cooking)], authors(:bob).post_categories +     +    authors = Author.joins(:post_categories).where('categories.id' => categories(:cooking).id) +    assert_equal [authors(:bob)], authors +     +    authors = Author.includes(:post_categories) +    assert_equal [categories(:general), categories(:cooking)], authors[2].post_categories +  end    # TODO: has_many through    # Source: has_many diff --git a/activerecord/test/fixtures/categories.yml b/activerecord/test/fixtures/categories.yml index b0770a093d..3e75e733a6 100644 --- a/activerecord/test/fixtures/categories.yml +++ b/activerecord/test/fixtures/categories.yml @@ -12,3 +12,8 @@ sti_test:    id: 3    name: Special category    type: SpecialCategory + +cooking: +  id: 4 +  name: Cooking +  type: Category diff --git a/activerecord/test/fixtures/categories_posts.yml b/activerecord/test/fixtures/categories_posts.yml index 3b41510cb1..c6f0d885f5 100644 --- a/activerecord/test/fixtures/categories_posts.yml +++ b/activerecord/test/fixtures/categories_posts.yml @@ -26,6 +26,6 @@ general_misc_by_bob:    category_id: 1    post_id: 8 -technology_misc_by_bob: -  category_id: 2 +cooking_misc_by_bob: +  category_id: 4    post_id: 8 | 
