diff options
author | Emilio Tagua <miloops@gmail.com> | 2011-02-15 12:01:04 -0300 |
---|---|---|
committer | Emilio Tagua <miloops@gmail.com> | 2011-02-15 12:01:04 -0300 |
commit | 8ee0b4414890f919594c1a388d987b5b7364a505 (patch) | |
tree | 6c6c6aa19da0eb8066d2f0b9b02b08f2cc696c29 /activerecord/test/cases/associations | |
parent | 348c0ec7c656b3691aa4e687565d28259ca0f693 (diff) | |
parent | c9f1ab5365319e087e1b010a3f90626a2b8f080b (diff) | |
download | rails-8ee0b4414890f919594c1a388d987b5b7364a505.tar.gz rails-8ee0b4414890f919594c1a388d987b5b7364a505.tar.bz2 rails-8ee0b4414890f919594c1a388d987b5b7364a505.zip |
Merge remote branch 'rails/master' into identity_map
Conflicts:
activerecord/examples/performance.rb
activerecord/lib/active_record/association_preload.rb
activerecord/lib/active_record/associations.rb
activerecord/lib/active_record/associations/association_proxy.rb
activerecord/lib/active_record/autosave_association.rb
activerecord/lib/active_record/base.rb
activerecord/lib/active_record/nested_attributes.rb
activerecord/test/cases/relations_test.rb
Diffstat (limited to 'activerecord/test/cases/associations')
14 files changed, 801 insertions, 323 deletions
diff --git a/activerecord/test/cases/associations/association_proxy_test.rb b/activerecord/test/cases/associations/association_proxy_test.rb new file mode 100644 index 0000000000..55d8da4c4e --- /dev/null +++ b/activerecord/test/cases/associations/association_proxy_test.rb @@ -0,0 +1,52 @@ +require "cases/helper" + +module ActiveRecord + module Associations + class AsssociationProxyTest < ActiveRecord::TestCase + class FakeOwner + attr_accessor :new_record + alias :new_record? :new_record + + def initialize + @new_record = false + end + end + + class FakeReflection < Struct.new(:options, :klass) + def initialize options = {}, klass = nil + super + end + + def check_validity! + true + end + end + + class FakeTarget + end + + class FakeTargetProxy < AssociationProxy + def association_scope + true + end + + def find_target + FakeTarget.new + end + end + + def test_method_missing_error + reflection = FakeReflection.new({}, Object.new) + owner = FakeOwner.new + proxy = FakeTargetProxy.new(owner, reflection) + + exception = assert_raises(NoMethodError) do + proxy.omg + end + + assert_match('omg', exception.message) + assert_match(FakeTarget.name, exception.message) + end + end + end +end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 1820f95261..01073bca3d 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -17,7 +17,7 @@ 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 @@ -78,27 +78,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase 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_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_not_nil citibank_result.instance_variable_get("@firm_with_primary_key_symbols") - 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 + assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols) end def test_creating_the_belonging_object @@ -133,6 +120,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 @@ -159,6 +163,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 @@ -175,17 +188,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) @@ -198,16 +200,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 @@ -311,13 +320,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 @@ -419,6 +436,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") @@ -488,39 +517,116 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_reassigning_the_parent_id_updates_the_object - original_parent = Firm.create! :name => "original" - updated_parent = Firm.create! :name => "updated" + client = companies(:second_client) - client = Client.new("client_of" => original_parent.id) - assert_equal original_parent, client.firm - assert_equal original_parent, client.firm_with_condition - assert_equal original_parent, client.firm_with_other_name + 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) - client.client_of = updated_parent.id - assert_equal updated_parent, client.firm - assert_equal updated_parent, client.firm_with_condition - assert_equal updated_parent, client.firm_with_other_name + 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 - member1 = Member.create! - member2 = Member.create! + 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 = Sponsor.new("sponsorable_type" => "Member", "sponsorable_id" => member1.id) - assert_equal member1, sponsor.sponsorable + sponsor.sponsorable_id = members(:some_other_guy).id - sponsor.sponsorable_id = member2.id - assert_equal member2, sponsor.sponsorable + assert proxy.stale_target? + assert_equal members(:some_other_guy), sponsor.sponsorable end def test_polymorphic_reassignment_of_associated_type_updates_the_object - member1 = Member.create! + 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.firm # note this is a proxy object + + client.firm = companies(:another_firm) + assert_equal companies(:another_firm), firm.reload + + client.client_of = companies(:first_firm).id + assert_equal companies(:first_firm), firm.reload + 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 = Sponsor.new("sponsorable_type" => "Member", "sponsorable_id" => member1.id) - assert_equal member1, sponsor.sponsorable + sponsor.sponsorable = groucho - sponsor.sponsorable_type = "Firm" - assert_not_equal member1, sponsor.sponsorable + 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 6a30e2905b..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 @@ -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) 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 c064731859..650bb15b55 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -21,12 +21,13 @@ 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, :categorizations, :owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books, - :developers, :projects, :developers_projects, :members, :memberships, :clubs + :developers, :projects, :developers_projects, :members, :memberships, :clubs, :sponsors def setup # preheat table existence caches @@ -211,6 +212,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 @@ -659,7 +669,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 @@ -845,6 +859,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 @@ -890,26 +906,40 @@ 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(1) { 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 @@ -917,4 +947,14 @@ class EagerAssociationTest < ActiveRecord::TestCase 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 efaab8569e..e1f5b16eca 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -29,6 +29,11 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_and_block.find_most_recent 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) 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 705550216c..dc382c3007 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, :OracleAdapter) - 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,34 +186,6 @@ 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 @@ -425,38 +365,41 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_removing_associations_on_destroy david = DeveloperWithBeforeDestroyRaise.find(1) assert !david.projects.empty? - assert_raise(RuntimeError) { 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 @@ -675,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 @@ -840,10 +771,13 @@ 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 diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index fb772bb8e6..ad774eb9ce 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -43,7 +43,7 @@ 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 @@ -66,14 +66,36 @@ class HasManyAssociationsTest < ActiveRecord::TestCase 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 @@ -257,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 @@ -608,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 @@ -616,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") @@ -628,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 @@ -649,11 +690,12 @@ 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 @@ -664,9 +706,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase 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 @@ -963,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 @@ -1302,4 +1357,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase 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 77bc369ecc..73b1d6c500 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -19,10 +19,14 @@ require 'models/book' require 'models/subscription' require 'models/categorization' require 'models/category' +require 'models/essay' +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, + fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags, + :owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses, :subscribers, :books, :subscriptions, :developers, :categorizations # Dummies to force column loads so query counts are clean. @@ -31,6 +35,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) } @@ -45,6 +56,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 @@ -107,8 +128,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? @@ -116,8 +139,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? @@ -130,6 +155,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)} @@ -324,12 +480,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 @@ -397,6 +549,34 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase 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 @@ -466,7 +646,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_with_default_scope_on_join_model - assert_equal posts(:welcome).comments, authors(:david).comments_on_first_posts + 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 @@ -479,4 +659,63 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase 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). + where('categorizations.id' => categorizations(:mary_thinking_sti).id) + + assert_equal [posts(:eager_other)], 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 end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 64449df8f5..6b73a35905 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 @@ -91,18 +95,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 +120,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 @@ -193,13 +168,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) - firm.build_account("credit_limit" => 1000) - 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 + + bulb = pirate.build_bulb + assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize - assert_equal count_of_account, Account.count + 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 @@ -208,25 +188,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 @@ -330,4 +317,51 @@ class HasOneAssociationsTest < ActiveRecord::TestCase 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.target + + 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.target + + 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 93a4f498d0..fc1db4e34b 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -25,10 +25,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") @@ -88,6 +84,18 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase 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).favourite_club.reload + + # 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).hairy_club.reload + end + def test_has_one_through_polymorphic_with_source_type assert_equal members(:groucho), clubs(:moustache_club).sponsored_member end @@ -189,7 +197,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_proxy, :member_type).loaded? assert_not_nil assert_no_queries { @new_detail.member_type } end @@ -235,6 +243,49 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_has_one_through_with_default_scope_on_join_model - assert_equal posts(:welcome).comments.first, authors(:david).comment_on_first_posts + 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/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index da2a81e98a..e2228228a3 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -4,6 +4,7 @@ require 'models/comment' require 'models/author' require 'models/category' require 'models/categorization' +require 'models/person' require 'models/tagging' require 'models/tag' @@ -16,6 +17,13 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase 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) diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 0491b2d1fa..e9a57a00a0 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" @@ -146,39 +146,6 @@ class InverseHasOneTests < ActiveRecord::TestCase 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) - 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_replaced_via_accessor_child m = Man.find(:first) f = Face.new(:description => 'haunted') @@ -203,18 +170,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_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 +212,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 +224,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') diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index d6e772e989..34f57d3155 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -13,7 +13,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 @@ -85,13 +86,6 @@ 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 assert_nothing_raised(NoMethodError) { tag.author_id } @@ -340,11 +334,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 @@ -512,6 +511,10 @@ 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 10, author.comments.size @@ -520,7 +523,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase 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 |