From 1777600e6e11e553ad97b7bc89e4b19e992eb3d3 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 12 Oct 2010 16:40:24 +0100 Subject: [PATCH] Support has_one through assocs as the source association --- .../associations/through_association_scope.rb | 31 ++++++++++------ ...sted_has_many_through_associations_test.rb | 35 +++++++++++++++++-- activerecord/test/fixtures/member_details.yml | 3 ++ activerecord/test/fixtures/members.yml | 2 ++ activerecord/test/fixtures/memberships.yml | 6 ++-- activerecord/test/fixtures/sponsors.yml | 9 +++-- activerecord/test/models/member.rb | 5 ++- 7 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 activerecord/test/fixtures/member_details.yml diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb index 8406f5fd20..81e29f047b 100644 --- a/activerecord/lib/active_record/associations/through_association_scope.rb +++ b/activerecord/lib/active_record/associations/through_association_scope.rb @@ -53,7 +53,7 @@ module ActiveRecord end def construct_joins(custom_joins = nil) - # puts @reflection.through_reflection_chain.map(&:inspect) + # p @reflection.through_reflection_chain "#{construct_through_joins} #{@reflection.options[:joins]} #{custom_joins}" end @@ -67,16 +67,27 @@ module ActiveRecord case when left.source_reflection.nil? - left_primary_key = left.primary_key_name - right_primary_key = right.klass.primary_key + # TODO: Perhaps need to pay attention to left.options[:primary_key] and + # left.options[:foreign_key] in places here - if left.options[:as] - polymorphic_join = "AND %s.%s = %s" % [ - table_aliases[left], "#{left.options[:as]}_type", - # TODO: Why right.klass.name? Rather than left.active_record.name? - # TODO: Also should maybe use the base_class (see related code in JoinAssociation) - @owner.class.quote_value(right.klass.name) - ] + case left.macro + when :belongs_to + left_primary_key = left.klass.primary_key + right_primary_key = right.primary_key_name + when :has_many, :has_one + left_primary_key = left.primary_key_name + right_primary_key = right.klass.primary_key + + if left.options[:as] + polymorphic_join = "AND %s.%s = %s" % [ + table_aliases[left], "#{left.options[:as]}_type", + # TODO: Why right.klass.name? Rather than left.active_record.name? + # TODO: Also should maybe use the base_class (see related code in JoinAssociation) + @owner.class.quote_value(right.klass.name) + ] + end + when :has_and_belongs_to_many + raise NotImplementedError end when left.source_reflection.macro == :belongs_to left_primary_key = left.klass.primary_key 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 8a4ab627bb..4fab426696 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 @@ -18,6 +18,9 @@ require 'models/subscriber' require 'models/book' require 'models/subscription' require 'models/rating' +require 'models/member' +require 'models/member_detail' +require 'models/member_type' # NOTE: Some of these tests might not really test "nested" HMT associations, as opposed to ones which # are just one level deep. But it's all the same thing really, as the "nested" code is being @@ -26,7 +29,8 @@ require 'models/rating' class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase fixtures :authors, :books, :posts, :subscriptions, :subscribers, :tags, :taggings, - :people, :readers, :references, :jobs, :ratings, :comments + :people, :readers, :references, :jobs, :ratings, :comments, :members, :member_details, + :member_types # Through associations can either use the has_many or has_one macros. # @@ -56,6 +60,9 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase authors = Author.joins(:tags).where('tags.id' => tags(:general).id) assert_equal [authors(:david)], authors.uniq + authors = Author.includes(:tags) + assert_equal [tags(:general), tags(:general)], authors.first.tags + # This ensures that the polymorphism of taggings is being observed correctly authors = Author.joins(:tags).where('taggings.taggable_type' => 'FakeModel') assert authors.empty? @@ -71,11 +78,24 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase # All authors with subscribers where one of the subscribers' nick is 'alterself' authors = Author.joins(:subscribers).where('subscribers.nick' => 'alterself') assert_equal [authors(:david)], authors + + # TODO: Make this work + # authors = Author.includes(:subscribers) + # assert_equal [subscribers(:first), subscribers(:second), subscribers(:second)], authors.first.subscribers end - # TODO: has_many through + # has_many through # Source: has_one through # Through: has_one + def test_has_many_through_has_one_with_has_one_through_source_reflection + assert_equal [member_types(:founding)], members(:groucho).nested_member_types + + members = Member.joins(:nested_member_types).where('member_types.id' => member_types(:founding).id) + assert_equal [members(:groucho)], members + + members = Member.includes(:nested_member_types) + assert_equal [member_types(:founding)], members.first.nested_member_types + end # TODO: has_many through # Source: has_one @@ -105,9 +125,18 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase # Source: has_many through # Through: belongs_to - # TODO: has_one through + # has_one through # Source: has_one through # Through: has_one + def test_has_one_through_has_one_with_has_one_through_source_reflection + assert_equal member_types(:founding), members(:groucho).nested_member_type + + members = Member.joins(:nested_member_type).where('member_types.id' => member_types(:founding).id) + assert_equal [members(:groucho)], members + + members = Member.includes(:nested_member_type) + assert_equal member_types(:founding), members.first.nested_member_type + end # TODO: has_one through # Source: belongs_to diff --git a/activerecord/test/fixtures/member_details.yml b/activerecord/test/fixtures/member_details.yml new file mode 100644 index 0000000000..020932246a --- /dev/null +++ b/activerecord/test/fixtures/member_details.yml @@ -0,0 +1,3 @@ +groucho: + id: 1 + member_id: 1 diff --git a/activerecord/test/fixtures/members.yml b/activerecord/test/fixtures/members.yml index 6db945e61d..824840b7e5 100644 --- a/activerecord/test/fixtures/members.yml +++ b/activerecord/test/fixtures/members.yml @@ -1,6 +1,8 @@ groucho: + id: 1 name: Groucho Marx member_type_id: 1 some_other_guy: + id: 2 name: Englebert Humperdink member_type_id: 2 diff --git a/activerecord/test/fixtures/memberships.yml b/activerecord/test/fixtures/memberships.yml index b9722dbc8a..eed8b22af8 100644 --- a/activerecord/test/fixtures/memberships.yml +++ b/activerecord/test/fixtures/memberships.yml @@ -1,20 +1,20 @@ membership_of_boring_club: joined_on: <%= 3.weeks.ago.to_s(:db) %> club: boring_club - member: groucho + member_id: 1 favourite: false type: CurrentMembership membership_of_favourite_club: joined_on: <%= 3.weeks.ago.to_s(:db) %> club: moustache_club - member: groucho + member_id: 1 favourite: true type: Membership other_guys_membership: joined_on: <%= 4.weeks.ago.to_s(:db) %> club: boring_club - member: some_other_guy + member_id: 2 favourite: false type: CurrentMembership diff --git a/activerecord/test/fixtures/sponsors.yml b/activerecord/test/fixtures/sponsors.yml index 42df8957d1..bfc6b238b1 100644 --- a/activerecord/test/fixtures/sponsors.yml +++ b/activerecord/test/fixtures/sponsors.yml @@ -1,9 +1,12 @@ moustache_club_sponsor_for_groucho: sponsor_club: moustache_club - sponsorable: groucho (Member) + sponsorable_id: 1 + sponsorable_type: Member boring_club_sponsor_for_groucho: sponsor_club: boring_club - sponsorable: some_other_guy (Member) + sponsorable_id: 2 + sponsorable_type: Member crazy_club_sponsor_for_groucho: sponsor_club: crazy_club - sponsorable: some_other_guy (Member) \ No newline at end of file + sponsorable_id: 2 + sponsorable_type: Member diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index 255fb569d7..b8b22d0fde 100644 --- a/activerecord/test/models/member.rb +++ b/activerecord/test/models/member.rb @@ -9,4 +9,7 @@ class Member < ActiveRecord::Base has_one :member_detail has_one :organization, :through => :member_detail belongs_to :member_type -end \ No newline at end of file + + has_many :nested_member_types, :through => :member_detail, :source => :member_type + has_one :nested_member_type, :through => :member_detail, :source => :member_type +end