public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Fix has_many :through when the source is a belongs_to association. [#323 
state:resolved]

Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
zdennis (author)
Sat Oct 04 07:42:36 -0700 2008
lifo (committer)
Sat Oct 04 09:49:39 -0700 2008
commit  95e1cf4812d4b964d7ab0fdf4bfa31177d27909c
tree    82d7154cbf293128a06fafe040cf68f4d9d18f7f
parent  7659fb6a2b638703a99a63033d947d19089a6b85
...
4
5
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
8
9
...
77
78
79
80
 
81
82
83
84
85
 
 
 
86
87
88
...
129
130
131
132
133
134
 
 
 
 
135
136
137
138
139
140
141
142
143
 
144
145
146
 
 
147
148
 
149
150
151
...
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
...
99
100
101
 
102
103
104
105
 
 
106
107
108
109
110
111
...
152
153
154
 
 
155
156
157
158
159
160
161
162
163
 
164
165
 
 
166
167
 
 
168
169
170
171
172
173
174
175
0
@@ -4,6 +4,28 @@ module ActiveRecord
0
       base.extend(ClassMethods)
0
     end
0
 
0
+    class HasManyAssociationStrategy
0
+      def initialize(through_reflection)
0
+        @through_reflection = through_reflection
0
+      end
0
+
0
+      def primary_key
0
+        if @through_reflection && @through_reflection.macro == :belongs_to
0
+          @through_reflection.klass.primary_key
0
+        else
0
+          @through_reflection.primary_key_name
0
+        end
0
+      end
0
+
0
+      def primary_key_name
0
+        if @through_reflection && @through_reflection.macro == :belongs_to
0
+          @through_reflection.primary_key_name
0
+        else
0
+          nil
0
+        end
0
+      end
0
+    end
0
+
0
     module ClassMethods
0
 
0
       # Loads the named associations for the activerecord record (or records) given
0
@@ -77,12 +99,13 @@ module ActiveRecord
0
         end
0
       end
0
 
0
-      def construct_id_map(records)
0
+      def construct_id_map(records, primary_key=nil)
0
         id_to_record_map = {}
0
         ids = []
0
         records.each do |record|
0
-          ids << record.id
0
-          mapped_records = (id_to_record_map[record.id.to_s] ||= [])
0
+          primary_key ||= record.class.primary_key
0
+          ids << record[primary_key]
0
+          mapped_records = (id_to_record_map[ids.last.to_s] ||= [])
0
           mapped_records << record
0
         end
0
         ids.uniq!
0
@@ -129,23 +152,24 @@ module ActiveRecord
0
       end
0
 
0
       def preload_has_many_association(records, reflection, preload_options={})
0
-        id_to_record_map, ids = construct_id_map(records)
0
-        records.each {|record| record.send(reflection.name).loaded}
0
         options = reflection.options
0
+        through_reflection = reflections[options[:through]]
0
+        strat = HasManyAssociationStrategy.new(through_reflection)
0
+        id_to_record_map, ids = construct_id_map(records, strat.primary_key_name)
0
+        records.each {|record| record.send(reflection.name).loaded}
0
 
0
         if options[:through]
0
           through_records = preload_through_records(records, reflection, options[:through])
0
           through_reflection = reflections[options[:through]]
0
-          through_primary_key = through_reflection.primary_key_name
0
           unless through_records.empty?
0
             source = reflection.source_reflection.name
0
-            #add conditions from reflection!
0
-            through_records.first.class.preload_associations(through_records, source, reflection.options)
0
+            through_records.first.class.preload_associations(through_records, source, options)
0
             through_records.each do |through_record|
0
-              add_preloaded_records_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
0
-                                                 reflection.name, through_record.send(source))
0
+              through_record_id = through_record[strat.primary_key].to_s
0
+              add_preloaded_records_to_collection(id_to_record_map[through_record_id], reflection.name, through_record.send(source))
0
             end
0
           end
0
+
0
         else
0
           set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
0
                                              reflection.primary_key_name)
...
32
33
34
 
 
 
 
 
 
 
 
35
36
37
...
61
62
63
 
