aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/associations
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test/cases/associations')
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb252
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb13
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb57
-rw-r--r--activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb3
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb185
-rw-r--r--activerecord/test/cases/associations/extension_test.rb25
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb206
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb205
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb373
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb153
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb124
-rw-r--r--activerecord/test/cases/associations/identity_map_test.rb137
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb32
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb118
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb96
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb546
17 files changed, 1995 insertions, 532 deletions
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index a1ce9b1689..9006914508 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -17,12 +17,12 @@ require 'models/essay'
class BelongsToAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :topics,
:developers_projects, :computers, :authors, :author_addresses,
- :posts, :tags, :taggings, :comments
+ :posts, :tags, :taggings, :comments, :sponsors, :members
def test_belongs_to
Client.find(3).firm.name
assert_equal companies(:first_firm).name, Client.find(3).firm.name
- assert !Client.find(3).firm.nil?, "Microsoft should have a firm"
+ assert_not_nil Client.find(3).firm, "Microsoft should have a firm"
end
def test_belongs_to_with_primary_key
@@ -50,11 +50,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised { account.firm = account.firm }
end
- def test_triple_equality
- assert Client.find(3).firm === Firm
- assert Firm === Client.find(3).firm
- end
-
def test_type_mismatch
assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 }
assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) }
@@ -75,23 +70,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_eager_loading_with_primary_key
- apple = Firm.create("name" => "Apple")
- citibank = Client.create("name" => "Citibank", :firm_name => "Apple")
+ Firm.create("name" => "Apple")
+ Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key)
- assert_not_nil citibank_result.instance_variable_get("@firm_with_primary_key")
+ assert citibank_result.association_cache.key?(:firm_with_primary_key)
end
- def test_no_unexpected_aliasing
- first_firm = companies(:first_firm)
- another_firm = companies(:another_firm)
-
- citibank = Account.create("credit_limit" => 10)
- citibank.firm = first_firm
- original_proxy = citibank.firm
- citibank.firm = another_firm
-
- assert_equal first_firm.object_id, original_proxy.target.object_id
- assert_equal another_firm.object_id, citibank.firm.target.object_id
+ def test_eager_loading_with_primary_key_as_symbol
+ Firm.create("name" => "Apple")
+ Client.create("name" => "Citibank", :firm_name => "Apple")
+ citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key_symbols)
+ assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols)
end
def test_creating_the_belonging_object
@@ -126,6 +115,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple.name, client.firm_name
end
+ def test_create!
+ client = Client.create!(:name => "Jimmy")
+ account = client.create_account!(:credit_limit => 10)
+ assert_equal account, client.account
+ assert account.persisted?
+ client.save
+ client.reload
+ assert_equal account, client.account
+ end
+
+ def test_failing_create!
+ client = Client.create!(:name => "Jimmy")
+ assert_raise(ActiveRecord::RecordInvalid) { client.create_account! }
+ assert_not_nil client.account
+ assert client.account.new_record?
+ end
+
def test_natural_assignment_to_nil
client = Client.find(3)
client.firm = nil
@@ -152,6 +158,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm"
end
+ def test_with_polymorphic_and_condition
+ sponsor = Sponsor.create
+ member = Member.create :name => "Bert"
+ sponsor.sponsorable = member
+
+ assert_equal member, sponsor.sponsorable
+ assert_nil sponsor.sponsorable_with_conditions
+ end
+
def test_with_select
assert_equal Company.find(2).firm_with_select.attributes.size, 1
assert_equal Company.find(2, :include => :firm_with_select ).firm_with_select.attributes.size, 1
@@ -168,17 +183,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
end
- def test_belongs_to_with_primary_key_counter
- debate = Topic.create("title" => "debate")
- assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"
-
- trash = debate.replies_with_primary_key.create("title" => "blah!", "content" => "world around!")
- assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created"
-
- trash.destroy
- assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
- end
-
def test_belongs_to_counter_with_assigning_nil
p = Post.find(1)
c = Comment.find(1)
@@ -191,16 +195,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Post.find(p.id).comments.size
end
- def test_belongs_to_with_primary_key_counter_with_assigning_nil
- debate = Topic.create("title" => "debate")
- reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate")
+ def test_belongs_to_with_primary_key_counter
+ debate = Topic.create("title" => "debate")
+ debate2 = Topic.create("title" => "debate2")
+ reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate")
- assert_equal debate.title, reply.parent_title
- assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count")
+ assert_equal 1, debate.reload.replies_count
+ assert_equal 0, debate2.reload.replies_count
+
+ reply.topic_with_primary_key = debate2
+
+ assert_equal 0, debate.reload.replies_count
+ assert_equal 1, debate2.reload.replies_count
reply.topic_with_primary_key = nil
- assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count")
+ assert_equal 0, debate.reload.replies_count
+ assert_equal 0, debate2.reload.replies_count
end
def test_belongs_to_counter_with_reassigning
@@ -278,10 +289,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
final_cut = Client.new("name" => "Final Cut")
firm = Firm.find(1)
final_cut.firm = firm
- assert final_cut.new_record?
+ assert !final_cut.persisted?
assert final_cut.save
- assert !final_cut.new_record?
- assert !firm.new_record?
+ assert final_cut.persisted?
+ assert firm.persisted?
assert_equal firm, final_cut.firm
assert_equal firm, final_cut.firm(true)
end
@@ -290,10 +301,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
final_cut = Client.new("name" => "Final Cut")
firm = Firm.find(1)
final_cut.firm_with_primary_key = firm
- assert final_cut.new_record?
+ assert !final_cut.persisted?
assert final_cut.save
- assert !final_cut.new_record?
- assert !firm.new_record?
+ assert final_cut.persisted?
+ assert firm.persisted?
assert_equal firm, final_cut.firm_with_primary_key
assert_equal firm, final_cut.firm_with_primary_key(true)
end
@@ -304,13 +315,21 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id
end
- def test_forgetting_the_load_when_foreign_key_enters_late
- c = Client.new
- assert_nil c.firm_with_basic_id
+ def test_setting_foreign_key_after_nil_target_loaded
+ client = Client.new
+ client.firm_with_basic_id
+ client.firm_id = 1
- c.firm_id = 1
- # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id
+ assert_equal companies(:first_firm), client.firm_with_basic_id
+ end
+
+ def test_polymorphic_setting_foreign_key_after_nil_target_loaded
+ sponsor = Sponsor.new
+ sponsor.sponsorable
+ sponsor.sponsorable_id = 1
+ sponsor.sponsorable_type = "Member"
+
+ assert_equal members(:groucho), sponsor.sponsorable
end
def test_field_name_same_as_foreign_key
@@ -412,6 +431,18 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_nil sponsor.sponsorable_id
end
+ def test_assignment_updates_foreign_id_field_for_new_and_saved_records
+ client = Client.new
+ saved_firm = Firm.create :name => "Saved"
+ new_firm = Firm.new
+
+ client.firm = saved_firm
+ assert_equal saved_firm.id, client.client_of
+
+ client.firm = new_firm
+ assert_nil client.client_of
+ end
+
def test_polymorphic_assignment_with_primary_key_updates_foreign_id_field_for_new_and_saved_records
essay = Essay.new
saved_writer = Author.create(:name => "David")
@@ -474,4 +505,125 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
Author.belongs_to :special_author_address, :dependent => :restrict
end
end
+
+ def test_attributes_are_being_set_when_initialized_from_belongs_to_association_with_where_clause
+ new_firm = accounts(:signals37).build_firm(:name => 'Apple')
+ assert_equal new_firm.name, "Apple"
+ end
+
+ def test_reassigning_the_parent_id_updates_the_object
+ client = companies(:second_client)
+
+ client.firm
+ client.firm_with_condition
+ firm_proxy = client.send(:association_instance_get, :firm)
+ firm_with_condition_proxy = client.send(:association_instance_get, :firm_with_condition)
+
+ assert !firm_proxy.stale_target?
+ assert !firm_with_condition_proxy.stale_target?
+ assert_equal companies(:first_firm), client.firm
+ assert_equal companies(:first_firm), client.firm_with_condition
+
+ client.client_of = companies(:another_firm).id
+
+ assert firm_proxy.stale_target?
+ assert firm_with_condition_proxy.stale_target?
+ assert_equal companies(:another_firm), client.firm
+ assert_equal companies(:another_firm), client.firm_with_condition
+ end
+
+ def test_polymorphic_reassignment_of_associated_id_updates_the_object
+ sponsor = sponsors(:moustache_club_sponsor_for_groucho)
+
+ sponsor.sponsorable
+ proxy = sponsor.send(:association_instance_get, :sponsorable)
+
+ assert !proxy.stale_target?
+ assert_equal members(:groucho), sponsor.sponsorable
+
+ sponsor.sponsorable_id = members(:some_other_guy).id
+
+ assert proxy.stale_target?
+ assert_equal members(:some_other_guy), sponsor.sponsorable
+ end
+
+ def test_polymorphic_reassignment_of_associated_type_updates_the_object
+ sponsor = sponsors(:moustache_club_sponsor_for_groucho)
+
+ sponsor.sponsorable
+ proxy = sponsor.send(:association_instance_get, :sponsorable)
+
+ assert !proxy.stale_target?
+ assert_equal members(:groucho), sponsor.sponsorable
+
+ sponsor.sponsorable_type = 'Firm'
+
+ assert proxy.stale_target?
+ assert_equal companies(:first_firm), sponsor.sponsorable
+ end
+
+ def test_reloading_association_with_key_change
+ client = companies(:second_client)
+ firm = client.association(:firm)
+
+ client.firm = companies(:another_firm)
+ firm.reload
+ assert_equal companies(:another_firm), firm.target
+
+ client.client_of = companies(:first_firm).id
+ firm.reload
+ assert_equal companies(:first_firm), firm.target
+ end
+
+ def test_polymorphic_counter_cache
+ tagging = taggings(:welcome_general)
+ post = posts(:welcome)
+ comment = comments(:greetings)
+
+ assert_difference 'post.reload.taggings_count', -1 do
+ assert_difference 'comment.reload.taggings_count', +1 do
+ tagging.taggable = comment
+ end
+ end
+ end
+
+ def test_polymorphic_with_custom_foreign_type
+ sponsor = sponsors(:moustache_club_sponsor_for_groucho)
+ groucho = members(:groucho)
+ other = members(:some_other_guy)
+
+ assert_equal groucho, sponsor.sponsorable
+ assert_equal groucho, sponsor.thing
+
+ sponsor.thing = other
+
+ assert_equal other, sponsor.sponsorable
+ assert_equal other, sponsor.thing
+
+ sponsor.sponsorable = groucho
+
+ assert_equal groucho, sponsor.sponsorable
+ assert_equal groucho, sponsor.thing
+ end
+
+ def test_build_with_conditions
+ client = companies(:second_client)
+ firm = client.build_bob_firm
+
+ assert_equal "Bob", firm.name
+ end
+
+ def test_create_with_conditions
+ client = companies(:second_client)
+ firm = client.create_bob_firm
+
+ assert_equal "Bob", firm.name
+ end
+
+ def test_create_bang_with_conditions
+ client = companies(:second_client)
+ firm = client.create_bob_firm!
+
+ assert_equal "Bob", firm.name
+ end
end
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 15537d6940..2d0d4541b4 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -3,6 +3,7 @@ require 'models/post'
require 'models/author'
require 'models/project'
require 'models/developer'
+require 'models/company'
class AssociationCallbacksTest < ActiveRecord::TestCase
fixtures :posts, :authors, :projects, :developers
@@ -72,7 +73,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
def test_has_many_callbacks_for_save_on_parent
jack = Author.new :name => "Jack"
- post = jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep"
+ jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep"
callback_log = ["before_adding<new>", "after_adding#{jack.posts_with_callbacks.first.id}"]
assert_equal callback_log, jack.post_log
@@ -81,6 +82,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
assert_equal callback_log, jack.post_log
end
+ def test_has_many_callbacks_for_destroy_on_parent
+ firm = Firm.create! :name => "Firm"
+ client = firm.clients.create! :name => "Client"
+ firm.destroy
+
+ assert_equal ["before_remove#{client.id}", "after_remove#{client.id}"], firm.log
+ end
+
def test_has_and_belongs_to_many_add_callback
david = developers(:david)
ar = projects(:active_record)
@@ -149,7 +158,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
assert !@david.unchangable_posts.include?(@authorless)
begin
@david.unchangable_posts << @authorless
- rescue Exception => e
+ rescue Exception
end
assert @david.post_log.empty?
assert !@david.unchangable_posts.include?(@authorless)
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index b93e49613d..39e8a7960a 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -3,28 +3,30 @@ require 'models/post'
require 'models/comment'
require 'models/author'
require 'models/categorization'
+require 'models/category'
require 'models/company'
require 'models/topic'
require 'models/reply'
require 'models/person'
class CascadedEagerLoadingTest < ActiveRecord::TestCase
- fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations, :people
+ fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments,
+ :categorizations, :people, :categories
def test_eager_association_loading_with_cascaded_two_levels
authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
- assert_equal 2, authors.size
+ assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
- assert_equal 1, authors[1].posts.size
- assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
+ assert_equal 3, authors[1].posts.size
+ assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
end
def test_eager_association_loading_with_cascaded_two_levels_and_one_level
authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id")
- assert_equal 2, authors.size
+ assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
- assert_equal 1, authors[1].posts.size
- assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
+ assert_equal 3, authors[1].posts.size
+ assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
assert_equal 1, authors[0].categorizations.size
assert_equal 2, authors[1].categorizations.size
end
@@ -35,7 +37,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
authors = Author.joins(:posts).eager_load(:comments).where(:posts => {:taggings_count => 1}).all
assert_equal 1, assert_no_queries { authors.size }
- assert_equal 9, assert_no_queries { authors[0].comments.size }
+ assert_equal 10, assert_no_queries { authors[0].comments.size }
end
def test_eager_association_loading_grafts_stashed_associations_to_correct_parent
@@ -45,6 +47,31 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').first
end
+ def test_cascaded_eager_association_loading_with_join_for_count
+ categories = Category.joins(:categorizations).includes([{:posts=>:comments}, :authors])
+
+ assert_nothing_raised do
+ assert_equal 3, categories.count
+ assert_equal 3, categories.all.uniq.size # Must uniq since instantiating with inner joins will get dupes
+ end
+ end
+
+ def test_cascaded_eager_association_loading_with_duplicated_includes
+ categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null")
+ assert_nothing_raised do
+ assert_equal 3, categories.count
+ assert_equal 3, categories.all.size
+ end
+ end
+
+ def test_cascaded_eager_association_loading_with_twice_includes_edge_cases
+ categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null")
+ assert_nothing_raised do
+ assert_equal 3, categories.count
+ assert_equal 3, categories.all.size
+ end
+ end
+
def test_eager_association_loading_with_join_for_count
authors = Author.joins(:special_posts).includes([:posts, :categorizations])
@@ -54,15 +81,15 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
- assert_equal 2, authors.size
+ assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
- assert_equal 1, authors[1].posts.size
- assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
+ assert_equal 3, authors[1].posts.size
+ assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
end
def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference
authors = Author.find(:all, :include=>{:posts=>[:comments, :author]}, :order=>"authors.id")
- assert_equal 2, authors.size
+ assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal authors(:david).name, authors[0].name
assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq
@@ -111,7 +138,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_multiple_stis_and_order
- author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => 'authors.name, comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
+ author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :conditions => 'posts.id = 4')
assert_equal authors(:david), author
assert_no_queries do
author.posts.first.special_comments
@@ -130,9 +157,9 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_where_first_level_returns_nil
authors = Author.find(:all, :include => {:post_about_thinking => :comments}, :order => 'authors.id DESC')
- assert_equal [authors(:mary), authors(:david)], authors
+ assert_equal [authors(:bob), authors(:mary), authors(:david)], authors
assert_no_queries do
- authors[1].post_about_thinking.comments.first
+ authors[2].post_about_thinking.comments.first
end
end
end
diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
index b124a2bfc3..d75791cab9 100644
--- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
+++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
@@ -17,7 +17,7 @@ class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
def generate_test_objects
post = Namespaced::Post.create( :title => 'Great stuff', :body => 'This is not', :author_id => 1 )
- tagging = Tagging.create( :taggable => post )
+ Tagging.create( :taggable => post )
end
def test_class_names
@@ -27,6 +27,7 @@ class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
assert_nil post.tagging
+ ActiveRecord::IdentityMap.clear
ActiveRecord::Base.store_full_sti_class = true
post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
assert_instance_of Tagging, post.tagging
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index c7671a8c22..2cf9f89c3c 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -1,9 +1,11 @@
require 'cases/helper'
require 'models/post'
+require 'models/tag'
require 'models/author'
require 'models/comment'
require 'models/category'
require 'models/categorization'
+require 'models/tagging'
require 'active_support/core_ext/array/random_access'
module Remembered
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 40859d425f..40c82f2fb8 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -17,12 +17,27 @@ require 'models/subscription'
require 'models/book'
require 'models/developer'
require 'models/project'
+require 'models/member'
+require 'models/membership'
+require 'models/club'
+require 'models/categorization'
+require 'models/sponsor'
class EagerAssociationTest < ActiveRecord::TestCase
fixtures :posts, :comments, :authors, :author_addresses, :categories, :categories_posts,
- :companies, :accounts, :tags, :taggings, :people, :readers,
+ :companies, :accounts, :tags, :taggings, :people, :readers, :categorizations,
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books,
- :developers, :projects, :developers_projects
+ :developers, :projects, :developers_projects, :members, :memberships, :clubs, :sponsors
+
+ def setup
+ # preheat table existence caches
+ Comment.find_by_id(1)
+ end
+
+ def test_eager_with_has_one_through_join_model_with_conditions_on_the_through
+ member = Member.find(members(:some_other_guy).id, :include => :favourite_club)
+ assert_nil member.favourite_club
+ end
def test_loading_with_one_association
posts = Post.find(:all, :include => :comments)
@@ -53,8 +68,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_with_ordering
list = Post.find(:all, :include => :comments, :order => "posts.id DESC")
- [:eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments,
- :authorless, :thinking, :welcome
+ [:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other,
+ :sti_habtm, :sti_post_and_comments, :sti_comments, :authorless, :thinking, :welcome
].each_with_index do |post, index|
assert_equal posts(post), list[index]
end
@@ -79,6 +94,57 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle
+ Post.connection.expects(:in_clause_length).at_least_once.returns(5)
+ posts = Post.find(:all, :include=>:comments)
+ assert_equal 11, posts.size
+ end
+
+ def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle
+ Post.connection.expects(:in_clause_length).at_least_once.returns(nil)
+ posts = Post.find(:all, :include=>:comments)
+ assert_equal 11, posts.size
+ end
+
+ def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle
+ Post.connection.expects(:in_clause_length).at_least_once.returns(5)
+ posts = Post.find(:all, :include=>:categories)
+ assert_equal 11, posts.size
+ end
+
+ def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle
+ Post.connection.expects(:in_clause_length).at_least_once.returns(nil)
+ posts = Post.find(:all, :include=>:categories)
+ assert_equal 11, posts.size
+ end
+
+ def test_load_associated_records_in_one_query_when_adapter_has_no_limit
+ Post.connection.expects(:in_clause_length).at_least_once.returns(nil)
+
+ post = posts(:welcome)
+ assert_queries(2) do
+ Post.includes(:comments).where(:id => post.id).to_a
+ end
+ end
+
+ def test_load_associated_records_in_several_queries_when_many_ids_passed
+ Post.connection.expects(:in_clause_length).at_least_once.returns(1)
+
+ post1, post2 = posts(:welcome), posts(:thinking)
+ assert_queries(3) do
+ Post.includes(:comments).where(:id => [post1.id, post2.id]).to_a
+ end
+ end
+
+ def test_load_associated_records_in_one_query_when_a_few_ids_passed
+ Post.connection.expects(:in_clause_length).at_least_once.returns(3)
+
+ post = posts(:welcome)
+ assert_queries(2) do
+ Post.includes(:comments).where(:id => post.id).to_a
+ end
+ end
+
def test_including_duplicate_objects_from_belongs_to
popular_post = Post.create!(:title => 'foo', :body => "I like cars!")
comment = popular_post.comments.create!(:body => "lol")
@@ -118,7 +184,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
author = authors(:david)
post = author.post_about_thinking_with_last_comment
last_comment = post.last_comment
- author = assert_queries(3) { Author.find(author.id, :include => {:post_about_thinking_with_last_comment => :last_comment})} # find the author, then find the posts, then find the comments
+ author = assert_queries(ActiveRecord::IdentityMap.enabled? ? 2 : 3) { Author.find(author.id, :include => {:post_about_thinking_with_last_comment => :last_comment})} # find the author, then find the posts, then find the comments
assert_no_queries do
assert_equal post, author.post_about_thinking_with_last_comment
assert_equal last_comment, author.post_about_thinking_with_last_comment.last_comment
@@ -129,7 +195,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
post = posts(:welcome)
author = post.author
author_address = author.author_address
- post = assert_queries(3) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author, then find the address
+ post = assert_queries(ActiveRecord::IdentityMap.enabled? ? 2 : 3) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author, then find the address
assert_no_queries do
assert_equal author, post.author_with_address
assert_equal author_address, post.author_with_address.author_address
@@ -145,6 +211,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_finding_with_includes_on_null_belongs_to_polymorphic_association
+ sponsor = sponsors(:moustache_club_sponsor_for_groucho)
+ sponsor.update_attributes!(:sponsorable => nil)
+ sponsor = assert_queries(1) { Sponsor.find(sponsor.id, :include => :sponsorable) }
+ assert_no_queries do
+ assert_equal nil, sponsor.sponsorable
+ end
+ end
+
def test_loading_from_an_association
posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id")
assert_equal 2, posts.first.comments.size
@@ -174,7 +249,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to
comments = Comment.find(:all, :include => :post)
- assert_equal 10, comments.length
+ assert_equal 11, comments.length
titles = comments.map { |c| c.post.title }
assert titles.include?(posts(:welcome).title)
assert titles.include?(posts(:sti_post_and_comments).title)
@@ -323,7 +398,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_through_a_belongs_to_association
author = authors(:mary)
- post = Post.create!(:author => author, :title => "TITLE", :body => "BODY")
+ Post.create!(:author => author, :title => "TITLE", :body => "BODY")
author.author_favorites.create(:favorite_author_id => 1)
author.author_favorites.create(:favorite_author_id => 2)
posts_with_author_favorites = author.posts.find(:all, :include => :author_favorites)
@@ -450,6 +525,22 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert posts[1].categories.include?(categories(:general))
end
+ # This is only really relevant when the identity map is off. Since the preloader for habtm
+ # gets raw row hashes from the database and then instantiates them, this test ensures that
+ # it only instantiates one actual object per record from the database.
+ def test_has_and_belongs_to_many_should_not_instantiate_same_records_multiple_times
+ welcome = posts(:welcome)
+ categories = Category.includes(:posts)
+
+ general = categories.find { |c| c == categories(:general) }
+ technology = categories.find { |c| c == categories(:technology) }
+
+ post1 = general.posts.to_a.find { |p| p == posts(:welcome) }
+ post2 = technology.posts.to_a.find { |p| p == posts(:welcome) }
+
+ assert_equal post1.object_id, post2.object_id
+ end
+
def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers
posts = authors(:david).posts.find(:all,
:include => :comments,
@@ -521,7 +612,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_inheritance
- posts = SpecialPost.find(:all, :include => [ :comments ])
+ SpecialPost.find(:all, :include => [ :comments ])
end
def test_eager_has_one_with_association_inheritance
@@ -532,7 +623,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_has_many_with_association_inheritance
post = Post.find(4, :include => [ :special_comments ])
post.special_comments.each do |special_comment|
- assert_equal "SpecialComment", special_comment.class.to_s
+ assert special_comment.is_a?(SpecialComment)
end
end
@@ -561,16 +652,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_invalid_association_reference
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
- post = Post.find(6, :include=> :monkeys )
+ Post.find(6, :include=> :monkeys )
}
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
- post = Post.find(6, :include=>[ :monkeys ])
+ Post.find(6, :include=>[ :monkeys ])
}
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
- post = Post.find(6, :include=>[ 'monkeys' ])
+ Post.find(6, :include=>[ 'monkeys' ])
}
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
- post = Post.find(6, :include=>[ :monkeys, :elephants ])
+ Post.find(6, :include=>[ :monkeys, :elephants ])
}
end
@@ -584,8 +675,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_limited_eager_with_multiple_order_columns
- assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title), posts.id', :limit => 2, :offset => 1)
- assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC, posts.id', :limit => 2, :offset => 1)
+ assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1)
+ assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1)
end
def test_limited_eager_with_numeric_in_association
@@ -593,7 +684,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_preload_with_interpolation
- assert_equal [comments(:greetings)], Post.find(posts(:welcome).id, :include => :comments_with_interpolated_conditions).comments_with_interpolated_conditions
+ post = Post.includes(:comments_with_interpolated_conditions).find(posts(:welcome).id)
+ assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
+
+ post = Post.joins(:comments_with_interpolated_conditions).find(posts(:welcome).id)
+ assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
end
def test_polymorphic_type_condition
@@ -726,8 +821,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts = assert_queries(2) do
Post.find(:all, :joins => :comments, :include => :author, :order => 'comments.id DESC')
end
- assert_equal posts(:eager_other), posts[0]
- assert_equal authors(:mary), assert_no_queries { posts[0].author}
+ assert_equal posts(:eager_other), posts[1]
+ assert_equal authors(:mary), assert_no_queries { posts[1].author}
end
def test_eager_loading_with_conditions_on_joined_table_preloads
@@ -737,18 +832,18 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
- posts = assert_queries(2) do
+ posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
- posts = assert_queries(2) do
+ posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id')
end
assert_equal posts(:welcome, :thinking), posts
- posts = assert_queries(2) do
+ posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id')
end
assert_equal posts(:welcome, :thinking), posts
@@ -762,7 +857,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
- posts = assert_queries(2) do
+ posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
end
assert_equal [posts(:welcome)], posts
@@ -779,6 +874,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_loading_with_conditions_on_join_model_preloads
+ Author.columns
+
authors = assert_queries(2) do
Author.find(:all, :include => :author_address, :joins => :comments, :conditions => "posts.title like 'Welcome%'")
end
@@ -824,25 +921,55 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_preload_has_one_using_primary_key
- expected = Firm.find(:first).account_using_primary_key
- firm = Firm.find :first, :include => :account_using_primary_key
+ expected = accounts(:signals37)
+ firm = Firm.find :first, :include => :account_using_primary_key, :order => 'companies.id'
assert_no_queries do
assert_equal expected, firm.account_using_primary_key
end
end
def test_include_has_one_using_primary_key
- expected = Firm.find(1).account_using_primary_key
+ expected = accounts(:signals37)
firm = Firm.find(:all, :include => :account_using_primary_key, :order => 'accounts.id').detect {|f| f.id == 1}
assert_no_queries do
assert_equal expected, firm.account_using_primary_key
end
end
- def test_preloading_empty_polymorphic_parent
+ def test_preloading_empty_belongs_to
+ c = Client.create!(:name => 'Foo', :client_of => Company.maximum(:id) + 1)
+
+ client = assert_queries(2) { Client.preload(:firm).find(c.id) }
+ assert_no_queries { assert_nil client.firm }
+ end
+
+ def test_preloading_empty_belongs_to_polymorphic
t = Tagging.create!(:taggable_type => 'Post', :taggable_id => Post.maximum(:id) + 1, :tag => tags(:general))
- assert_queries(2) { @tagging = Tagging.preload(:taggable).find(t.id) }
- assert_no_queries { assert ! @tagging.taggable }
+ tagging = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Tagging.preload(:taggable).find(t.id) }
+ assert_no_queries { assert_nil tagging.taggable }
+ end
+
+ def test_preloading_through_empty_belongs_to
+ c = Client.create!(:name => 'Foo', :client_of => Company.maximum(:id) + 1)
+
+ client = assert_queries(2) { Client.preload(:accounts).find(c.id) }
+ assert_no_queries { assert client.accounts.empty? }
+ end
+
+ def test_preloading_has_many_through_with_uniq
+ mary = Author.includes(:unique_categorized_posts).where(:id => authors(:mary).id).first
+ assert_equal 1, mary.unique_categorized_posts.length
+ assert_equal 1, mary.unique_categorized_post_ids.length
+ end
+
+ def test_preloading_polymorphic_with_custom_foreign_type
+ sponsor = sponsors(:moustache_club_sponsor_for_groucho)
+ groucho = members(:groucho)
+
+ sponsor = assert_queries(2) {
+ Sponsor.includes(:thing).where(:id => sponsor.id).first
+ }
+ assert_no_queries { assert_equal groucho, sponsor.thing }
end
end
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 9390633d5b..24830a661a 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -30,6 +30,11 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
assert_equal projects(:active_record), developers(:david).projects_extended_by_name_and_block.find_least_recent
end
+ def test_extension_with_scopes
+ assert_equal comments(:greetings), posts(:welcome).comments.offset(1).find_most_recent
+ assert_equal comments(:greetings), posts(:welcome).comments.not_again.find_most_recent
+ end
+
def test_marshalling_extensions
david = developers(:david)
assert_equal projects(:action_controller), david.projects.find_most_recent
@@ -46,17 +51,17 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
end
-
- def test_extension_name
- extension = Proc.new {}
- name = :association_name
-
- assert_equal 'DeveloperAssociationNameAssociationExtension', Developer.send(:create_extension_modules, name, extension, []).first.name
- assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension',
-MyApplication::Business::Developer.send(:create_extension_modules, name, extension, []).first.name
- assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension', MyApplication::Business::Developer.send(:create_extension_modules, name, extension, []).first.name
- assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension', MyApplication::Business::Developer.send(:create_extension_modules, name, extension, []).first.name
+ def test_extension_name
+ assert_equal 'DeveloperAssociationNameAssociationExtension', extension_name(Developer)
+ assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension', extension_name(MyApplication::Business::Developer)
+ assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension', extension_name(MyApplication::Business::Developer)
end
+ private
+ def extension_name(model)
+ builder = ActiveRecord::Associations::Builder::HasMany.new(model, :association_name, {}) { }
+ builder.send(:wrap_block_extension)
+ builder.options[:extend].first.name
+ end
end
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 ed7d9a782c..73d02c9676 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
@@ -72,7 +72,7 @@ class DeveloperWithCounterSQL < ActiveRecord::Base
:join_table => "developers_projects",
:association_foreign_key => "project_id",
:foreign_key => "developer_id",
- :counter_sql => 'SELECT COUNT(*) AS count_all FROM projects INNER JOIN developers_projects ON projects.id = developers_projects.project_id WHERE developers_projects.developer_id =#{id}'
+ :counter_sql => proc { "SELECT COUNT(*) AS count_all FROM projects INNER JOIN developers_projects ON projects.id = developers_projects.project_id WHERE developers_projects.developer_id =#{id}" }
end
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
@@ -101,38 +101,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 't1', record[1]
end
- def test_should_record_timestamp_for_join_table
- setup_data_for_habtm_case
-
- con = ActiveRecord::Base.connection
- sql = 'select * from countries_treaties'
- record = con.select_rows(sql).last
- assert_not_nil record[2]
- assert_not_nil record[3]
- if current_adapter?(:Mysql2Adapter)
- assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2].to_s(:db)
- assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3].to_s(:db)
- else
- assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2]
- assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3]
- end
- end
-
- def test_should_record_timestamp_for_join_table_only_if_timestamp_should_be_recorded
- begin
- Treaty.record_timestamps = false
- setup_data_for_habtm_case
-
- con = ActiveRecord::Base.connection
- sql = 'select * from countries_treaties'
- record = con.select_rows(sql).last
- assert_nil record[2]
- assert_nil record[3]
- ensure
- Treaty.record_timestamps = true
- end
- end
-
def test_has_and_belongs_to_many
david = Developer.find(1)
@@ -218,43 +186,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, aredridel.projects(true).size
end
- def test_adding_uses_default_values_on_join_table
- ac = projects(:action_controller)
- assert !developers(:jamis).projects.include?(ac)
- developers(:jamis).projects << ac
-
- assert developers(:jamis, :reload).projects.include?(ac)
- project = developers(:jamis).projects.detect { |p| p == ac }
- assert_equal 1, project.access_level.to_i
- end
-
- def test_habtm_attribute_access_and_respond_to
- project = developers(:jamis).projects[0]
- assert project.has_attribute?("name")
- assert project.has_attribute?("joined_on")
- assert project.has_attribute?("access_level")
- assert project.respond_to?("name")
- assert project.respond_to?("name=")
- assert project.respond_to?("name?")
- assert project.respond_to?("joined_on")
- # given that the 'join attribute' won't be persisted, I don't
- # think we should define the mutators
- #assert project.respond_to?("joined_on=")
- assert project.respond_to?("joined_on?")
- assert project.respond_to?("access_level")
- #assert project.respond_to?("access_level=")
- assert project.respond_to?("access_level?")
- end
-
def test_habtm_adding_before_save
no_of_devels = Developer.count
no_of_projects = Project.count
aredridel = Developer.new("name" => "Aredridel")
aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")])
- assert aredridel.new_record?
- assert p.new_record?
+ assert !aredridel.persisted?
+ assert !p.persisted?
assert aredridel.save
- assert !aredridel.new_record?
+ assert aredridel.persisted?
assert_equal no_of_devels+1, Developer.count
assert_equal no_of_projects+1, Project.count
assert_equal 2, aredridel.projects.size
@@ -288,22 +228,22 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal devel.projects.last, proj
assert devel.projects.loaded?
- assert proj.new_record?
+ assert !proj.persisted?
devel.save
- assert !proj.new_record?
+ assert proj.persisted?
assert_equal devel.projects.last, proj
assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
end
def test_build_by_new_record
devel = Developer.new(:name => "Marcel", :salary => 75000)
- proj1 = devel.projects.build(:name => "Make bed")
+ devel.projects.build(:name => "Make bed")
proj2 = devel.projects.build(:name => "Lie in it")
assert_equal devel.projects.last, proj2
- assert proj2.new_record?
+ assert !proj2.persisted?
devel.save
- assert !devel.new_record?
- assert !proj2.new_record?
+ assert devel.persisted?
+ assert proj2.persisted?
assert_equal devel.projects.last, proj2
assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated
end
@@ -316,19 +256,19 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal devel.projects.last, proj
assert !devel.projects.loaded?
- assert !proj.new_record?
+ assert proj.persisted?
assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
end
def test_create_by_new_record
devel = Developer.new(:name => "Marcel", :salary => 75000)
- proj1 = devel.projects.build(:name => "Make bed")
+ devel.projects.build(:name => "Make bed")
proj2 = devel.projects.build(:name => "Lie in it")
assert_equal devel.projects.last, proj2
- assert proj2.new_record?
+ assert !proj2.persisted?
devel.save
- assert !devel.new_record?
- assert !proj2.new_record?
+ assert devel.persisted?
+ assert proj2.persisted?
assert_equal devel.projects.last, proj2
assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated
end
@@ -343,7 +283,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
# in Oracle '' is saved as null therefore need to save ' ' in not null column
another_post = categories(:general).post_with_conditions.create(:body => ' ')
- assert !another_post.new_record?
+ assert another_post.persisted?
assert_equal 'Yet Another Testing Title', another_post.title
end
@@ -425,38 +365,41 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_removing_associations_on_destroy
david = DeveloperWithBeforeDestroyRaise.find(1)
assert !david.projects.empty?
- assert_nothing_raised { david.destroy }
+ david.destroy
assert david.projects.empty?
assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty?
end
- def test_additional_columns_from_join_table
- assert_date_from_db Date.new(2004, 10, 10), Developer.find(1).projects.first.joined_on.to_date
- end
-
def test_destroying
david = Developer.find(1)
- active_record = Project.find(1)
+ project = Project.find(1)
david.projects.reload
assert_equal 2, david.projects.size
- assert_equal 3, active_record.developers.size
+ assert_equal 3, project.developers.size
- assert_difference "Project.count", -1 do
- david.projects.destroy(active_record)
+ assert_no_difference "Project.count" do
+ david.projects.destroy(project)
end
+ join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id} AND project_id = #{project.id}")
+ assert join_records.empty?
+
assert_equal 1, david.reload.projects.size
assert_equal 1, david.projects(true).size
end
- def test_destroying_array
+ def test_destroying_many
david = Developer.find(1)
david.projects.reload
+ projects = Project.all
- assert_difference "Project.count", -Project.count do
- david.projects.destroy(Project.find(:all))
+ assert_no_difference "Project.count" do
+ david.projects.destroy(*projects)
end
+ join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id}")
+ assert join_records.empty?
+
assert_equal 0, david.reload.projects.size
assert_equal 0, david.projects(true).size
end
@@ -465,7 +408,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
david = Developer.find(1)
david.projects.reload
assert !david.projects.empty?
- david.projects.destroy_all
+
+ assert_no_difference "Project.count" do
+ david.projects.destroy_all
+ end
+
+ join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id}")
+ assert join_records.empty?
+
assert david.projects.empty?
assert david.projects(true).empty?
end
@@ -559,24 +509,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_dynamic_find_should_respect_association_order
# Developers are ordered 'name DESC, id DESC'
- low_id_jamis = developers(:jamis)
- middle_id_jamis = developers(:poor_jamis)
high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
assert_equal high_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'")
assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis')
end
- def test_dynamic_find_order_should_override_association_order
- # Developers are ordered 'name DESC, id DESC'
- low_id_jamis = developers(:jamis)
- middle_id_jamis = developers(:poor_jamis)
- high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
-
- assert_equal low_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'", :order => 'id')
- assert_equal low_id_jamis, projects(:active_record).developers.find_by_name('Jamis', :order => 'id')
- end
-
def test_dynamic_find_all_should_respect_association_order
# Developers are ordered 'name DESC, id DESC'
low_id_jamis = developers(:jamis)
@@ -587,14 +525,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis')
end
- def test_dynamic_find_all_order_should_override_association_order
- # Developers are ordered 'name DESC, id DESC'
- low_id_jamis = developers(:jamis)
- middle_id_jamis = developers(:poor_jamis)
- high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
-
- assert_equal [low_id_jamis, middle_id_jamis, high_id_jamis], projects(:active_record).developers.find(:all, :conditions => "name = 'Jamis'", :order => 'id')
- assert_equal [low_id_jamis, middle_id_jamis, high_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis', :order => 'id')
+ def test_find_should_append_to_association_order
+ ordered_developers = projects(:active_record).developers.order('projects.id')
+ assert_equal ['developers.name desc, developers.id desc', 'projects.id'], ordered_developers.order_values
end
def test_dynamic_find_all_should_respect_association_limit
@@ -625,11 +558,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_in_association_with_options
- developers = projects(:active_record).developers.find(:all)
+ developers = projects(:active_record).developers.all
assert_equal 3, developers.size
- assert_equal developers(:poor_jamis), projects(:active_record).developers.find(:first, :conditions => "salary < 10000")
- assert_equal developers(:jamis), projects(:active_record).developers.find(:first, :order => "salary DESC")
+ assert_equal developers(:poor_jamis), projects(:active_record).developers.where("salary < 10000").first
end
def test_replace_with_less
@@ -693,25 +625,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_respond_to categories(:technology).select_testing_posts.find(:first), :correctness_marker
end
- def test_updating_attributes_on_rich_associations
- david = projects(:action_controller).developers.first
- david.name = "DHH"
- assert_raise(ActiveRecord::ReadOnlyRecord) { david.save! }
- end
-
- def test_updating_attributes_on_rich_associations_with_limited_find_from_reflection
- david = projects(:action_controller).selected_developers.first
- david.name = "DHH"
- assert_nothing_raised { david.save! }
- end
-
-
- def test_updating_attributes_on_rich_associations_with_limited_find
- david = projects(:action_controller).developers.find(:all, :select => "developers.*").first
- david.name = "DHH"
- assert david.save!
- end
-
def test_join_table_alias
assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL').size
end
@@ -729,12 +642,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_find_grouped
all_posts_from_category1 = Post.find(:all, :conditions => "category_id = 1", :joins => :categories)
grouped_posts_of_category1 = Post.find(:all, :conditions => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories)
- assert_equal 4, all_posts_from_category1.size
- assert_equal 1, grouped_posts_of_category1.size
+ assert_equal 5, all_posts_from_category1.size
+ assert_equal 2, grouped_posts_of_category1.size
end
def test_find_scoped_grouped
- assert_equal 4, categories(:general).posts_grouped_by_title.size
+ assert_equal 5, categories(:general).posts_grouped_by_title.size
assert_equal 1, categories(:technology).posts_grouped_by_title.size
end
@@ -858,10 +771,29 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
david = Developer.find(1)
# clear cache possibly created by other tests
david.projects.reset_column_information
- assert_queries(0) { david.projects.columns; david.projects.columns }
- # and again to verify that reset_column_information clears the cache correctly
+
+ # One query for columns, one for primary key
+ assert_queries(2) { david.projects.columns; david.projects.columns }
+
+ ## and again to verify that reset_column_information clears the cache correctly
david.projects.reset_column_information
- assert_queries(0) { david.projects.columns; david.projects.columns }
+ assert_queries(2) { david.projects.columns; david.projects.columns }
+ end
+
+ def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause
+ new_developer = projects(:action_controller).developers.where(:name => "Marcelo").build
+ assert_equal new_developer.name, "Marcelo"
end
+ def test_attributes_are_being_set_when_initialized_from_habm_association_with_multiple_where_clauses
+ new_developer = projects(:action_controller).developers.where(:name => "Marcelo").where(:salary => 90_000).build
+ assert_equal new_developer.name, "Marcelo"
+ assert_equal new_developer.salary, 90_000
+ end
+
+ def test_include_method_in_has_and_belongs_to_many_association_should_return_true_for_instance_added_with_build
+ project = Project.new
+ developer = project.developers.build
+ assert project.developers.include?(developer)
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ac2021c369..ad774eb9ce 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -13,6 +13,8 @@ require 'models/reader'
require 'models/tagging'
require 'models/invoice'
require 'models/line_item'
+require 'models/car'
+require 'models/bulb'
class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase
class Invoice < ActiveRecord::Base
@@ -41,12 +43,61 @@ end
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments,
- :people, :posts, :readers, :taggings
+ :people, :posts, :readers, :taggings, :cars
def setup
Client.destroyed_client_ids.clear
end
+ def test_create_from_association_should_respect_default_scope
+ car = Car.create(:name => 'honda')
+ assert_equal 'honda', car.name
+
+ bulb = Bulb.create
+ assert_equal 'defaulty', bulb.name
+
+ bulb = car.bulbs.build
+ assert_equal 'defaulty', bulb.name
+
+ bulb = car.bulbs.create
+ assert_equal 'defaulty', bulb.name
+
+ bulb = car.bulbs.create(:name => 'exotic')
+ assert_equal 'exotic', bulb.name
+ end
+
+ # When creating objects on the association, we must not do it within a scope (even though it
+ # would be convenient), because this would cause that scope to be applied to any callbacks etc.
+ def test_build_and_create_should_not_happen_within_scope
+ car = cars(:honda)
+ original_scoped_methods = Bulb.scoped_methods
+
+ bulb = car.bulbs.build
+ assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+
+ bulb = car.bulbs.create
+ assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+
+ bulb = car.bulbs.create!
+ assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ end
+
+ def test_no_sql_should_be_fired_if_association_already_loaded
+ Car.create(:name => 'honda')
+ bulbs = Car.first.bulbs
+ bulbs.inspect # to load all instances of bulbs
+
+ assert_no_queries do
+ bulbs.first()
+ bulbs.first({})
+ end
+
+ assert_no_queries do
+ bulbs.last()
+ bulbs.last({})
+ end
+ end
+
def test_create_resets_cached_counters
person = Person.create!(:first_name => 'tenderlove')
post = Post.first
@@ -54,7 +105,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [], person.readers
assert_nil person.readers.find_by_post_id(post.id)
- reader = person.readers.create(:post_id => post.id)
+ person.readers.create(:post_id => post.id)
assert_equal 1, person.readers.count
assert_equal 1, person.readers.length
@@ -69,7 +120,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [], person.readers
assert_nil person.readers.find_by_post_id(post.id)
- reader = person.readers.find_or_create_by_post_id(post.id)
+ person.readers.find_or_create_by_post_id(post.id)
assert_equal 1, person.readers.count
assert_equal 1, person.readers.length
@@ -128,6 +179,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size
end
+ def test_find_should_append_to_association_order
+ ordered_clients = companies(:first_firm).clients_sorted_desc.order('companies.id')
+ assert_equal ['id DESC', 'companies.id'], ordered_clients.order_values
+ end
+
def test_dynamic_find_last_without_specified_order
assert_equal companies(:second_client), companies(:first_firm).unsorted_clients.find_last_by_type('Client')
end
@@ -137,21 +193,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client')
end
- def test_dynamic_find_order_should_override_association_order
- assert_equal companies(:first_client), companies(:first_firm).clients_sorted_desc.find(:first, :conditions => "type = 'Client'", :order => 'id')
- assert_equal companies(:first_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client', :order => 'id')
- end
-
def test_dynamic_find_all_should_respect_association_order
assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find(:all, :conditions => "type = 'Client'")
assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client')
end
- def test_dynamic_find_all_order_should_override_association_order
- assert_equal [companies(:first_client), companies(:second_client)], companies(:first_firm).clients_sorted_desc.find(:all, :conditions => "type = 'Client'", :order => 'id')
- assert_equal [companies(:first_client), companies(:second_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client', :order => 'id')
- end
-
def test_dynamic_find_all_should_respect_association_limit
assert_equal 1, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'").length
assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length
@@ -173,7 +219,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
assert_equal number_of_posts + 1, Post.count
assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
- assert !another.new_record?
+ assert another.persisted?
end
def test_cant_save_has_many_readonly_association
@@ -233,7 +279,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_counting_using_finder_sql
assert_equal 2, Firm.find(4).clients_using_sql.count
- assert_equal 2, Firm.find(4).clients_using_multiline_sql.count
end
def test_belongs_to_sanity
@@ -388,7 +433,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_adding_using_create
first_firm = companies(:first_firm)
assert_equal 2, first_firm.plain_clients.size
- natural = first_firm.plain_clients.create(:name => "Natural Company")
+ first_firm.plain_clients.create(:name => "Natural Company")
assert_equal 3, first_firm.plain_clients.length
assert_equal 3, first_firm.plain_clients.size
end
@@ -439,7 +484,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !company.clients_of_firm.loaded?
assert_equal "Another Client", new_client.name
- assert new_client.new_record?
+ assert !new_client.persisted?
assert_equal new_client, company.clients_of_firm.last
end
@@ -469,7 +514,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_build_followed_by_save_does_not_load_target
- new_client = companies(:first_firm).clients_of_firm.build("name" => "Another Client")
+ companies(:first_firm).clients_of_firm.build("name" => "Another Client")
assert companies(:first_firm).save
assert !companies(:first_firm).clients_of_firm.loaded?
end
@@ -494,7 +539,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !company.clients_of_firm.loaded?
assert_equal "Another Client", new_client.name
- assert new_client.new_record?
+ assert !new_client.persisted?
assert_equal new_client, company.clients_of_firm.last
end
@@ -529,7 +574,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create
force_signal37_to_load_all_clients_of_firm
new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
- assert !new_client.new_record?
+ assert new_client.persisted?
assert_equal new_client, companies(:first_firm).clients_of_firm.last
assert_equal new_client, companies(:first_firm).clients_of_firm(true).last
end
@@ -540,7 +585,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_create_followed_by_save_does_not_load_target
- new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
+ companies(:first_firm).clients_of_firm.create("name" => "Another Client")
assert companies(:first_firm).save
assert !companies(:first_firm).clients_of_firm.loaded?
end
@@ -549,7 +594,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client")
assert_equal companies(:first_firm).id, the_client.firm_id
assert_equal "Yet another client", the_client.name
- assert the_client.new_record?
+ assert !the_client.persisted?
end
def test_find_or_create_updates_size
@@ -584,7 +629,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal topic.replies.to_a.size, topic.replies_count
end
- def test_deleting_updates_counter_cache_without_dependent_destroy
+ def test_deleting_updates_counter_cache_without_dependent_option
post = posts(:welcome)
assert_difference "post.reload.taggings_count", -1 do
@@ -592,6 +637,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_deleting_updates_counter_cache_with_dependent_delete_all
+ post = posts(:welcome)
+ post.update_attribute(:taggings_with_delete_all_count, post.taggings_count)
+
+ assert_difference "post.reload.taggings_with_delete_all_count", -1 do
+ post.taggings_with_delete_all.delete(post.taggings_with_delete_all.first)
+ end
+ end
+
+ def test_deleting_updates_counter_cache_with_dependent_destroy
+ post = posts(:welcome)
+ post.update_attribute(:taggings_with_destroy_count, post.taggings_count)
+
+ assert_difference "post.reload.taggings_with_destroy_count", -1 do
+ post.taggings_with_destroy.delete(post.taggings_with_destroy.first)
+ end
+ end
+
def test_deleting_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
@@ -604,8 +667,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_delete_all
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
- assert_equal 2, companies(:first_firm).clients_of_firm.size
- companies(:first_firm).clients_of_firm.delete_all
+ clients = companies(:first_firm).clients_of_firm.to_a
+ assert_equal 2, clients.count
+ deleted = companies(:first_firm).clients_of_firm.delete_all
+ assert_equal clients.sort_by(&:id), deleted.sort_by(&:id)
assert_equal 0, companies(:first_firm).clients_of_firm.size
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
end
@@ -625,24 +690,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
client_id = firm.clients_of_firm.first.id
assert_equal 1, firm.clients_of_firm.size
- firm.clients_of_firm.clear
+ cleared = firm.clients_of_firm.clear
assert_equal 0, firm.clients_of_firm.size
assert_equal 0, firm.clients_of_firm(true).size
assert_equal [], Client.destroyed_client_ids[firm.id]
+ assert_equal firm.clients_of_firm.object_id, cleared.object_id
# Should not be destroyed since the association is not dependent.
assert_nothing_raised do
- assert Client.find(client_id).firm.nil?
+ assert_nil Client.find(client_id).firm
end
end
def test_clearing_updates_counter_cache
topic = Topic.first
- topic.replies.clear
- topic.reload
- assert_equal 0, topic.replies_count
+ assert_difference 'topic.reload.replies_count', -1 do
+ topic.replies.clear
+ end
end
def test_clearing_a_dependent_association_collection
@@ -658,7 +724,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [client_id], Client.destroyed_client_ids[firm.id]
# Should be destroyed since the association is dependent.
- assert Client.find_by_id(client_id).nil?
+ assert_nil Client.find_by_id(client_id)
end
def test_clearing_an_exclusively_dependent_association_collection
@@ -738,7 +804,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
another_ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.create
- assert !another_ms_client.new_record?
+ assert another_ms_client.persisted?
assert_equal 'Microsoft', another_ms_client.name
end
@@ -838,7 +904,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_dependence_for_associations_with_hash_condition
david = authors(:david)
- post = posts(:thinking).id
assert_difference('Post.count', -1) { assert david.destroy }
end
@@ -858,7 +923,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_three_levels_of_dependence
topic = Topic.create "title" => "neat and simple"
reply = topic.replies.create "title" => "neat and simple", "content" => "still digging it"
- silly_reply = reply.replies.create "title" => "neat and simple", "content" => "ain't complaining"
+ reply.replies.create "title" => "neat and simple", "content" => "ain't complaining"
assert_nothing_raised { topic.destroy }
end
@@ -883,7 +948,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_depends_and_nullify
num_accounts = Account.count
- num_companies = Company.count
core = companies(:rails_core)
assert_equal accounts(:rails_core_account), core.account
@@ -900,7 +964,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_restrict
firm = RestrictedFirm.new(:name => 'restrict')
firm.save!
- child_firm = firm.companies.create(:name => 'child')
+ firm.companies.create(:name => 'child')
assert !firm.companies.empty?
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
end
@@ -941,6 +1005,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !firm.clients.include?(:first_client)
end
+ def test_replace_failure
+ firm = companies(:first_firm)
+ account = Account.new
+ orig_accounts = firm.accounts.to_a
+
+ assert !account.valid?
+ assert !orig_accounts.empty?
+ assert_raise ActiveRecord::RecordNotSaved do
+ firm.accounts = [account]
+ end
+ assert_equal orig_accounts, firm.accounts
+ end
+
def test_get_ids
assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids
end
@@ -999,21 +1076,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type('SpecialComment')
end
- def test_dynamic_find_order_should_override_association_order_for_through
- assert_equal Comment.find(3), authors(:david).comments_desc.find(:first, :conditions => "comments.type = 'SpecialComment'", :order => 'comments.id')
- assert_equal Comment.find(3), authors(:david).comments_desc.find_by_type('SpecialComment', :order => 'comments.id')
- end
-
def test_dynamic_find_all_should_respect_association_order_for_through
assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find(:all, :conditions => "comments.type = 'SpecialComment'")
assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment')
end
- def test_dynamic_find_all_order_should_override_association_order_for_through
- assert_equal [Comment.find(3), Comment.find(6), Comment.find(7), Comment.find(10)], authors(:david).comments_desc.find(:all, :conditions => "comments.type = 'SpecialComment'", :order => 'comments.id')
- assert_equal [Comment.find(3), Comment.find(6), Comment.find(7), Comment.find(10)], authors(:david).comments_desc.find_all_by_type('SpecialComment', :order => 'comments.id')
- end
-
def test_dynamic_find_all_should_respect_association_limit_for_through
assert_equal 1, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'").length
assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length
@@ -1251,4 +1318,50 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
EOF
end
+
+ def test_attributes_are_being_set_when_initialized_from_has_many_association_with_where_clause
+ new_comment = posts(:welcome).comments.where(:body => "Some content").build
+ assert_equal new_comment.body, "Some content"
+ end
+
+ def test_attributes_are_being_set_when_initialized_from_has_many_association_with_multiple_where_clauses
+ new_comment = posts(:welcome).comments.where(:body => "Some content").where(:type => 'SpecialComment').build
+ assert_equal new_comment.body, "Some content"
+ assert_equal new_comment.type, "SpecialComment"
+ assert_equal new_comment.post_id, posts(:welcome).id
+ end
+
+ def test_include_method_in_has_many_association_should_return_true_for_instance_added_with_build
+ post = Post.new
+ comment = post.comments.build
+ assert post.comments.include?(comment)
+ end
+
+ def test_load_target_respects_protected_attributes
+ topic = Topic.create!
+ reply = topic.replies.create(:title => "reply 1")
+ reply.approved = false
+ reply.save!
+
+ # Save with a different object instance, so the instance that's still held
+ # in topic.relies doesn't know about the changed attribute.
+ reply2 = Reply.find(reply.id)
+ reply2.approved = true
+ reply2.save!
+
+ # Force loading the collection from the db. This will merge the existing
+ # object (reply) with what gets loaded from the db (which includes the
+ # changed approved attribute). approved is a protected attribute, so if mass
+ # assignment is used, it won't get updated and will still be false.
+ first = topic.replies.to_a.first
+ assert_equal reply.id, first.id
+ assert_equal true, first.approved?
+ end
+
+ def test_to_a_should_dup_target
+ ary = topics(:first).replies.to_a
+ target = topics(:first).replies.target
+
+ assert_not_equal target.object_id, ary.object_id
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 0eaadac5ae..9adaebe924 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -17,11 +17,18 @@ require 'models/developer'
require 'models/subscriber'
require 'models/book'
require 'models/subscription'
+require 'models/essay'
+require 'models/category'
+require 'models/owner'
+require 'models/categorization'
+require 'models/member'
+require 'models/membership'
+require 'models/club'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :posts, :readers, :people, :comments, :authors,
- :owners, :pets, :toys, :jobs, :references, :companies,
- :subscribers, :books, :subscriptions, :developers
+ fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags,
+ :owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses,
+ :subscribers, :books, :subscriptions, :developers, :categorizations, :essays
# Dummies to force column loads so query counts are clean.
def setup
@@ -29,6 +36,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Reader.create :person_id => 0, :post_id => 0
end
+ def test_include?
+ person = Person.new
+ post = Post.new
+ person.posts << post
+ assert person.posts.include?(post)
+ end
+
def test_associate_existing
assert_queries(2) { posts(:thinking); people(:david) }
@@ -43,6 +57,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:thinking).reload.people(true).include?(people(:david))
end
+ def test_associate_existing_record_twice_should_add_to_target_twice
+ post = posts(:thinking)
+ person = people(:david)
+
+ assert_difference 'post.people.to_a.count', 2 do
+ post.people << person
+ post.people << person
+ end
+ end
+
def test_associating_new
assert_queries(1) { posts(:thinking) }
new_person = nil # so block binding catches it
@@ -90,6 +114,24 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Ted")
end
+ def test_build_then_save_with_has_many_inverse
+ post = posts(:thinking)
+ person = post.people.build(:first_name => "Bob")
+ person.save
+ post.reload
+
+ assert post.people.include?(person)
+ end
+
+ def test_build_then_save_with_has_one_inverse
+ post = posts(:thinking)
+ person = post.single_people.build(:first_name => "Bob")
+ person.save
+ post.reload
+
+ assert post.single_people.include?(person)
+ end
+
def test_delete_association
assert_queries(2){posts(:welcome);people(:michael); }
@@ -105,8 +147,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_destroy_association
- assert_difference ["Person.count", "Reader.count"], -1 do
- posts(:welcome).people.destroy(people(:michael))
+ assert_no_difference "Person.count" do
+ assert_difference "Reader.count", -1 do
+ posts(:welcome).people.destroy(people(:michael))
+ end
end
assert posts(:welcome).reload.people.empty?
@@ -114,8 +158,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_destroy_all
- assert_difference ["Person.count", "Reader.count"], -1 do
- posts(:welcome).people.destroy_all
+ assert_no_difference "Person.count" do
+ assert_difference "Reader.count", -1 do
+ posts(:welcome).people.destroy_all
+ end
end
assert posts(:welcome).reload.people.empty?
@@ -128,6 +174,137 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_delete_through_belongs_to_with_dependent_nullify
+ Reference.make_comments = true
+
+ person = people(:michael)
+ job = jobs(:magician)
+ reference = Reference.where(:job_id => job.id, :person_id => person.id).first
+
+ assert_no_difference ['Job.count', 'Reference.count'] do
+ assert_difference 'person.jobs.count', -1 do
+ person.jobs_with_dependent_nullify.delete(job)
+ end
+ end
+
+ assert_equal nil, reference.reload.job_id
+ ensure
+ Reference.make_comments = false
+ end
+
+ def test_delete_through_belongs_to_with_dependent_delete_all
+ Reference.make_comments = true
+
+ person = people(:michael)
+ job = jobs(:magician)
+
+ # Make sure we're not deleting everything
+ assert person.jobs.count >= 2
+
+ assert_no_difference 'Job.count' do
+ assert_difference ['person.jobs.count', 'Reference.count'], -1 do
+ person.jobs_with_dependent_delete_all.delete(job)
+ end
+ end
+
+ # Check that the destroy callback on Reference did not run
+ assert_equal nil, person.reload.comments
+ ensure
+ Reference.make_comments = false
+ end
+
+ def test_delete_through_belongs_to_with_dependent_destroy
+ Reference.make_comments = true
+
+ person = people(:michael)
+ job = jobs(:magician)
+
+ # Make sure we're not deleting everything
+ assert person.jobs.count >= 2
+
+ assert_no_difference 'Job.count' do
+ assert_difference ['person.jobs.count', 'Reference.count'], -1 do
+ person.jobs_with_dependent_destroy.delete(job)
+ end
+ end
+
+ # Check that the destroy callback on Reference ran
+ assert_equal "Reference destroyed", person.reload.comments
+ ensure
+ Reference.make_comments = false
+ end
+
+ def test_belongs_to_with_dependent_destroy
+ person = PersonWithDependentDestroyJobs.find(1)
+
+ # Create a reference which is not linked to a job. This should not be destroyed.
+ person.references.create!
+
+ assert_no_difference 'Job.count' do
+ assert_difference 'Reference.count', -person.jobs.count do
+ person.destroy
+ end
+ end
+ end
+
+ def test_belongs_to_with_dependent_delete_all
+ person = PersonWithDependentDeleteAllJobs.find(1)
+
+ # Create a reference which is not linked to a job. This should not be destroyed.
+ person.references.create!
+
+ assert_no_difference 'Job.count' do
+ assert_difference 'Reference.count', -person.jobs.count do
+ person.destroy
+ end
+ end
+ end
+
+ def test_belongs_to_with_dependent_nullify
+ person = PersonWithDependentNullifyJobs.find(1)
+
+ references = person.references.to_a
+
+ assert_no_difference ['Reference.count', 'Job.count'] do
+ person.destroy
+ end
+
+ references.each do |reference|
+ assert_equal nil, reference.reload.job_id
+ end
+ end
+
+ def test_update_counter_caches_on_delete
+ post = posts(:welcome)
+ tag = post.tags.create!(:name => 'doomed')
+
+ assert_difference ['post.reload.taggings_count', 'post.reload.tags_count'], -1 do
+ posts(:welcome).tags.delete(tag)
+ end
+ end
+
+ def test_update_counter_caches_on_delete_with_dependent_destroy
+ post = posts(:welcome)
+ tag = post.tags.create!(:name => 'doomed')
+ post.update_attribute(:tags_with_destroy_count, post.tags.count)
+
+ assert_difference ['post.reload.taggings_count', 'post.reload.tags_with_destroy_count'], -1 do
+ posts(:welcome).tags_with_destroy.delete(tag)
+ end
+ end
+
+ def test_update_counter_caches_on_delete_with_dependent_nullify
+ post = posts(:welcome)
+ tag = post.tags.create!(:name => 'doomed')
+ post.update_attribute(:tags_with_nullify_count, post.tags.count)
+
+ assert_no_difference 'post.reload.taggings_count' do
+ assert_difference 'post.reload.tags_with_nullify_count', -1 do
+ posts(:welcome).tags_with_nullify.delete(tag)
+ end
+ end
+ end
+
def test_replace_association
assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)}
@@ -322,12 +499,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal 2, people(:michael).jobs.size
end
- def test_get_ids_for_belongs_to_source
- assert_sql(/DISTINCT/) { assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort }
- end
-
- def test_get_ids_for_has_many_source
- assert_equal [comments(:eager_other_comment1).id], authors(:mary).comment_ids
+ def test_get_ids
+ assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort
end
def test_get_ids_for_loaded_associations
@@ -354,7 +527,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_association_through_a_belongs_to_association_where_the_association_doesnt_exist
- author = authors(:mary)
post = Post.create!(:title => "TITLE", :body => "BODY")
assert_equal [], post.author_favorites
end
@@ -389,6 +561,41 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
+ def test_has_many_association_through_a_has_many_association_to_self
+ sarah = Person.create!(:first_name => 'Sarah', :primary_contact_id => people(:susan).id, :gender => 'F', :number1_fan_id => 1)
+ john = Person.create!(:first_name => 'John', :primary_contact_id => sarah.id, :gender => 'M', :number1_fan_id => 1)
+ assert_equal sarah.agents, [john]
+ assert_equal people(:susan).agents.map(&:agents).flatten, people(:susan).agents_of_agents
+ end
+
+ def test_associate_existing_with_nonstandard_primary_key_on_belongs_to
+ Categorization.create(:author => authors(:mary), :named_category_name => categories(:general).name)
+ assert_equal categories(:general), authors(:mary).named_categories.first
+ end
+
+ def test_collection_build_with_nonstandard_primary_key_on_belongs_to
+ author = authors(:mary)
+ category = author.named_categories.build(:name => "Primary")
+ author.save
+ assert Categorization.exists?(:author_id => author.id, :named_category_name => category.name)
+ assert author.named_categories(true).include?(category)
+ end
+
+ def test_collection_create_with_nonstandard_primary_key_on_belongs_to
+ author = authors(:mary)
+ category = author.named_categories.create(:name => "Primary")
+ assert Categorization.exists?(:author_id => author.id, :named_category_name => category.name)
+ assert author.named_categories(true).include?(category)
+ end
+
+ def test_collection_delete_with_nonstandard_primary_key_on_belongs_to
+ author = authors(:mary)
+ category = author.named_categories.create(:name => "Primary")
+ author.named_categories.delete(category)
+ assert !Categorization.exists?(:author_id => author.id, :named_category_name => category.name)
+ assert author.named_categories(true).empty?
+ end
+
def test_collection_singular_ids_getter_with_string_primary_keys
book = books(:awdr)
assert_equal 2, book.subscriber_ids.size
@@ -421,4 +628,142 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids}
end
+ def test_build_a_model_from_hm_through_association_with_where_clause
+ assert_nothing_raised { books(:awdr).subscribers.where(:nick => "marklazz").build }
+ end
+
+ def test_attributes_are_being_set_when_initialized_from_hm_through_association_with_where_clause
+ new_subscriber = books(:awdr).subscribers.where(:nick => "marklazz").build
+ assert_equal new_subscriber.nick, "marklazz"
+ end
+
+ def test_attributes_are_being_set_when_initialized_from_hm_through_association_with_multiple_where_clauses
+ new_subscriber = books(:awdr).subscribers.where(:nick => "marklazz").where(:name => 'Marcelo Giorgi').build
+ assert_equal new_subscriber.nick, "marklazz"
+ assert_equal new_subscriber.name, "Marcelo Giorgi"
+ end
+
+ def test_include_method_in_association_through_should_return_true_for_instance_added_with_build
+ person = Person.new
+ reference = person.references.build
+ job = reference.build_job
+ assert person.jobs.include?(job)
+ end
+
+ def test_include_method_in_association_through_should_return_true_for_instance_added_with_nested_builds
+ author = Author.new
+ post = author.posts.build
+ comment = post.comments.build
+ assert author.comments.include?(comment)
+ end
+
+ def test_has_many_through_polymorphic_with_primary_key_option
+ assert_equal [categories(:general)], authors(:david).essay_categories
+
+ authors = Author.joins(:essay_categories).where('categories.id' => categories(:general).id)
+ assert_equal authors(:david), authors.first
+
+ assert_equal [owners(:blackbeard)], authors(:david).essay_owners
+
+ authors = Author.joins(:essay_owners).where("owners.name = 'blackbeard'")
+ assert_equal authors(:david), authors.first
+ end
+
+ def test_has_many_through_with_primary_key_option
+ assert_equal [categories(:general)], authors(:david).essay_categories_2
+
+ authors = Author.joins(:essay_categories_2).where('categories.id' => categories(:general).id)
+ assert_equal authors(:david), authors.first
+ end
+
+ def test_size_of_through_association_should_increase_correctly_when_has_many_association_is_added
+ post = posts(:thinking)
+ readers = post.readers.size
+ post.people << people(:michael)
+ assert_equal readers + 1, post.readers.size
+ end
+
+ def test_has_many_through_with_default_scope_on_join_model
+ assert_equal posts(:welcome).comments.order('id').all, authors(:david).comments_on_first_posts
+ end
+
+ def test_create_has_many_through_with_default_scope_on_join_model
+ category = authors(:david).special_categories.create(:name => "Foo")
+ assert_equal 1, category.categorizations.where(:special => true).count
+ end
+
+ def test_joining_has_many_through_with_uniq
+ mary = Author.joins(:unique_categorized_posts).where(:id => authors(:mary).id).first
+ assert_equal 1, mary.unique_categorized_posts.length
+ assert_equal 1, mary.unique_categorized_post_ids.length
+ end
+
+ def test_joining_has_many_through_belongs_to
+ posts = Post.joins(:author_categorizations).order('posts.id').
+ where('categorizations.id' => categorizations(:mary_thinking_sti).id)
+
+ assert_equal [posts(:eager_other), posts(:misc_by_mary), posts(:other_by_mary)], posts
+ end
+
+ def test_select_chosen_fields_only
+ author = authors(:david)
+ assert_equal ['body'], author.comments.select('comments.body').first.attributes.keys
+ end
+
+ def test_get_has_many_through_belongs_to_ids_with_conditions
+ assert_equal [categories(:general).id], authors(:mary).categories_like_general_ids
+ end
+
+ def test_count_has_many_through_with_named_scope
+ assert_equal 2, authors(:mary).categories.count
+ assert_equal 1, authors(:mary).categories.general.count
+ end
+
+ def test_has_many_through_belongs_to_should_update_when_the_through_foreign_key_changes
+ post = posts(:eager_other)
+
+ post.author_categorizations
+ proxy = post.send(:association_instance_get, :author_categorizations)
+
+ assert !proxy.stale_target?
+ assert_equal authors(:mary).categorizations.sort_by(&:id), post.author_categorizations.sort_by(&:id)
+
+ post.author_id = authors(:david).id
+
+ assert proxy.stale_target?
+ assert_equal authors(:david).categorizations.sort_by(&:id), post.author_categorizations.sort_by(&:id)
+ end
+
+ def test_create_with_conditions_hash_on_through_association
+ member = members(:groucho)
+ club = member.clubs.create!
+
+ assert_equal true, club.reload.membership.favourite
+ end
+
+ def test_deleting_from_has_many_through_a_belongs_to_should_not_try_to_update_counter
+ post = posts(:welcome)
+ address = author_addresses(:david_address)
+
+ assert post.author_addresses.include?(address)
+ post.author_addresses.delete(address)
+ assert post[:author_count].nil?
+ end
+
+ def test_interpolated_conditions
+ post = posts(:welcome)
+ assert !post.tags.empty?
+ assert_equal post.tags, post.interpolated_tags
+ assert_equal post.tags, post.interpolated_tags_2
+ end
+
+ def test_primary_key_option_on_source
+ post = posts(:welcome)
+ category = categories(:general)
+ categorization = Categorization.create!(:post_id => post.id, :named_category_name => category.name)
+
+ assert_equal [category], post.named_categories
+ assert_equal [category.name], post.named_category_ids # checks when target loaded
+ assert_equal [category.name], post.reload.named_category_ids # checks when target no loaded
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 469a21b9bf..c1dad5e246 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -2,9 +2,13 @@ require "cases/helper"
require 'models/developer'
require 'models/project'
require 'models/company'
+require 'models/ship'
+require 'models/pirate'
+require 'models/bulb'
class HasOneAssociationsTest < ActiveRecord::TestCase
- fixtures :accounts, :companies, :developers, :projects, :developers_projects
+ self.use_transactional_fixtures = false unless supports_savepoints?
+ fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates
def setup
Account.destroyed_account_ids.clear
@@ -62,11 +66,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised { company.account = company.account }
end
- def test_triple_equality
- assert Account === companies(:first_firm).account
- assert companies(:first_firm).account === Account
- end
-
def test_type_mismatch
assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 }
assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) }
@@ -91,18 +90,18 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_nullification_on_association_change
firm = companies(:rails_core)
old_account_id = firm.account.id
- firm.account = Account.new
+ firm.account = Account.new(:credit_limit => 5)
# account is dependent with nullify, therefore its firm_id should be nil
assert_nil Account.find(old_account_id).firm_id
end
def test_association_change_calls_delete
- companies(:first_firm).deletable_account = Account.new
+ companies(:first_firm).deletable_account = Account.new(:credit_limit => 5)
assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id]
end
def test_association_change_calls_destroy
- companies(:first_firm).account = Account.new
+ companies(:first_firm).account = Account.new(:credit_limit => 5)
assert_equal [companies(:first_firm).id], Account.destroyed_account_ids[companies(:first_firm).id]
end
@@ -116,35 +115,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal company.account, account
end
- def test_assignment_without_replacement
- apple = Firm.create("name" => "Apple")
- citibank = Account.create("credit_limit" => 10)
- apple.account = citibank
- assert_equal apple.id, citibank.firm_id
-
- hsbc = apple.build_account({ :credit_limit => 20}, false)
- assert_equal apple.id, hsbc.firm_id
- hsbc.save
- assert_equal apple.id, citibank.firm_id
-
- nykredit = apple.create_account({ :credit_limit => 30}, false)
- assert_equal apple.id, nykredit.firm_id
- assert_equal apple.id, citibank.firm_id
- assert_equal apple.id, hsbc.firm_id
- end
-
- def test_assignment_without_replacement_on_create
- apple = Firm.create("name" => "Apple")
- citibank = Account.create("credit_limit" => 10)
- apple.account = citibank
- assert_equal apple.id, citibank.firm_id
-
- hsbc = apple.create_account({:credit_limit => 10}, false)
- assert_equal apple.id, hsbc.firm_id
- hsbc.save
- assert_equal apple.id, citibank.firm_id
- end
-
def test_dependence
num_accounts = Account.count
@@ -163,7 +133,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
firm = ExclusivelyDependentFirm.find(9)
assert_not_nil firm.account
- account_id = firm.account.id
assert_equal [], Account.destroyed_account_ids[firm.id]
firm.destroy
@@ -180,7 +149,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_dependence_with_restrict
firm = RestrictedFirm.new(:name => 'restrict')
firm.save!
- account = firm.create_account(:credit_limit => 10)
+ firm.create_account(:credit_limit => 10)
assert_not_nil firm.account
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
end
@@ -194,13 +163,18 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.account
end
- def test_build_association_twice_without_saving_affects_nothing
- count_of_account = Account.count
- firm = Firm.find(:first)
- account1 = firm.build_account("credit_limit" => 1000)
- account2 = firm.build_account("credit_limit" => 2000)
+ def test_build_and_create_should_not_happen_within_scope
+ pirate = pirates(:blackbeard)
+ original_scoped_methods = Bulb.scoped_methods.dup
- assert_equal count_of_account, Account.count
+ bulb = pirate.build_bulb
+ assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+
+ bulb = pirate.create_bulb
+ assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+
+ bulb = pirate.create_bulb!
+ assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
end
def test_create_association
@@ -209,25 +183,32 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.reload.account
end
- def test_build
- firm = Firm.new("name" => "GlobalMegaCorp")
- firm.save
+ def test_create_association_with_bang
+ firm = Firm.create(:name => "GlobalMegaCorp")
+ account = firm.create_account!(:credit_limit => 1000)
+ assert_equal account, firm.reload.account
+ end
- firm.account = account = Account.new("credit_limit" => 1000)
- assert_equal account, firm.account
- assert account.save
- assert_equal account, firm.account
+ def test_create_association_with_bang_failing
+ firm = Firm.create(:name => "GlobalMegaCorp")
+ assert_raise ActiveRecord::RecordInvalid do
+ firm.create_account!
+ end
+ account = firm.account
+ assert_not_nil account
+ account.credit_limit = 5
+ account.save
+ assert_equal account, firm.reload.account
end
- def test_failing_build_association
+ def test_build
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save
- firm.account = account = Account.new
+ firm.account = account = Account.new("credit_limit" => 1000)
assert_equal account, firm.account
- assert !account.save
+ assert account.save
assert_equal account, firm.account
- assert_equal ["can't be empty"], account.errors["credit_limit"]
end
def test_create
@@ -253,7 +234,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_dependence_with_missing_association_and_nullify
Account.destroy_all
firm = DependentFirm.find(:first)
- assert firm.account.nil?
+ assert_nil firm.account
firm.destroy
end
@@ -268,7 +249,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_assignment_before_child_saved
firm = Firm.find(1)
firm.account = a = Account.new("credit_limit" => 1000)
- assert !a.new_record?
+ assert a.persisted?
assert_equal a, firm.account
assert_equal a, firm.account
assert_equal a, firm.account(true)
@@ -323,7 +304,59 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_create_respects_hash_condition
account = companies(:first_firm).create_account_limit_500_with_hash_conditions
- assert !account.new_record?
+ assert account.persisted?
assert_equal 500, account.credit_limit
end
+
+ def test_attributes_are_being_set_when_initialized_from_has_one_association_with_where_clause
+ new_account = companies(:first_firm).build_account(:firm_name => 'Account')
+ assert_equal new_account.firm_name, "Account"
+ end
+
+ def test_creation_failure_without_dependent_option
+ pirate = pirates(:blackbeard)
+ orig_ship = pirate.ship
+
+ assert_equal ships(:black_pearl), orig_ship
+ new_ship = pirate.create_ship
+ assert_not_equal ships(:black_pearl), new_ship
+ assert_equal new_ship, pirate.ship
+ assert new_ship.new_record?
+ assert_nil orig_ship.pirate_id
+ assert !orig_ship.changed? # check it was saved
+ end
+
+ def test_creation_failure_with_dependent_option
+ pirate = pirates(:blackbeard).becomes(DestructivePirate)
+ orig_ship = pirate.dependent_ship
+
+ new_ship = pirate.create_dependent_ship
+ assert new_ship.new_record?
+ assert orig_ship.destroyed?
+ end
+
+ def test_replacement_failure_due_to_existing_record_should_raise_error
+ pirate = pirates(:blackbeard)
+ pirate.ship.name = nil
+
+ assert !pirate.ship.valid?
+ assert_raise(ActiveRecord::RecordNotSaved) do
+ pirate.ship = ships(:interceptor)
+ end
+ assert_equal ships(:black_pearl), pirate.ship
+ assert_equal pirate.id, pirate.ship.pirate_id
+ end
+
+ def test_replacement_failure_due_to_new_record_should_raise_error
+ pirate = pirates(:blackbeard)
+ new_ship = Ship.new
+
+ assert_raise(ActiveRecord::RecordNotSaved) do
+ pirate.ship = new_ship
+ end
+ assert_equal ships(:black_pearl), pirate.ship
+ assert_equal pirate.id, pirate.ship.pirate_id
+ assert_equal pirate.id, ships(:black_pearl).reload.pirate_id
+ assert_nil new_ship.pirate_id
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 3fcd150422..9ba5549277 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -9,10 +9,17 @@ require 'models/member_detail'
require 'models/minivan'
require 'models/dashboard'
require 'models/speedometer'
+require 'models/category'
+require 'models/author'
+require 'models/essay'
+require 'models/owner'
+require 'models/post'
+require 'models/comment'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, :dashboards, :speedometers
-
+ fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans,
+ :dashboards, :speedometers, :authors, :posts, :comments, :categories, :essays, :owners
+
def setup
@member = members(:groucho)
end
@@ -21,10 +28,6 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_equal clubs(:boring_club), @member.club
end
- def test_has_one_through_with_has_many
- assert_equal clubs(:moustache_club), @member.favourite_club
- end
-
def test_creating_association_creates_through_record
new_member = Member.create(:name => "Chris")
new_member.club = Club.create(:name => "LRUG")
@@ -41,19 +44,19 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert new_member.save
assert_equal clubs(:moustache_club), new_member.club
end
-
+
def test_replace_target_record
new_club = Club.create(:name => "Marx Bros")
@member.club = new_club
@member.reload
assert_equal new_club, @member.club
end
-
+
def test_replacing_target_record_deletes_old_association
assert_no_difference "Membership.count" do
new_club = Club.create(:name => "Bananarama")
@member.club = new_club
- @member.reload
+ @member.reload
end
end
@@ -81,7 +84,19 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
Member.find(:all, :include => :sponsor_club, :conditions => ["name = ?", "Groucho Marx"])
end
assert_equal 1, members.size
- assert_not_nil assert_no_queries {members[0].sponsor_club}
+ assert_not_nil assert_no_queries {members[0].sponsor_club}
+ end
+
+ def test_has_one_through_with_conditions_eager_loading
+ # conditions on the through table
+ assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :favourite_club).favourite_club
+ memberships(:membership_of_favourite_club).update_attribute(:favourite, false)
+ assert_equal nil, Member.find(@member.id, :include => :favourite_club).reload.favourite_club
+
+ # conditions on the source table
+ assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :hairy_club).hairy_club
+ clubs(:moustache_club).update_attribute(:name, "Association of Clean-Shaven Persons")
+ assert_equal nil, Member.find(@member.id, :include => :hairy_club).reload.hairy_club
end
def test_has_one_through_polymorphic_with_source_type
@@ -127,7 +142,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_assigning_association_correctly_assigns_target
new_member = Member.create(:name => "Chris")
new_member.club = new_club = Club.create(:name => "LRUG")
- assert_equal new_club, new_member.club.target
+ assert_equal new_club, new_member.association(:club).target
end
def test_has_one_through_proxy_should_not_respond_to_private_methods
@@ -185,7 +200,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
MemberDetail.find(:all, :include => :member_type)
end
@new_detail = @member_details[0]
- assert @new_detail.loaded_member_type?
+ assert @new_detail.send(:association, :member_type).loaded?
assert_not_nil assert_no_queries { @new_detail.member_type }
end
@@ -205,11 +220,94 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
Club.find(@club.id, :include => :sponsored_member).save!
end
end
-
+
+ def test_through_belongs_to_after_destroy
+ @member_detail = MemberDetail.new(:extra_data => 'Extra')
+ @member.member_detail = @member_detail
+ @member.save!
+
+ assert_not_nil @member_detail.member_type
+ @member_detail.destroy
+ assert_queries(1) do
+ assert_not_nil @member_detail.member_type(true)
+ end
+
+ @member_detail.member.destroy
+ assert_queries(1) do
+ assert_nil @member_detail.member_type(true)
+ end
+ end
+
def test_value_is_properly_quoted
minivan = Minivan.find('m1')
assert_nothing_raised do
minivan.dashboard
end
end
+
+ def test_has_one_through_polymorphic_with_primary_key_option
+ assert_equal categories(:general), authors(:david).essay_category
+
+ authors = Author.joins(:essay_category).where('categories.id' => categories(:general).id)
+ assert_equal authors(:david), authors.first
+
+ assert_equal owners(:blackbeard), authors(:david).essay_owner
+
+ authors = Author.joins(:essay_owner).where("owners.name = 'blackbeard'")
+ assert_equal authors(:david), authors.first
+ end
+
+ def test_has_one_through_with_primary_key_option
+ assert_equal categories(:general), authors(:david).essay_category_2
+
+ authors = Author.joins(:essay_category_2).where('categories.id' => categories(:general).id)
+ assert_equal authors(:david), authors.first
+ end
+
+ def test_has_one_through_with_default_scope_on_join_model
+ assert_equal posts(:welcome).comments.order('id').first, authors(:david).comment_on_first_post
+ end
+
+ def test_has_one_through_many_raises_exception
+ assert_raise(ActiveRecord::HasOneThroughCantAssociateThroughCollection) do
+ members(:groucho).club_through_many
+ end
+ end
+
+ def test_has_one_through_belongs_to_should_update_when_the_through_foreign_key_changes
+ minivan = minivans(:cool_first)
+
+ minivan.dashboard
+ proxy = minivan.send(:association_instance_get, :dashboard)
+
+ assert !proxy.stale_target?
+ assert_equal dashboards(:cool_first), minivan.dashboard
+
+ minivan.speedometer_id = speedometers(:second).id
+
+ assert proxy.stale_target?
+ assert_equal dashboards(:second), minivan.dashboard
+ end
+
+ def test_has_one_through_belongs_to_setting_belongs_to_foreign_key_after_nil_target_loaded
+ minivan = Minivan.new
+
+ minivan.dashboard
+ proxy = minivan.send(:association_instance_get, :dashboard)
+
+ minivan.speedometer_id = speedometers(:second).id
+
+ assert proxy.stale_target?
+ assert_equal dashboards(:second), minivan.dashboard
+ end
+
+ def test_assigning_has_one_through_belongs_to_with_new_record_owner
+ minivan = Minivan.new
+ dashboard = dashboards(:cool_first)
+
+ minivan.dashboard = dashboard
+
+ assert_equal dashboard, minivan.dashboard
+ assert_equal dashboard, minivan.speedometer.dashboard
+ end
end
diff --git a/activerecord/test/cases/associations/identity_map_test.rb b/activerecord/test/cases/associations/identity_map_test.rb
new file mode 100644
index 0000000000..9b8635774c
--- /dev/null
+++ b/activerecord/test/cases/associations/identity_map_test.rb
@@ -0,0 +1,137 @@
+require "cases/helper"
+require 'models/author'
+require 'models/post'
+
+if ActiveRecord::IdentityMap.enabled?
+class InverseHasManyIdentityMapTest < ActiveRecord::TestCase
+ fixtures :authors, :posts
+
+ def test_parent_instance_should_be_shared_with_every_child_on_find
+ m = Author.first
+ is = m.posts
+ is.each do |i|
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance"
+ end
+ end
+
+ def test_parent_instance_should_be_shared_with_eager_loaded_children
+ m = Author.find(:first, :include => :posts)
+ is = m.posts
+ is.each do |i|
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance"
+ end
+
+ m = Author.find(:first, :include => :posts, :order => 'posts.id')
+ is = m.posts
+ is.each do |i|
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance"
+ end
+ end
+
+ def test_parent_instance_should_be_shared_with_newly_built_child
+ m = Author.first
+ i = m.posts.build(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
+ assert_not_nil i.author
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to just-built-child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_newly_block_style_built_child
+ m = Author.first
+ i = m.posts.build {|ii| ii.title = 'Industrial Revolution Re-enactment'; ii.body = 'Lorem ipsum'}
+ assert_not_nil i.title, "Child attributes supplied to build via blocks should be populated"
+ assert_not_nil i.author
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to just-built-child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_newly_created_child
+ m = Author.first
+ i = m.posts.create(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
+ assert_not_nil i.author
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child
+ m = Author.first
+ i = m.posts.create!(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
+ assert_not_nil i.author
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_newly_block_style_created_child
+ m = Author.first
+ i = m.posts.create {|ii| ii.title = 'Industrial Revolution Re-enactment'; ii.body = 'Lorem ipsum'}
+ assert_not_nil i.title, "Child attributes supplied to create via blocks should be populated"
+ assert_not_nil i.author
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_poked_in_child
+ m = Author.first
+ i = Post.create(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
+ m.posts << i
+ assert_not_nil i.author
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_replaced_via_accessor_children
+ m = Author.first
+ i = Post.new(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
+ m.posts = [i]
+ assert_same m, i.author
+ assert_not_nil i.author
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to replaced-child-owned instance"
+ end
+
+ def test_parent_instance_should_be_shared_with_replaced_via_method_children
+ m = Author.first
+ i = Post.new(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
+ m.posts = [i]
+ assert_not_nil i.author
+ assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
+ i.author.name = 'Mungo'
+ assert_equal m.name, i.author.name, "Name of man should be the same after changes to replaced-child-owned instance"
+ end
+end
+end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 4ba867dc7c..e2228228a3 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -4,15 +4,26 @@ require 'models/comment'
require 'models/author'
require 'models/category'
require 'models/categorization'
+require 'models/person'
+require 'models/tagging'
+require 'models/tag'
class InnerJoinAssociationTest < ActiveRecord::TestCase
- fixtures :authors, :posts, :comments, :categories, :categories_posts, :categorizations
+ fixtures :authors, :posts, :comments, :categories, :categories_posts, :categorizations,
+ :taggings, :tags
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
result = Author.joins(:thinking_posts, :welcome_posts).to_a
assert_equal authors(:david), result.first
end
+ def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations
+ assert_nothing_raised do
+ sql = Person.joins(:agents => {:agents => :agents}).joins(:agents => {:agents => {:primary_contact => :agents}}).to_sql
+ assert_match(/agents_people_4/i, sql)
+ end
+ end
+
def test_construct_finder_sql_ignores_empty_joins_hash
sql = Author.joins({}).to_sql
assert_no_match(/JOIN/i, sql)
@@ -62,4 +73,23 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
authors_with_welcoming_post_titles = Author.calculate(:count, 'authors.id', :joins => :posts, :distinct => true, :conditions => "posts.title like 'Welcome%'")
assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'"
end
+
+ def test_find_with_sti_join
+ scope = Post.joins(:special_comments).where(:id => posts(:sti_comments).id)
+
+ # The join should match SpecialComment and its subclasses only
+ assert scope.where("comments.type" => "Comment").empty?
+ assert !scope.where("comments.type" => "SpecialComment").empty?
+ assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ end
+
+ def test_find_with_conditions_on_reflection
+ assert !posts(:welcome).comments.empty?
+ assert Post.joins(:nonexistant_comments).where(:id => posts(:welcome).id).empty? # [sic!]
+ end
+
+ def test_find_with_conditions_on_through_reflection
+ assert !posts(:welcome).tags.empty?
+ assert Post.joins(:misc_tags).where(:id => posts(:welcome).id).empty?
+ end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index fa5c2e49df..76282213d8 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -114,7 +114,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_built_child
- m = men(:gordon)
+ m = Man.find(:first)
f = m.build_face(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
@@ -125,7 +125,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_created_child
- m = men(:gordon)
+ m = Man.find(:first)
f = m.create_face(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
@@ -137,40 +137,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method
m = Man.find(:first)
- f = m.face.create!(:description => 'haunted')
- assert_not_nil f.man
- assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
- f.man.name = 'Mungo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_newly_built_child_when_we_dont_replace_existing
- m = Man.find(:first)
- f = m.build_face({:description => 'haunted'}, false)
- assert_not_nil f.man
- assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
- f.man.name = 'Mungo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_newly_created_child_when_we_dont_replace_existing
- m = Man.find(:first)
- f = m.create_face({:description => 'haunted'}, false)
- assert_not_nil f.man
- assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
- f.man.name = 'Mungo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method_when_we_dont_replace_existing
- m = Man.find(:first)
- f = m.face.create!({:description => 'haunted'}, false)
+ f = m.create_face!(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
m.name = 'Bongo'
@@ -191,30 +158,6 @@ class InverseHasOneTests < ActiveRecord::TestCase
assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
end
- def test_parent_instance_should_be_shared_with_replaced_via_method_child
- m = Man.find(:first)
- f = Face.new(:description => 'haunted')
- m.face.replace(f)
- assert_not_nil f.man
- assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
- f.man.name = 'Mungo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_replaced_via_method_child_when_we_dont_replace_existing
- m = Man.find(:first)
- f = Face.new(:description => 'haunted')
- m.face.replace(f, false)
- assert_not_nil f.man
- assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
- f.man.name = 'Mungo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
- end
-
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).dirty_face }
end
@@ -257,17 +200,6 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
end
- def test_parent_instance_should_be_shared_with_newly_built_child
- m = men(:gordon)
- i = m.interests.build(:topic => 'Industrial Revolution Re-enactment')
- assert_not_nil i.man
- assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
- i.man.name = 'Mungo'
- assert_equal m.name, i.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
- end
-
def test_parent_instance_should_be_shared_with_newly_block_style_built_child
m = Man.find(:first)
i = m.interests.build {|ii| ii.topic = 'Industrial Revolution Re-enactment'}
@@ -280,17 +212,6 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
end
- def test_parent_instance_should_be_shared_with_newly_created_child
- m = men(:gordon)
- i = m.interests.create(:topic => 'Industrial Revolution Re-enactment')
- assert_not_nil i.man
- assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
- i.man.name = 'Mungo'
- assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
- end
-
def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child
m = Man.find(:first)
i = m.interests.create!(:topic => 'Industrial Revolution Re-enactment')
@@ -338,18 +259,6 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
end
- def test_parent_instance_should_be_shared_with_replaced_via_method_children
- m = Man.find(:first)
- i = Interest.new(:topic => 'Industrial Revolution Re-enactment')
- m.interests.replace([i])
- assert_not_nil i.man
- assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
- i.man.name = 'Mungo'
- assert_equal m.name, i.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
- end
-
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).secret_interests }
end
@@ -433,19 +342,6 @@ class InverseBelongsToTests < ActiveRecord::TestCase
assert_equal f.description, m.face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
end
- def test_child_instance_should_be_shared_with_replaced_via_method_parent
- f = faces(:trusting)
- assert_not_nil f.man
- m = Man.new(:name => 'Charles')
- f.man.replace(m)
- assert_not_nil m.face
- assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
- f.description = 'gormless'
- assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
- m.face.description = 'pleasing'
- assert_equal f.description, m.face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
- end
-
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_man }
end
@@ -484,7 +380,6 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
def test_child_instance_should_be_shared_with_replaced_via_accessor_parent
face = faces(:confused)
- old_man = face.polymorphic_man
new_man = Man.new
assert_not_nil face.polymorphic_man
@@ -499,11 +394,10 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
def test_child_instance_should_be_shared_with_replaced_via_method_parent
face = faces(:confused)
- old_man = face.polymorphic_man
new_man = Man.new
assert_not_nil face.polymorphic_man
- face.polymorphic_man.replace(new_man)
+ face.polymorphic_man = new_man
assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance"
face.description = 'Bongo'
@@ -551,8 +445,8 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models
assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
i = Interest.find(:first)
- z = i.zine
- m = i.man
+ i.zine
+ i.man
end
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 447fe4d275..1f95b31497 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -2,6 +2,7 @@ require "cases/helper"
require 'models/tag'
require 'models/tagging'
require 'models/post'
+require 'models/rating'
require 'models/item'
require 'models/comment'
require 'models/author'
@@ -13,7 +14,8 @@ require 'models/book'
require 'models/citation'
class AssociationsJoinModelTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
+
fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books,
# Reload edges table from fixtures as otherwise repeated test was failing
:edges
@@ -43,11 +45,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_queries(1) { assert_equal 0, author.unique_categorized_posts.count(:title, :conditions => "title is NULL") }
assert !authors(:mary).unique_categorized_posts.loaded?
end
-
+
def test_has_many_uniq_through_find
assert_equal 1, authors(:mary).unique_categorized_posts.find(:all).size
end
-
+
def test_has_many_uniq_through_dynamic_find
assert_equal 1, authors(:mary).unique_categorized_posts.find_all_by_title("So I was thinking").size
end
@@ -85,16 +87,9 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
end
- def test_polymorphic_has_many_going_through_join_model_with_disabled_include
- assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
- assert_queries 1 do
- tag.tagging
- end
- end
-
def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins
assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
- tag.author_id
+ assert_nothing_raised(NoMethodError) { tag.author_id }
end
def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key
@@ -159,7 +154,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_create_polymorphic_has_one_with_scope
old_count = Tagging.count
- tagging = posts(:welcome).tagging.create(:tag => tags(:misc))
+ tagging = posts(:welcome).create_tagging(:tag => tags(:misc))
assert_equal "Post", tagging.taggable_type
assert_equal old_count+1, Tagging.count
end
@@ -220,7 +215,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_with_piggyback
- assert_equal "2", categories(:sti_test).authors.first.post_id.to_s
+ assert_equal "2", categories(:sti_test).authors_with_select.first.post_id.to_s
end
def test_include_has_many_through
@@ -294,10 +289,26 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_going_through_join_model_with_custom_foreign_key
- assert_equal [], posts(:thinking).authors
+ assert_equal [authors(:bob)], posts(:thinking).authors
assert_equal [authors(:mary)], posts(:authorless).authors
end
-
+
+ def test_has_many_going_through_join_model_with_custom_primary_key
+ assert_equal [authors(:david)], posts(:thinking).authors_using_author_id
+ end
+
+ def test_has_many_going_through_polymorphic_join_model_with_custom_primary_key
+ assert_equal [tags(:general)], posts(:eager_other).tags_using_author_id
+ end
+
+ def test_has_many_through_with_custom_primary_key_on_belongs_to_source
+ assert_equal [authors(:david), authors(:david)], posts(:thinking).author_using_custom_pk
+ end
+
+ def test_has_many_through_with_custom_primary_key_on_has_many_source
+ assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order('authors.id')
+ end
+
def test_both_scoped_and_explicit_joins_should_be_respected
assert_nothing_raised do
Post.send(:with_scope, :find => {:joins => "left outer join comments on comments.id = posts.id"}) do
@@ -324,11 +335,16 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_polymorphic
- assert_raise ActiveRecord::HasManyThroughAssociationPolymorphicError do
- assert_equal posts(:welcome, :thinking), tags(:general).taggables
+ assert_raise ActiveRecord::HasManyThroughAssociationPolymorphicSourceError do
+ tags(:general).taggables
+ end
+
+ assert_raise ActiveRecord::HasManyThroughAssociationPolymorphicThroughError do
+ taggings(:welcome_general).things
end
+
assert_raise ActiveRecord::EagerLoadPolymorphicError do
- assert_equal posts(:welcome, :thinking), tags(:general).taggings.find(:all, :include => :taggable, :conditions => 'bogus_table.column = 1')
+ tags(:general).taggings.find(:all, :include => :taggable, :conditions => 'bogus_table.column = 1')
end
end
@@ -384,19 +400,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
end
- def test_has_many_through_has_many_through
- assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags }
- end
-
- def test_has_many_through_habtm
- assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).post_categories }
- end
-
def test_eager_load_has_many_through_has_many
author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id'
SpecialComment.new; VerySpecialComment.new
assert_no_queries do
- assert_equal [1,2,3,5,6,7,8,9,10], author.comments.collect(&:id)
+ assert_equal [1,2,3,5,6,7,8,9,10,12], author.comments.collect(&:id)
end
end
@@ -427,8 +435,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_uses_conditions_specified_on_the_has_many_association
author = Author.find(:first)
- assert !author.comments.blank?
- assert author.nonexistant_comments.blank?
+ assert_present author.comments
+ assert_blank author.nonexistant_comments
end
def test_has_many_through_uses_correct_attributes
@@ -440,11 +448,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
new_tag = Tag.new(:name => "new")
saved_post.tags << new_tag
- assert !new_tag.new_record? #consistent with habtm!
- assert !saved_post.new_record?
+ assert new_tag.persisted? #consistent with habtm!
+ assert saved_post.persisted?
assert saved_post.tags.include?(new_tag)
- assert !new_tag.new_record?
+ assert new_tag.persisted?
assert saved_post.reload.tags(true).include?(new_tag)
@@ -452,16 +460,16 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
saved_tag = tags(:general)
new_post.tags << saved_tag
- assert new_post.new_record?
- assert !saved_tag.new_record?
+ assert !new_post.persisted?
+ assert saved_tag.persisted?
assert new_post.tags.include?(saved_tag)
new_post.save!
- assert !new_post.new_record?
+ assert new_post.persisted?
assert new_post.reload.tags(true).include?(saved_tag)
- assert posts(:thinking).tags.build.new_record?
- assert posts(:thinking).tags.new.new_record?
+ assert !posts(:thinking).tags.build.persisted?
+ assert !posts(:thinking).tags.new.persisted?
end
def test_create_associate_when_adding_to_has_many_through
@@ -496,15 +504,19 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) }
end
+ def test_add_to_join_table_with_no_id
+ assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) }
+ end
+
def test_has_many_through_collection_size_doesnt_load_target_if_not_loaded
author = authors(:david)
- assert_equal 9, author.comments.size
+ assert_equal 10, author.comments.size
assert !author.comments.loaded?
end
def test_has_many_through_collection_size_uses_counter_cache_if_it_exists
author = authors(:david)
- author.stubs(:read_attribute).with('comments_count').returns(100)
+ author.stubs(:_read_attribute).with('comments_count').returns(100)
assert_equal 100, author.comments.size
assert !author.comments.loaded?
end
@@ -575,7 +587,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_calculations_on_has_many_through_should_disambiguate_fields
assert_nothing_raised { authors(:david).categories.maximum(:id) }
end
-
+
def test_calculations_on_has_many_through_should_not_disambiguate_fields_unless_necessary
assert_nothing_raised { authors(:david).categories.maximum("categories.id") }
end
@@ -632,7 +644,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_preload_nil_polymorphic_belongs_to
assert_nothing_raised do
- taggings = Tagging.find(:all, :include => :taggable, :conditions => ['taggable_type IS NULL'])
+ Tagging.find(:all, :include => :taggable, :conditions => ['taggable_type IS NULL'])
end
end
@@ -675,7 +687,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
assert ! david.categories.loaded?
end
-
+
def test_has_many_through_include_returns_false_for_non_matching_record_to_verify_scoping
david = authors(:david)
category = Category.create!(:name => 'Not Associated')
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
new file mode 100644
index 0000000000..dd450a2a8e
--- /dev/null
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -0,0 +1,546 @@
+require "cases/helper"
+require 'models/author'
+require 'models/post'
+require 'models/person'
+require 'models/reference'
+require 'models/job'
+require 'models/reader'
+require 'models/comment'
+require 'models/tag'
+require 'models/tagging'
+require 'models/subscriber'
+require 'models/book'
+require 'models/subscription'
+require 'models/rating'
+require 'models/member'
+require 'models/member_detail'
+require 'models/member_type'
+require 'models/sponsor'
+require 'models/club'
+require 'models/organization'
+require 'models/category'
+require 'models/categorization'
+require 'models/membership'
+require 'models/essay'
+
+class NestedThroughAssociationsTest < ActiveRecord::TestCase
+ fixtures :authors, :books, :posts, :subscriptions, :subscribers, :tags, :taggings,
+ :people, :readers, :references, :jobs, :ratings, :comments, :members, :member_details,
+ :member_types, :sponsors, :clubs, :organizations, :categories, :categories_posts,
+ :categorizations, :memberships, :essays
+
+ # Through associations can either use the has_many or has_one macros.
+ #
+ # has_many
+ # - Source reflection can be has_many, has_one, belongs_to or has_and_belongs_to_many
+ # - Through reflection can be has_many, has_one, belongs_to or has_and_belongs_to_many
+ #
+ # has_one
+ # - Source reflection can be has_one or belongs_to
+ # - Through reflection can be has_one or belongs_to
+ #
+ # Additionally, the source reflection and/or through reflection may be subject to
+ # polymorphism and/or STI.
+ #
+ # When testing these, we need to make sure it works via loading the association directly, or
+ # joining the association, or including the association. We also need to ensure that associations
+ # are readonly where relevant.
+
+ # has_many through
+ # Source: has_many through
+ # Through: has_many
+ def test_has_many_through_has_many_with_has_many_through_source_reflection
+ general = tags(:general)
+ assert_equal [general, general], authors(:david).tags
+ end
+
+ def test_has_many_through_has_many_with_has_many_through_source_reflection_preload
+ authors = assert_queries(5) { Author.includes(:tags).to_a }
+ general = tags(:general)
+
+ assert_no_queries do
+ assert_equal [general, general], authors.first.tags
+ end
+ end
+
+ def test_has_many_through_has_many_with_has_many_through_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Author.where('tags.id' => tags(:general).id),
+ [authors(:david)], :tags
+ )
+
+ # This ensures that the polymorphism of taggings is being observed correctly
+ authors = Author.joins(:tags).where('taggings.taggable_type' => 'FakeModel')
+ assert authors.empty?
+ end
+
+ # has_many through
+ # Source: has_many
+ # Through: has_many through
+ def test_has_many_through_has_many_through_with_has_many_source_reflection
+ luke, david = subscribers(:first), subscribers(:second)
+ assert_equal [luke, david, david], authors(:david).subscribers.order('subscribers.nick')
+ end
+
+ def test_has_many_through_has_many_through_with_has_many_source_reflection_preload
+ luke, david = subscribers(:first), subscribers(:second)
+ authors = assert_queries(4) { Author.includes(:subscribers).to_a }
+ assert_no_queries do
+ assert_equal [luke, david, david], authors.first.subscribers.sort_by(&:nick)
+ end
+ end
+
+ def test_has_many_through_has_many_through_with_has_many_source_reflection_preload_via_joins
+ # All authors with subscribers where one of the subscribers' nick is 'alterself'
+ assert_includes_and_joins_equal(
+ Author.where('subscribers.nick' => 'alterself'),
+ [authors(:david)], :subscribers
+ )
+ end
+
+ # 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
+ end
+
+ def test_has_many_through_has_one_with_has_one_through_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:nested_member_types).to_a }
+ founding = member_types(:founding)
+ assert_no_queries do
+ assert_equal [founding], members.first.nested_member_types
+ end
+ end
+
+ def test_has_many_through_has_one_with_has_one_through_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('member_types.id' => member_types(:founding).id),
+ [members(:groucho)], :nested_member_types
+ )
+ end
+
+ # has_many through
+ # Source: has_one
+ # Through: has_one through
+ def test_has_many_through_has_one_through_with_has_one_source_reflection
+ assert_equal [sponsors(:moustache_club_sponsor_for_groucho)], members(:groucho).nested_sponsors
+ end
+
+ def test_has_many_through_has_one_through_with_has_one_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:nested_sponsors).to_a }
+ mustache = sponsors(:moustache_club_sponsor_for_groucho)
+ assert_no_queries do
+ assert_equal [mustache], members.first.nested_sponsors
+ end
+ end
+
+ def test_has_many_through_has_one_through_with_has_one_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('sponsors.id' => sponsors(:moustache_club_sponsor_for_groucho).id),
+ [members(:groucho)], :nested_sponsors
+ )
+ end
+
+ # has_many through
+ # Source: has_many through
+ # Through: has_one
+ def test_has_many_through_has_one_with_has_many_through_source_reflection
+ groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy)
+
+ assert_equal [groucho_details, other_details],
+ members(:groucho).organization_member_details.order('member_details.id')
+ end
+
+ def test_has_many_through_has_one_with_has_many_through_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:organization_member_details).to_a.sort_by(&:id) }
+ groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy)
+
+ assert_no_queries do
+ assert_equal [groucho_details, other_details], members.first.organization_member_details.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_has_one_with_has_many_through_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('member_details.id' => member_details(:groucho).id).order('member_details.id'),
+ [members(:groucho), members(:some_other_guy)], :organization_member_details
+ )
+
+ members = Member.joins(:organization_member_details).
+ where('member_details.id' => 9)
+ assert members.empty?
+ end
+
+ # has_many through
+ # Source: has_many
+ # Through: has_one through
+ def test_has_many_through_has_one_through_with_has_many_source_reflection
+ groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy)
+
+ assert_equal [groucho_details, other_details],
+ members(:groucho).organization_member_details_2.order('member_details.id')
+ end
+
+ def test_has_many_through_has_one_through_with_has_many_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:organization_member_details_2).to_a.sort_by(&:id) }
+ groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy)
+
+ assert_no_queries do
+ assert_equal [groucho_details, other_details], members.first.organization_member_details_2.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_has_one_through_with_has_many_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('member_details.id' => member_details(:groucho).id).order('member_details.id'),
+ [members(:groucho), members(:some_other_guy)], :organization_member_details_2
+ )
+
+ members = Member.joins(:organization_member_details_2).
+ where('member_details.id' => 9)
+ assert members.empty?
+ end
+
+ # has_many through
+ # Source: has_and_belongs_to_many
+ # Through: has_many
+ def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection
+ general, cooking = categories(:general), categories(:cooking)
+
+ assert_equal [general, cooking], authors(:bob).post_categories.order('categories.id')
+ end
+
+ def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload
+ authors = assert_queries(3) { Author.includes(:post_categories).to_a.sort_by(&:id) }
+ general, cooking = categories(:general), categories(:cooking)
+
+ assert_no_queries do
+ assert_equal [general, cooking], authors[2].post_categories.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Author.where('categories.id' => categories(:cooking).id),
+ [authors(:bob)], :post_categories
+ )
+ end
+
+ # has_many through
+ # Source: has_many
+ # Through: has_and_belongs_to_many
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection
+ greetings, more = comments(:greetings), comments(:more_greetings)
+
+ assert_equal [greetings, more], categories(:technology).post_comments.order('comments.id')
+ end
+
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload
+ categories = assert_queries(3) { Category.includes(:post_comments).to_a.sort_by(&:id) }
+ greetings, more = comments(:greetings), comments(:more_greetings)
+
+ assert_no_queries do
+ assert_equal [greetings, more], categories[1].post_comments.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Category.where('comments.id' => comments(:more_greetings).id).order('comments.id'),
+ [categories(:general), categories(:technology)], :post_comments
+ )
+ end
+
+ # has_many through
+ # Source: has_many through a habtm
+ # Through: has_many through
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection
+ greetings, more = comments(:greetings), comments(:more_greetings)
+
+ assert_equal [greetings, more], authors(:bob).category_post_comments.order('comments.id')
+ end
+
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload
+ authors = assert_queries(5) { Author.includes(:category_post_comments).to_a.sort_by(&:id) }
+ greetings, more = comments(:greetings), comments(:more_greetings)
+
+ assert_no_queries do
+ assert_equal [greetings, more], authors[2].category_post_comments.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Author.where('comments.id' => comments(:does_it_hurt).id).order('authors.id'),
+ [authors(:david), authors(:mary)], :category_post_comments
+ )
+ end
+
+ # has_many through
+ # Source: belongs_to
+ # Through: has_many through
+ def test_has_many_through_has_many_through_with_belongs_to_source_reflection
+ assert_equal [tags(:general), tags(:general)], authors(:david).tagging_tags
+ end
+
+ def test_has_many_through_has_many_through_with_belongs_to_source_reflection_preload
+ authors = assert_queries(5) { Author.includes(:tagging_tags).to_a }
+ general = tags(:general)
+
+ assert_no_queries do
+ assert_equal [general, general], authors.first.tagging_tags
+ end
+ end
+
+ def test_has_many_through_has_many_through_with_belongs_to_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Author.where('tags.id' => tags(:general).id),
+ [authors(:david)], :tagging_tags
+ )
+ end
+
+ # has_many through
+ # Source: has_many through
+ # Through: belongs_to
+ def test_has_many_through_belongs_to_with_has_many_through_source_reflection
+ welcome_general, thinking_general = taggings(:welcome_general), taggings(:thinking_general)
+
+ assert_equal [welcome_general, thinking_general],
+ categorizations(:david_welcome_general).post_taggings.order('taggings.id')
+ end
+
+ def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload
+ categorizations = assert_queries(4) { Categorization.includes(:post_taggings).to_a.sort_by(&:id) }
+ welcome_general, thinking_general = taggings(:welcome_general), taggings(:thinking_general)
+
+ assert_no_queries do
+ assert_equal [welcome_general, thinking_general], categorizations.first.post_taggings.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Categorization.where('taggings.id' => taggings(:welcome_general).id).order('taggings.id'),
+ [categorizations(:david_welcome_general)], :post_taggings
+ )
+ end
+
+ # 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
+ end
+
+ def test_has_one_through_has_one_with_has_one_through_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:nested_member_type).to_a.sort_by(&:id) }
+ founding = member_types(:founding)
+
+ assert_no_queries do
+ assert_equal founding, members.first.nested_member_type
+ end
+ end
+
+ def test_has_one_through_has_one_with_has_one_through_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('member_types.id' => member_types(:founding).id),
+ [members(:groucho)], :nested_member_type
+ )
+ end
+
+ # has_one through
+ # Source: belongs_to
+ # Through: has_one through
+ def test_has_one_through_has_one_through_with_belongs_to_source_reflection
+ assert_equal categories(:general), members(:groucho).club_category
+ end
+
+ def test_has_one_through_has_one_through_with_belongs_to_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:club_category).to_a.sort_by(&:id) }
+ general = categories(:general)
+
+ assert_no_queries do
+ assert_equal general, members.first.club_category
+ end
+ end
+
+ def test_has_one_through_has_one_through_with_belongs_to_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('categories.id' => categories(:technology).id),
+ [members(:blarpy_winkup)], :club_category
+ )
+ end
+
+ def test_distinct_has_many_through_a_has_many_through_association_on_source_reflection
+ author = authors(:david)
+ assert_equal [tags(:general)], author.distinct_tags
+ end
+
+ def test_distinct_has_many_through_a_has_many_through_association_on_through_reflection
+ author = authors(:david)
+ assert_equal [subscribers(:first), subscribers(:second)],
+ author.distinct_subscribers.order('subscribers.nick')
+ end
+
+ def test_nested_has_many_through_with_a_table_referenced_multiple_times
+ author = authors(:bob)
+ assert_equal [posts(:misc_by_bob), posts(:misc_by_mary), posts(:other_by_bob), posts(:other_by_mary)],
+ author.similar_posts.sort_by(&:id)
+
+ # Mary and Bob both have posts in misc, but they are the only ones.
+ authors = Author.joins(:similar_posts).where('posts.id' => posts(:misc_by_bob).id)
+ assert_equal [authors(:mary), authors(:bob)], authors.uniq.sort_by(&:id)
+
+ # Check the polymorphism of taggings is being observed correctly (in both joins)
+ authors = Author.joins(:similar_posts).where('taggings.taggable_type' => 'FakeModel')
+ assert authors.empty?
+ authors = Author.joins(:similar_posts).where('taggings_authors_join.taggable_type' => 'FakeModel')
+ assert authors.empty?
+ end
+
+ def test_has_many_through_with_foreign_key_option_on_through_reflection
+ assert_equal [posts(:welcome), posts(:authorless)], people(:david).agents_posts.order('posts.id')
+ assert_equal [authors(:david)], references(:david_unicyclist).agents_posts_authors
+
+ references = Reference.joins(:agents_posts_authors).where('authors.id' => authors(:david).id)
+ assert_equal [references(:david_unicyclist)], references
+ end
+
+ def test_has_many_through_with_foreign_key_option_on_source_reflection
+ assert_equal [people(:michael), people(:susan)], jobs(:unicyclist).agents.order('people.id')
+
+ jobs = Job.joins(:agents)
+ assert_equal [jobs(:unicyclist), jobs(:unicyclist)], jobs
+ end
+
+ def test_has_many_through_with_sti_on_through_reflection
+ ratings = posts(:sti_comments).special_comments_ratings.sort_by(&:id)
+ assert_equal [ratings(:special_comment_rating), ratings(:sub_special_comment_rating)], ratings
+
+ # Ensure STI is respected in the join
+ scope = Post.joins(:special_comments_ratings).where(:id => posts(:sti_comments).id)
+ assert scope.where("comments.type" => "Comment").empty?
+ assert !scope.where("comments.type" => "SpecialComment").empty?
+ assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ end
+
+ def test_has_many_through_with_sti_on_nested_through_reflection
+ taggings = posts(:sti_comments).special_comments_ratings_taggings
+ assert_equal [taggings(:special_comment_rating)], taggings
+
+ scope = Post.joins(:special_comments_ratings_taggings).where(:id => posts(:sti_comments).id)
+ assert scope.where("comments.type" => "Comment").empty?
+ assert !scope.where("comments.type" => "SpecialComment").empty?
+ end
+
+ def test_nested_has_many_through_writers_should_raise_error
+ david = authors(:david)
+ subscriber = subscribers(:first)
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers = [subscriber]
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscriber_ids = [subscriber.id]
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers << subscriber
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers.delete(subscriber)
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers.clear
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers.build
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers.create
+ end
+ end
+
+ def test_nested_has_one_through_writers_should_raise_error
+ groucho = members(:groucho)
+ founding = member_types(:founding)
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ groucho.nested_member_type = founding
+ end
+ end
+
+ def test_nested_has_many_through_with_conditions_on_through_associations
+ assert_equal [tags(:blue)], authors(:bob).misc_post_first_blue_tags
+ end
+
+ def test_nested_has_many_through_with_conditions_on_through_associations_preload
+ assert Author.where('tags.id' => 100).joins(:misc_post_first_blue_tags).empty?
+
+ authors = assert_queries(3) { Author.includes(:misc_post_first_blue_tags).to_a.sort_by(&:id) }
+ blue = tags(:blue)
+
+ assert_no_queries do
+ assert_equal [blue], authors[2].misc_post_first_blue_tags
+ end
+ end
+
+ def test_nested_has_many_through_with_conditions_on_through_associations_preload_via_joins
+ # Pointless condition to force single-query loading
+ assert_includes_and_joins_equal(
+ Author.where('tags.id = tags.id'),
+ [authors(:bob)], :misc_post_first_blue_tags
+ )
+ end
+
+ def test_nested_has_many_through_with_conditions_on_source_associations
+ assert_equal [tags(:blue)], authors(:bob).misc_post_first_blue_tags_2
+ end
+
+ def test_nested_has_many_through_with_conditions_on_source_associations_preload
+ authors = assert_queries(4) { Author.includes(:misc_post_first_blue_tags_2).to_a.sort_by(&:id) }
+ blue = tags(:blue)
+
+ assert_no_queries do
+ assert_equal [blue], authors[2].misc_post_first_blue_tags_2
+ end
+ end
+
+ def test_nested_has_many_through_with_conditions_on_source_associations_preload_via_joins
+ # Pointless condition to force single-query loading
+ assert_includes_and_joins_equal(
+ Author.where('tags.id = tags.id'),
+ [authors(:bob)], :misc_post_first_blue_tags_2
+ )
+ end
+
+ def test_nested_has_many_through_with_foreign_key_option_on_the_source_reflection_through_reflection
+ assert_equal [categories(:general)], organizations(:nsa).author_essay_categories
+
+ organizations = Organization.joins(:author_essay_categories).
+ where('categories.id' => categories(:general).id)
+ assert_equal [organizations(:nsa)], organizations
+
+ assert_equal categories(:general), organizations(:nsa).author_owned_essay_category
+
+ organizations = Organization.joins(:author_owned_essay_category).
+ where('categories.id' => categories(:general).id)
+ assert_equal [organizations(:nsa)], organizations
+ end
+
+ private
+
+ def assert_includes_and_joins_equal(query, expected, association)
+ actual = assert_queries(1) { query.joins(association).to_a.uniq }
+ assert_equal expected, actual
+
+ actual = assert_queries(1) { query.includes(association).to_a.uniq }
+ assert_equal expected, actual
+ end
+end