Ticket #9640: preload_ar_polymorph.diff
| File preload_ar_polymorph.diff, 35.7 kB (added by fcheung, 7 months ago) |
|---|
-
activerecord/test/associations/preload_test.rb
old new 1 require 'abstract_unit' 2 require 'fixtures/post' 3 require 'fixtures/tag' 4 require 'fixtures/comment' 5 require 'fixtures/author' 6 require 'fixtures/category' 7 require 'fixtures/categorization' 8 require 'fixtures/tagging' 9 require 'fixtures/company' 10 require 'fixtures/person' 11 require 'fixtures/reader' 12 13 class PreloadAssociationTest < Test::Unit::TestCase 14 fixtures :posts, :comments, :authors, :categories, :categories_posts, 15 :companies, :accounts, :tags, :people, :readers, :taggings, :categorizations, :items 16 17 def test_loading_with_one_association 18 posts = Post.find(:all, :preload => :comments) 19 post = posts.find { |p| p.id == 1 } 20 assert_no_queries do 21 assert_equal 2, post.comments.size 22 end 23 assert post.comments.include?(comments(:greetings)) 24 25 post = Post.find(:first, :preload => :comments, :conditions => "posts.title = 'Welcome to the weblog'") 26 assert_equal 2, post.comments.size 27 assert post.comments.include?(comments(:greetings)) 28 end 29 30 def test_with_ordering 31 list = Post.find(:all, :preload => :comments, :order => "posts.id DESC") 32 [:eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments, 33 :authorless, :thinking, :welcome 34 ].each_with_index do |post, index| 35 assert_equal posts(post), list[index] 36 end 37 end 38 39 def test_loading_with_multiple_associations 40 posts = Post.find(:all, :preload => [ :comments, :author, :categories ], :order => "posts.id") 41 assert_equal 2, posts.first.comments.size 42 assert_equal 2, posts.first.categories.size 43 assert posts.first.comments.include?(comments(:greetings)) 44 end 45 46 def test_loading_from_an_association 47 posts = authors(:david).posts.find(:all, :preload => :comments, :order => "posts.id") 48 assert_equal 2, posts.first.comments.size 49 end 50 51 def test_loading_with_no_associations 52 assert_nil Post.find(posts(:authorless).id, :preload => :author).author 53 end 54 55 def test_eager_association_loading_with_belongs_to 56 comments = Comment.find(:all, :preload => :post) 57 assert_equal 10, comments.length 58 titles = comments.map { |c| c.post.title } 59 assert titles.include?(posts(:welcome).title) 60 assert titles.include?(posts(:sti_post_and_comments).title) 61 end 62 63 def test_eager_association_loading_with_belongs_to_and_limit 64 comments = Comment.find(:all, :preload => :post, :limit => 5, :order => 'comments.id') 65 assert_equal 5, comments.length 66 assert_equal [1,2,3,5,6], comments.collect { |c| c.id } 67 end 68 69 def test_eager_association_loading_with_belongs_to_and_limit_and_offset 70 comments = Comment.find(:all, :preload => :post, :limit => 3, :offset => 2, :order => 'comments.id') 71 assert_equal 3, comments.length 72 assert_equal [3,5,6], comments.collect { |c| c.id } 73 end 74 75 76 def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations 77 posts = Post.find(:all, :preload => [:author, :very_special_comment], :limit => 1, :order => 'posts.id') 78 assert_equal 1, posts.length 79 assert_equal [1], posts.collect { |p| p.id } 80 end 81 82 def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations 83 posts = Post.find(:all, :preload => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id') 84 assert_equal 1, posts.length 85 assert_equal [2], posts.collect { |p| p.id } 86 end 87 88 def test_eager_association_loading_with_explicit_join 89 posts = Post.find(:all, :preload => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id') 90 assert_equal 1, posts.length 91 end 92 93 def test_eager_with_has_many_through 94 posts_with_comments = people(:michael).posts.find(:all, :preload => :comments) 95 posts_with_author = people(:michael).posts.find(:all, :preload => :author ) 96 posts_with_comments_and_author = people(:michael).posts.find(:all, :preload => [ :comments, :author ]) 97 assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size } 98 assert_equal authors(:david), assert_no_queries { posts_with_author.first.author } 99 assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author } 100 end 101 102 def test_eager_with_has_many_through_an_sti_join_model 103 author = Author.find(:first, :preload => :special_post_comments, :order => 'authors.id') 104 assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments } 105 end 106 107 def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both 108 author = Author.find(:first, :preload => :special_nonexistant_post_comments, :order => 'authors.id') 109 assert_equal [], author.special_nonexistant_post_comments 110 end 111 112 def test_eager_with_has_many_through_join_model_with_conditions 113 assert_equal Author.find(:first, :preload => :hello_post_comments, 114 :order => 'authors.id').hello_post_comments.sort_by(&:id), 115 Author.find(:first, :order => 'authors.id').hello_post_comments.sort_by(&:id) 116 end 117 118 def test_eager_with_has_many_and_limit 119 posts = Post.find(:all, :order => 'posts.id asc', :preload => [ :author, :comments ], :limit => 2) 120 assert_equal 2, posts.size 121 assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size } 122 end 123 124 def test_eager_with_has_many_and_limit_and_conditions_array 125 if current_adapter?(:OpenBaseAdapter) 126 posts = Post.find(:all, :preload => [ :author, :comments ], :limit => 2, :conditions => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id") 127 else 128 posts = Post.find(:all, :preload => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id") 129 end 130 assert_equal 2, posts.size 131 assert_equal [4,5], posts.collect { |p| p.id } 132 end 133 134 def test_eager_with_has_many_and_limit_with_no_results 135 posts = Post.find(:all, :preload => [ :author, :comments ], :limit => 2, :conditions => "posts.title = 'magic forest'") 136 assert_equal 0, posts.size 137 end 138 139 def test_eager_with_has_and_belongs_to_many_and_limit 140 posts = Post.find(:all, :preload => :categories, :order => "posts.id", :limit => 3) 141 assert_equal 3, posts.size 142 assert_equal 2, posts[0].categories.size 143 assert_equal 1, posts[1].categories.size 144 assert_equal 0, posts[2].categories.size 145 assert posts[0].categories.include?(categories(:technology)) 146 assert posts[1].categories.include?(categories(:general)) 147 end 148 149 def test_eager_association_loading_with_habtm 150 posts = Post.find(:all, :preload => :categories, :order => "posts.id") 151 assert_equal 2, posts[0].categories.size 152 assert_equal 1, posts[1].categories.size 153 assert_equal 0, posts[2].categories.size 154 assert posts[0].categories.include?(categories(:technology)) 155 assert posts[1].categories.include?(categories(:general)) 156 end 157 158 def test_eager_with_inheritance 159 posts = SpecialPost.find(:all, :preload => [ :comments ]) 160 end 161 162 def test_eager_has_one_with_association_inheritance 163 post = Post.find(4, :preload => [ :very_special_comment ]) 164 assert_equal "VerySpecialComment", post.very_special_comment.class.to_s 165 end 166 167 def test_eager_has_many_with_association_inheritance 168 post = Post.find(4, :preload => [ :special_comments ]) 169 post.special_comments.each do |special_comment| 170 assert_equal "SpecialComment", special_comment.class.to_s 171 end 172 end 173 174 def test_eager_habtm_with_association_inheritance 175 post = Post.find(6, :preload => [ :special_categories ]) 176 assert_equal 1, post.special_categories.size 177 post.special_categories.each do |special_category| 178 assert_equal "SpecialCategory", special_category.class.to_s 179 end 180 end 181 182 def test_eager_with_has_one_dependent_does_not_destroy_dependent 183 assert_not_nil companies(:first_firm).account 184 f = Firm.find(:first, :preload => :account, 185 :conditions => ["companies.name = ?", "37signals"]) 186 assert_not_nil f.account 187 assert_equal companies(:first_firm, :reload).account, f.account 188 end 189 190 def test_eager_with_multi_table_conditional_properly_counts_the_records_when_using_size 191 author = authors(:david) 192 posts_with_no_comments = author.posts.select { |post| post.comments.blank? } 193 assert_equal posts_with_no_comments.size, author.posts_with_no_comments.size 194 assert_equal posts_with_no_comments, author.posts_with_no_comments 195 end 196 197 def test_eager_with_invalid_association_reference 198 assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :preload => :monkeys") { 199 post = Post.find(6, :preload=> :monkeys ) 200 } 201 assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :preload => :monkeys") { 202 post = Post.find(6, :preload=>[ :monkeys ]) 203 } 204 assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :preload => :monkeys") { 205 post = Post.find(6, :preload=>[ 'monkeys' ]) 206 } 207 assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :preload => :monkeys, :elephants") { 208 post = Post.find(6, :preload=>[ :monkeys, :elephants ]) 209 } 210 end 211 212 def find_all_ordered(className, include=nil) 213 className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :preload=>include) 214 end 215 216 def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm 217 # Eager includes of has many and habtm associations aren't necessarily sorted in the same way 218 def assert_equal_after_sort(item1, item2, item3 = nil) 219 assert_equal(item1.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) 220 assert_equal(item3.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) if item3 221 end 222 # Test regular association, association with conditions, association with 223 # STI, and association with conditions assured not to be true 224 post_types = [:posts, :other_posts, :special_posts] 225 # test both has_many and has_and_belongs_to_many 226 [Author, Category].each do |className| 227 d1 = find_all_ordered(className) 228 # test including all post types at once 229 d2 = find_all_ordered(className, post_types) 230 d1.each_index do |i| 231 assert_equal(d1[i], d2[i]) 232 assert_equal_after_sort(d1[i].posts, d2[i].posts) 233 post_types[1..-1].each do |post_type| 234 # test including post_types together 235 d3 = find_all_ordered(className, [:posts, post_type]) 236 assert_equal(d1[i], d3[i]) 237 assert_equal_after_sort(d1[i].posts, d3[i].posts) 238 assert_equal_after_sort(d1[i].send(post_type), d2[i].send(post_type), d3[i].send(post_type)) 239 end 240 end 241 end 242 end 243 244 def test_eager_with_multiple_associations_with_same_table_has_one 245 d1 = find_all_ordered(Firm) 246 d2 = find_all_ordered(Firm, :account) 247 d1.each_index do |i| 248 assert_equal(d1[i], d2[i]) 249 assert_equal(d1[i].account, d2[i].account) 250 end 251 end 252 253 def test_eager_with_multiple_associations_with_same_table_belongs_to 254 firm_types = [:firm, :firm_with_basic_id, :firm_with_other_name, :firm_with_condition] 255 d1 = find_all_ordered(Client) 256 d2 = find_all_ordered(Client, firm_types) 257 d1.each_index do |i| 258 assert_equal(d1[i], d2[i]) 259 firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) } 260 end 261 end 262 263 def test_eager_with_valid_association_as_string_not_symbol 264 assert_nothing_raised { Post.find(:all, :preload => 'comments') } 265 end 266 267 def test_preload_has_one 268 post = Post.find(posts(:sti_comments).id, :preload => 'very_special_comment', :order => 'id') 269 assert_no_queries {post.very_special_comment.body} 270 assert_equal comments(:eager_sti_on_associations_vs_comment), post.very_special_comment 271 end 272 273 def test_preload_belongs_to 274 posts = Post.find(:all, :preload => 'author', :order => 'id', :conditions => 'author_id != 0') 275 assert_no_queries {posts.each {|post| post.author.name}} 276 assert_equal authors(:david), posts.first.author 277 end 278 279 def test_duplicate_middle_objects 280 comments = Comment.find :all, :conditions => 'post_id = 1', :preload => [:post => :author] 281 assert_no_queries do 282 comments.each {|comment| comment.post.author.name} 283 end 284 end 285 286 def test_finding_with_condition 287 firm = Firm.find(:first, :preload => :clients_like_ms) 288 assert_no_queries do 289 assert_equal "Microsoft", firm.clients_like_ms.first.name 290 end 291 end 292 293 def test_finding_with_condition_hash 294 firm = Firm.find(:first, :preload => :clients_like_ms_with_hash_conditions) 295 assert_no_queries do 296 assert_equal "Microsoft", firm.clients_like_ms_with_hash_conditions.first.name 297 end 298 end 299 300 def test_belongs_to_association_with_preload 301 author = posts(:welcome).author_with_preloaded_posts 302 assert_no_queries do 303 author.posts.length 304 end 305 end 306 307 def test_has_many_association_with_preload 308 comments = posts(:welcome).comments_with_post 309 comments.length 310 assert_no_queries do 311 comments.each {|comment| comment.post} 312 end 313 end 314 315 def test_hatbm_association_with_preload 316 categories = posts(:welcome).categories_with_categorizations 317 general= categories.sort_by(&:id).first 318 technology = categories.sort_by(&:id)[1] 319 320 assert_equal categories(:general), general 321 assert_equal categories(:technology), technology 322 323 expected = [categorizations(:david_welcome_general), categorizations(:mary_thinking_general)] 324 assert_no_queries do 325 assert_equal [], technology.categorizations 326 assert_equal expected, general.categorizations 327 end 328 end 329 330 def test_has_one_association_with_preload 331 comment = posts(:sti_comments).very_special_comment_with_preloaded_post 332 assert_no_queries do 333 assert_equal posts(:sti_comments), comment.post 334 end 335 end 336 337 338 def test_polymorphic_has_many 339 expected = taggings(:welcome_general) 340 p = Post.find(posts(:welcome).id, :preload => :taggings) 341 assert_no_queries {assert p.taggings.include?(expected)} 342 assert posts(:welcome).taggings.include?(taggings(:welcome_general)) 343 end 344 345 def test_polymorphic_has_one 346 expected = posts(:welcome) 347 348 tagging = Tagging.find(taggings(:welcome_general).id, :preload => :taggable) 349 assert_no_queries { assert_equal expected, tagging.taggable} 350 end 351 352 def test_polymorphic_belongs_to 353 p = Post.find(posts(:welcome).id, :preload => {:taggings => :taggable}) 354 assert_no_queries {assert_equal posts(:welcome), p.taggings.first.taggable} 355 end 356 357 def test_preload_polymorphic_has_many_through 358 posts = Post.find(:all, :order => 'posts.id') 359 posts_with_tags = Post.find(:all, :preload => :tags, :order => 'posts.id') 360 assert_equal posts.length, posts_with_tags.length 361 posts.length.times do |i| 362 assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } 363 end 364 end 365 366 def test_preload_polymorph_many_types 367 taggings = Tagging.find :all, :preload => :taggable, :conditions => ['taggable_type != ?', 'FakeModel'] 368 assert_no_queries do 369 taggings.first.taggable.id 370 taggings[1].taggable.id 371 end 372 assert_equal items(:dvd), taggings.last.taggable 373 assert_equal posts(:welcome), taggings.first.taggable 374 end 375 376 def test_preload_polymorphic_has_many 377 posts = Post.find(:all, :order => 'posts.id') 378 posts_with_taggings = Post.find(:all, :preload => :taggings, :order => 'posts.id') 379 assert_equal posts.length, posts_with_taggings.length 380 posts.length.times do |i| 381 assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } 382 end 383 end 384 385 end -
activerecord/test/associations/cascaded_preload_test.rb
old new 1 require 'abstract_unit' 2 require 'fixtures/post' 3 require 'fixtures/comment' 4 require 'fixtures/author' 5 require 'fixtures/category' 6 require 'fixtures/categorization' 7 require 'fixtures/company' 8 require 'fixtures/topic' 9 require 'fixtures/reply' 10 11 class CascadedPreloadTest < Test::Unit::TestCase 12 fixtures :authors, :mixins, :companies, :posts, :topics 13 14 def test_eager_association_loading_with_cascaded_two_levels 15 authors = Author.find(:all, :preload=>{:posts=>:comments}, :order=>"authors.id") 16 assert_equal 2, authors.size 17 assert_equal 5, authors[0].posts.size 18 assert_equal 1, authors[1].posts.size 19 assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} 20 end 21 22 def test_eager_association_loading_with_cascaded_two_levels_and_one_level 23 authors = Author.find(:all, :preload=>[{:posts=>:comments}, :categorizations], :order=>"authors.id") 24 assert_equal 2, authors.size 25 assert_equal 5, authors[0].posts.size 26 assert_equal 1, authors[1].posts.size 27 assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} 28 assert_equal 1, authors[0].categorizations.size 29 assert_equal 2, authors[1].categorizations.size 30 end 31 32 def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations 33 authors = Author.find(:all, :preload=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id") 34 assert_no_queries do 35 assert_equal 2, authors.size 36 assert_equal 5, authors[0].posts.size 37 assert_equal 1, authors[1].posts.size 38 assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} 39 end 40 end 41 42 def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference 43 authors = Author.find(:all, :preload=>{:posts=>[:comments, :author]}, :order=>"authors.id") 44 assert_equal 2, authors.size 45 assert_equal 5, authors[0].posts.size 46 assert_equal authors(:david).name, authors[0].name 47 assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq 48 end 49 50 def test_eager_association_loading_with_cascaded_two_levels_with_condition 51 authors = Author.find(:all, :preload=>{:posts=>:comments}, :conditions=>"authors.id=1", :order=>"authors.id") 52 assert_equal 1, authors.size 53 assert_equal 5, authors[0].posts.size 54 end 55 56 def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong 57 firms = Firm.find(:all, :preload=>{:account=>{:firm=>:account}}, :order=>"companies.id") 58 assert_equal 2, firms.size 59 assert_equal firms.first.account, firms.first.account.firm.account 60 assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account } 61 assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account } 62 end 63 64 def test_eager_association_loading_with_has_many_sti 65 topics = Topic.find(:all, :preload => :replies, :order => 'topics.id') 66 assert_equal topics(:first, :second), topics 67 assert_no_queries do 68 assert_equal 1, topics[0].replies.size 69 assert_equal 0, topics[1].replies.size 70 end 71 end 72 73 def test_eager_association_loading_with_belongs_to_sti 74 replies = Reply.find(:all, :preload => :topic, :order => 'topics.id') 75 assert_equal [topics(:second)], replies 76 assert_equal topics(:first), assert_no_queries { replies.first.topic } 77 end 78 79 end 80 81 require 'fixtures/vertex' 82 require 'fixtures/edge' 83 class CascadedPreloadTest < Test::Unit::TestCase 84 fixtures :edges, :vertices 85 86 def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through 87 source = Vertex.find(:first, :preload=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id') 88 assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first } 89 end 90 91 def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many 92 sink = Vertex.find(:first, :preload=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC') 93 assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first } 94 end 95 end -
activerecord/test/fixtures/post.rb
old new 6 6 end 7 7 8 8 belongs_to :author_with_posts, :class_name => "Author", :foreign_key => :author_id, :include => :posts 9 belongs_to :author_with_preloaded_posts, :class_name => "Author", :foreign_key => :author_id, :preload => :posts 9 10 10 11 has_many :comments, :order => "body" do 11 12 def find_most_recent … … 15 16 16 17 has_one :very_special_comment 17 18 has_one :very_special_comment_with_post, :class_name => "VerySpecialComment", :include => :post 19 has_one :very_special_comment_with_preloaded_post, :class_name => "VerySpecialComment", :preload => :post 18 20 has_many :special_comments 19 21 20 22 has_and_belongs_to_many :categories 21 23 has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id' 24 has_and_belongs_to_many :categories_with_categorizations, :class_name => "Category", :join_table => "categories_posts", :join_table => "categories_posts", 25 :association_foreign_key => 'category_id', :preload => :categorizations 22 26 27 has_many :comments_with_post, :class_name => 'Comment', :preload => :post 28 23 29 has_many :taggings, :as => :taggable 24 30 has_many :tags, :through => :taggings, :include => :tagging do 25 31 def add_joins_and_select -
activerecord/lib/active_record/associations/association_proxy.rb
old new 114 114 :offset => @reflection.options[:offset], 115 115 :joins => @reflection.options[:joins], 116 116 :include => @reflection.options[:include], 117 :preload => @reflection.options[:preload], 117 118 :select => @reflection.options[:select] 118 119 ) 119 120 end -
activerecord/lib/active_record/associations/belongs_to_association.rb
old new 44 44 @reflection.klass.find( 45 45 @owner[@reflection.primary_key_name], 46 46 :conditions => conditions, 47 :include => @reflection.options[:include] 47 :include => @reflection.options[:include], 48 :preload => @reflection.options[:preload] 48 49 ) 49 50 end 50 51 -
activerecord/lib/active_record/associations/has_one_association.rb
old new 53 53 @reflection.klass.find(:first, 54 54 :conditions => @finder_sql, 55 55 :order => @reflection.options[:order], 56 :include => @reflection.options[:include] 56 :include => @reflection.options[:include], 57 :preload => @reflection.options[:preload] 57 58 ) 58 59 end 59 60 -
activerecord/lib/active_record/associations.rb
old new 1166 1166 :uniq, 1167 1167 :finder_sql, :counter_sql, 1168 1168 :before_add, :after_add, :before_remove, :after_remove, 1169 :extend 1169 :extend, :preload 1170 1170 ) 1171 1171 1172 1172 options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given? … … 1176 1176 1177 1177 def create_has_one_reflection(association_id, options) 1178 1178 options.assert_valid_keys( 1179 :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as 1179 :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :preload 1180 1180 ) 1181 1181 1182 1182 create_reflection(:has_one, association_id, options, self) … … 1185 1185 def create_belongs_to_reflection(association_id, options) 1186 1186 options.assert_valid_keys( 1187 1187 :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, 1188 :counter_cache, :extend, :polymorphic 1188 :counter_cache, :extend, :polymorphic, :preload 1189 1189 ) 1190 1190 1191 1191 reflection = create_reflection(:belongs_to, association_id, options, self) … … 1204 1204 :uniq, 1205 1205 :finder_sql, :delete_sql, :insert_sql, 1206 1206 :before_add, :after_add, :before_remove, :after_remove, 1207 :extend 1207 :extend, :preload 1208 1208 ) 1209 1209 1210 1210 options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given? -
activerecord/lib/active_record/base.rb
old new 428 428 # end 429 429 def find(*args) 430 430 options = args.extract_options! 431 associations_to_preload = options.delete(:preload) 431 432 validate_find_options(options) 432 433 set_readonly_option!(options) 433 434 434 case args.first435 results = case args.first 435 436 when :first then find_initial(options) 436 437 when :all then find_every(options) 437 438 else find_from_ids(args, options) 438 439 end 440 if associations_to_preload 441 preload_associations(results, associations_to_preload) 442 end 443 results 439 444 end 440 445 441 446 # Works like find(:all), but requires a complete SQL string. Examples: … … 1663 1668 quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) " 1664 1669 quoted_value 1665 1670 end 1671 1672 #loads the named associations for the activerecord object (or objects) given 1673 # 1674 # 1675 def preload_associations(top_objects, associations) 1676 top_objects = [top_objects].flatten.compact 1677 case associations 1678 when Array then associations.each {|association| preload_associations(top_objects, association)} 1679 when Symbol, String then preload_one_association(top_objects, associations.to_sym) 1680 when Hash then 1681 associations.each do |parent, child| 1682 raise "parent must be an association name" unless parent.is_a?( String) || parent.is_a?( Symbol) 1683 preload_associations(top_objects, parent) 1684 reflection = reflections[parent] 1685 parents = top_objects.map {|top_object| top_object.send(reflection.name)}.flatten 1686 unless parents.empty? 1687 parents.first.class.preload_associations(parents, child) 1688 end 1689 end 1690 end 1691 1692 end 1693 1694 def add_preloaded_object_to_collection(parent_object, reflection_name, associated_object) 1695 association_proxy = parent_object.send(reflection_name) 1696 association_proxy.loaded 1697 if associated_object.is_a? Array 1698 association_proxy.target.push(*associated_object) 1699 else 1700 association_proxy.target << associated_object 1701 end 1702 end 1703 1704 def preload_collection_object(id_to_object_map, reflection_name, associated_object, key) 1705 mapped_objects = id_to_object_map[associated_object[key].to_i] 1706 mapped_objects.each do |mapped_object| 1707 add_preloaded_object_to_collection(mapped_object, reflection_name, associated_object) 1708 end 1709 end 1710 1711 def preload_single_object(id_to_object_map, reflection_name, associated_object, key) 1712 mapped_objects = id_to_object_map[associated_object[key].to_i] 1713 mapped_objects.each do |mapped_object| 1714 mapped_object.send("set_#{reflection_name}_target", associated_object) 1715 end 1716 end 1717 1718 1719 def has_n_preload_conditions(reflection) 1720 options = reflection.options 1721 if interface = reflection.options[:as] 1722 conditions = "#{reflection.klass.table_name}.#{interface}_id IN (?) and #{reflection.klass.table_name}.#{interface}_type = '#{self.name}'" 1723 else 1724 foreign_key = options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key 1725 conditions = "#{reflection.klass.table_name}.#{foreign_key} IN (?)" 1726 end 1727 end 1728 1729 def preload_one_association(top_objects, association) 1730 reflection = reflections[association] 1731 raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection 1732 1733 reflection_name = reflection.name 1734 options = reflection.options 1735 table_name = reflection.klass.table_name unless options[:polymorphic] 1736 primary_key_name = reflection.primary_key_name 1737 foreign_key = options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key 1738 if options[:as] 1739 foreign_key = "#{options[:as]}_id" 1740 end 1741 #it is not enough to just do index_by &:id as there could be duplicates (see test_duplicate_middle_objects) 1742 1743 id_to_object_map = {} 1744 ids = [] 1745 top_objects.each do |top_object| 1746 ids << top_object.id 1747 mapped_objects = (id_to_object_map[top_object.id] ||= []) 1748 mapped_objects << top_object 1749 end 1750 ids = top_objects.uniq 1751 1752 case reflection.macro 1753 when :belongs_to 1754 if options[:polymorphic] 1755 polymorph_type = options[:foreign_type] 1756 klasses = top_objects.map {|object| object.send polymorph_type}.uniq.compact.map &:constantize 1757 else 1758 klasses = [reflection.klass] 1759 end 1760 klasses.each do |klass| 1761 table_name = klass.table_name 1762 conditions = "t0.#{self.primary_key} IN (?)" 1763 conditions << " AND (#{sanitize_sql options[:conditions]})" if options[:conditions] 1764 1765 joins = "INNER JOIN #{self.table_name} AS t0 ON #{table_name}.#{primary_key} = t0.#{primary_key_name}" 1766 if options[:polymorphic] 1767 joins << " AND #{polymorph_type} = '#{klass.name}'" 1768 end 1769 associated_objects = klass.find(:all, :conditions => [conditions, ids], 1770 :joins => joins, 1771 :preload => options[:preload], 1772 :select => "#{options[:select] || table_name+'.*'}, t0.#{self.primary_key} as _parent_object_id", 1773 :order => options[:order]) 1774 associated_objects.each do |associated_object| 1775 preload_single_object(id_to_object_map, reflection_name, associated_object, '_parent_object_id') 1776 end 1777 end 1778 when :has_one 1779 top_objects.each {|object| object.send("set_#{reflection.name}_target", nil)} 1780 conditions = has_n_preload_conditions(reflection) 1781 conditions << " AND (#{sanitize_sql options[:conditions]})" if options[:conditions] 1782 associated_objects = reflection.klass.find(:all, :conditions => [conditions, ids], 1783 :select => (options[:select] || "#{table_name}.*"), 1784 :preload => options[:preload], 1785 :order => options[:order]) 1786 associated_objects.each do |associated_object| 1787 preload_single_object(id_to_object_map, reflection_name, associated_object, foreign_key) 1788 end 1789 1790 when :has_many 1791 top_objects.each {|object| object.send(reflection.name).loaded} 1792 1793 if options[:through] 1794 through_reflection = reflections[options[:through]] 1795 through_primary_key = through_reflection.primary_key_name 1796 top_objects.first.class.preload_one_association(top_objects, options[:through]) 1797 through_objects = top_objects.compact.map {|object| object.send options[:through]}.flatten 1798 1799 unless through_objects.empty? 1800 source = reflection.source_reflection.name 1801 through_objects.first.class.preload_one_association(through_objects, source) 1802 1803 through_objects.compact.each do |through_object| 1804 associated_object = through_object.send(source) 1805 mapped_objects = id_to_object_map[through_object[through_primary_key].to_i] 1806 mapped_objects.each do |mapped_object| 1807 add_preloaded_object_to_collection(mapped_object, reflection_name, associated_object) 1808 end 1809 end 1810 end 1811 1812 else 1813 conditions = has_n_preload_conditions(reflection) 1814 conditions << " AND (#{sanitize_sql options[:conditions]})" if options[:conditions] 1815 associated_objects = reflection.klass.find(:all, :conditions => [conditions, ids], 1816 :select => (options[:select] || "#{table_name}.*"), 1817 :preload => options[:preload], 1818 :order => options[:order]) 1819 associated_objects.each do |associated_object| 1820 preload_collection_object(id_to_object_map, reflection_name, associated_object, foreign_key) 1821 end 1822 end 1823 when :has_and_belongs_to_many 1824 top_objects.each {|object| object.send(reflection.name).loaded} 1825 conditions = "t0.#{reflection.primary_key_name} IN (?)" 1826 conditions << " AND (#{options[:conditions]})" if options[:conditions] 1827 associated_objects = reflection.klass.find(:all, :conditions => [conditions, ids], 1828 :preload => options[:preload], 1829 :joins => "INNER JOIN #{options[:join_table]} as t0 ON #{reflection.klass.table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}", 1830 :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as _parent_object_id", 1831 :order => options[:order]) 1832 associated_objects.each do |associated_object| 1833 preload_collection_object(id_to_object_map, reflection_name, associated_object, '_parent_object_id') 1834 end 1835 else 1836 raise "Unsupported association type #{reflection.name}!" 1837 end 1838 1839 end 1840 1666 1841 end 1667 1842 1668 1843 public