64
65
66
...
102
103
104
 
 
105
106
107
...
32
33
34
35
36
37
38
39
40
41
42
43
44
45
...
69
70
71
72
73
74
75
...
111
112
113
114
115
116
117
118
0
@@ -32,6 +32,14 @@ module ActiveRecord
0
       end
0
       
0
       protected
0
+        def target_reflection_has_associated_record?
0
+          if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
0
+            false
0
+          else
0
+            true
0
+          end
0
+        end
0
+
0
         def construct_find_options!(options)
0
           options[:select]  = construct_select(options[:select])
0
           options[:from]  ||= construct_from
0
@@ -61,6 +69,7 @@ module ActiveRecord
0
         end
0
 
0
         def find_target
0
+          return [] unless target_reflection_has_associated_record?
0
           @reflection.klass.find(:all,
0
             :select     => construct_select,
0
             :conditions => construct_conditions,
0
@@ -102,6 +111,8 @@ module ActiveRecord
0
               "#{as}_type" => reflection.klass.quote_value(
0
                 @owner.class.base_class.name.to_s,
0
                 reflection.klass.columns_hash["#{as}_type"]) }
0
+          elsif reflection.macro == :belongs_to
0
+            { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
0
           else
0
             { reflection.primary_key_name => owner_quoted_id }
0
           end
...
275
276
277
 
 
 
 
 
 
 
 
 
278
279
280
...
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
0
@@ -275,6 +275,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
0
     assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
0
   end
0
 
0
+  def test_eager_with_has_many_through_a_belongs_to_association
0
+    author = authors(:mary)
0
+    post = Post.create!(:author => author, :title => "TITLE", :body => "BODY")
0
+    author.author_favorites.create(:favorite_author_id => 1)
0
+    author.author_favorites.create(:favorite_author_id => 2)
0
+    posts_with_author_favorites = author.posts.find(:all, :include => :author_favorites)
0
+    assert_no_queries { posts_with_author_favorites.first.author_favorites.first.author_id }
0
+  end
0
+
0
   def test_eager_with_has_many_through_an_sti_join_model
0
     author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id')
0
     assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
...
1081
1082
1083
 
...
1081
1082
1083
1084
0
@@ -1081,3 +1081,4 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
0
   end
0
 
0
 end
0
+
...
5
6
7
8
 
9
10
11
...
229
230
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
...
5
6
7
 
8
9
10
11
...
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
0
@@ -5,7 +5,7 @@ require 'models/reader'
0
 require 'models/comment'
0
 
0
 class HasManyThroughAssociationsTest < ActiveRecord::TestCase
0
-  fixtures :posts, :readers, :people, :comments
0
+  fixtures :posts, :readers, :people, :comments, :authors
0
 
0
   def test_associate_existing
0
     assert_queries(2) { posts(:thinking);people(:david) }
0
@@ -229,4 +229,19 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
0
       end
0
     end
0
   end
0
+
0
+  def test_has_many_association_through_a_belongs_to_association_where_the_association_doesnt_exist
0
+    author = authors(:mary)
0
+    post = Post.create!(:title => "TITLE", :body => "BODY")
0
+    assert_equal [], post.author_favorites
0
+  end
0
+
0
+  def test_has_many_association_through_a_belongs_to_association
0
+    author = authors(:mary)
0
+    post = Post.create!(:author => author, :title => "TITLE", :body => "BODY")
0
+    author.author_favorites.create(:favorite_author_id => 1)
0
+    author.author_favorites.create(:favorite_author_id => 2)
0
+    author.author_favorites.create(:favorite_author_id => 3)
0
+    assert_equal post.author.author_favorites, post.author_favorites
0
+  end
0
 end
...
22
23
24
 
 
25
26
27
...
22
23
24
25
26
27
28
29
0
@@ -22,6 +22,8 @@ class Post < ActiveRecord::Base
0
     end
0
   end
0
 
0
+  has_many :author_favorites, :through => :author
0
+
0
   has_many :comments_with_interpolated_conditions, :class_name => 'Comment',
0
       :conditions => ['#{"#{aliased_table_name}." rescue ""}body = ?', 'Thank you for the welcome']
0
 

Comments