Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #9640: preload_ar_polymorph.diff

File preload_ar_polymorph.diff, 35.7 kB (added by fcheung, 7 months ago)

Add support for polymorphic associations

  • activerecord/test/associations/preload_test.rb

    old new  
     1require 'abstract_unit' 
     2require 'fixtures/post' 
     3require 'fixtures/tag' 
     4require 'fixtures/comment' 
     5require 'fixtures/author' 
     6require 'fixtures/category' 
     7require 'fixtures/categorization' 
     8require 'fixtures/tagging' 
     9require 'fixtures/company' 
     10require 'fixtures/person' 
     11require 'fixtures/reader' 
     12 
     13class 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   
     385end 
  • activerecord/test/associations/cascaded_preload_test.rb

    old new  
     1require 'abstract_unit' 
     2require 'fixtures/post' 
     3require 'fixtures/comment' 
     4require 'fixtures/author' 
     5require 'fixtures/category' 
     6require 'fixtures/categorization' 
     7require 'fixtures/company' 
     8require 'fixtures/topic' 
     9require 'fixtures/reply' 
     10 
     11class 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 
     79end 
     80 
     81require 'fixtures/vertex' 
     82require 'fixtures/edge' 
     83class 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 
     95end 
  • activerecord/test/fixtures/post.rb

    old new  
    66  end 
    77 
    88  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 
    910 
    1011  has_many   :comments, :order => "body" do 
    1112    def find_most_recent 
     
    1516 
    1617  has_one  :very_special_comment 
    1718  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 
    1820  has_many :special_comments 
    1921 
    2022  has_and_belongs_to_many :categories 
    2123  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 
    2226 
     27  has_many :comments_with_post, :class_name => 'Comment', :preload => :post 
     28   
    2329  has_many :taggings, :as => :taggable 
    2430  has_many :tags, :through => :taggings, :include => :tagging do 
    2531    def add_joins_and_select 
  • activerecord/lib/active_record/associations/association_proxy.rb

    old new  
    114114            :offset  => @reflection.options[:offset], 
    115115            :joins   => @reflection.options[:joins], 
    116116            :include => @reflection.options[:include], 
     117            :preload => @reflection.options[:preload], 
    117118            :select  => @reflection.options[:select] 
    118119          ) 
    119120        end 
  • activerecord/lib/active_record/associations/belongs_to_association.rb

    old new  
    4444          @reflection.klass.find( 
    4545            @owner[@reflection.primary_key_name],  
    4646            :conditions => conditions, 
    47             :include    => @reflection.options[:include] 
     47            :include    => @reflection.options[:include], 
     48            :preload    => @reflection.options[:preload] 
    4849          ) 
    4950        end 
    5051 
  • activerecord/lib/active_record/associations/has_one_association.rb

    old new  
    5353          @reflection.klass.find(:first,  
    5454            :conditions => @finder_sql,  
    5555            :order      => @reflection.options[:order],  
    56             :include    => @reflection.options[:include] 
     56            :include    => @reflection.options[:include], 
     57            :preload    => @reflection.options[:preload] 
    5758          ) 
    5859        end 
    5960 
  • activerecord/lib/active_record/associations.rb

    old new  
    11661166            :uniq, 
    11671167            :finder_sql, :counter_sql,  
    11681168            :before_add, :after_add, :before_remove, :after_remove,  
    1169             :extend 
     1169            :extend, :preload 
    11701170          ) 
    11711171 
    11721172          options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given? 
     
    11761176 
    11771177        def create_has_one_reflection(association_id, options) 
    11781178          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 
    11801180          ) 
    11811181 
    11821182          create_reflection(:has_one, association_id, options, self) 
     
    11851185        def create_belongs_to_reflection(association_id, options) 
    11861186          options.assert_valid_keys( 
    11871187            :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,  
    1188             :counter_cache, :extend, :polymorphic 
     1188            :counter_cache, :extend, :polymorphic, :preload 
    11891189          ) 
    11901190           
    11911191          reflection = create_reflection(:belongs_to, association_id, options, self) 
     
    12041204            :uniq,  
    12051205            :finder_sql, :delete_sql, :insert_sql, 
    12061206            :before_add, :after_add, :before_remove, :after_remove,  
    1207             :extend 
     1207            :extend, :preload 
    12081208          ) 
    12091209 
    12101210          options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given? 
  • activerecord/lib/active_record/base.rb

    old new  
    428428      #   end 
    429429      def find(*args) 
    430430        options = args.extract_options! 
     431        associations_to_preload = options.delete(:preload) 
    431432        validate_find_options(options) 
    432433        set_readonly_option!(options) 
    433434 
    434         case args.first 
     435        results = case args.first 
    435436          when :first then find_initial(options) 
    436437          when :all   then find_every(options) 
    437438          else             find_from_ids(args, options) 
    438439        end 
     440        if associations_to_preload 
     441          preload_associations(results, associations_to_preload) 
     442        end 
     443        results 
    439444      end 
    440445       
    441446      # Works like find(:all), but requires a complete SQL string. Examples: 
     
    16631668          quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "  
    16641669          quoted_value  
    16651670        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         
    16661841    end 
    16671842 
    16681843    public