aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/associations/has_many_associations_test.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test/cases/associations/has_many_associations_test.rb')
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb300
1 files changed, 246 insertions, 54 deletions
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 5f01352ab4..21a45042fa 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1,11 +1,13 @@
require "cases/helper"
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/company'
require 'models/contract'
require 'models/topic'
require 'models/reply'
require 'models/category'
+require 'models/image'
require 'models/post'
require 'models/author'
require 'models/essay'
@@ -28,6 +30,9 @@ require 'models/college'
require 'models/student'
require 'models/pirate'
require 'models/ship'
+require 'models/tyre'
+require 'models/subscriber'
+require 'models/subscription'
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
fixtures :authors, :posts, :comments
@@ -36,16 +41,63 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa
author = authors(:david)
# this can fail on adapters which require ORDER BY expressions to be included in the SELECT expression
# if the reorder clauses are not correctly handled
- assert author.posts_with_comments_sorted_by_comment_id.where('comments.id > 0').reorder('posts.comments_count DESC', 'posts.taggings_count DESC').last
+ assert author.posts_with_comments_sorted_by_comment_id.where('comments.id > 0').reorder('posts.comments_count DESC', 'posts.tags_count DESC').last
end
end
+class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
+ fixtures :authors, :essays, :subscribers, :subscriptions, :people
+
+ def test_custom_primary_key_on_new_record_should_fetch_with_query
+ subscriber = Subscriber.new(nick: 'webster132')
+ assert !subscriber.subscriptions.loaded?
+
+ assert_queries 1 do
+ assert_equal 2, subscriber.subscriptions.size
+ end
+
+ assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: 'webster132')
+ end
+
+ def test_association_primary_key_on_new_record_should_fetch_with_query
+ author = Author.new(:name => "David")
+ assert !author.essays.loaded?
+
+ assert_queries 1 do
+ assert_equal 1, author.essays.size
+ end
+
+ assert_equal author.essays, Essay.where(writer_id: "David")
+ end
+
+ def test_has_many_custom_primary_key
+ david = authors(:david)
+ assert_equal david.essays, Essay.where(writer_id: "David")
+ end
+
+ def test_has_many_assignment_with_custom_primary_key
+ david = people(:david)
+
+ assert_equal ["A Modest Proposal"], david.essays.map(&:name)
+ david.essays = [Essay.create!(name: "Remote Work" )]
+ assert_equal ["Remote Work"], david.essays.map(&:name)
+ end
+
+ def test_blank_custom_primary_key_on_new_record_should_not_run_queries
+ author = Author.new
+ assert !author.essays.loaded?
+
+ assert_queries 0 do
+ assert_equal 0, author.essays.size
+ end
+ end
+end
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments,
- :people, :posts, :readers, :taggings, :cars, :essays,
- :categorizations, :jobs, :tags
+ :posts, :readers, :taggings, :cars, :jobs, :tags,
+ :categorizations
def setup
Client.destroyed_client_ids.clear
@@ -75,6 +127,32 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
dev.developer_projects.map(&:project_id).sort
end
+ def test_default_scope_on_relations_is_not_cached
+ counter = 0
+ posts = Class.new(ActiveRecord::Base) {
+ self.table_name = 'posts'
+ self.inheritance_column = 'not_there'
+ post = self
+
+ comments = Class.new(ActiveRecord::Base) {
+ self.table_name = 'comments'
+ self.inheritance_column = 'not_there'
+ belongs_to :post, :class => post
+ default_scope -> {
+ counter += 1
+ where("id = :inc", :inc => counter)
+ }
+ }
+ has_many :comments, :class => comments, :foreign_key => 'post_id'
+ }
+ assert_equal 0, counter
+ post = posts.first
+ assert_equal 0, counter
+ sql = capture_sql { post.comments.to_a }
+ post.comments.reset
+ assert_not_equal sql, capture_sql { post.comments.to_a }
+ end
+
def test_has_many_build_with_options
college = College.create(name: 'UFMT')
Student.create(active: true, college_id: college.id, name: 'Sarah')
@@ -386,6 +464,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "Summit", Firm.all.merge!(:order => "id").first.clients_using_primary_key.first.name
end
+ def test_update_all_on_association_accessed_before_save
+ firm = Firm.new(name: 'Firm')
+ firm.clients << Client.first
+ firm.save!
+ assert_equal firm.clients.count, firm.clients.update_all(description: 'Great!')
+ end
+
def test_belongs_to_sanity
c = Client.new
assert_nil c.firm, "belongs_to failed sanity check on new object"
@@ -554,17 +639,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_create_with_bang_on_has_many_when_parent_is_new_raises
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
firm = Firm.new
firm.plain_clients.create! :name=>"Whoever"
end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_regular_create_on_has_many_when_parent_is_new_raises
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
firm = Firm.new
firm.plain_clients.create :name=>"Whoever"
end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_create_with_bang_on_has_many_raises_when_record_not_saved
@@ -575,9 +664,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_create_with_bang_on_habtm_when_parent_is_new_raises
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
Developer.new("name" => "Aredridel").projects.create!
end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_adding_a_mismatch_class
@@ -606,7 +697,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transactions_when_adding_to_new_record
- assert_no_queries do
+ assert_no_queries(ignore_none: false) do
firm = Firm.new
firm.clients_of_firm.concat(Client.new("name" => "Natural Company"))
end
@@ -621,7 +712,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
company = companies(:first_firm)
- new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") }
+ new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.new("name" => "Another Client") }
assert !company.clients_of_firm.loaded?
assert_equal "Another Client", new_client.name
@@ -631,7 +722,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build
company = companies(:first_firm)
- new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
+ new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
assert !company.clients_of_firm.loaded?
assert_equal "Another Client", new_client.name
@@ -667,7 +758,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many
company = companies(:first_firm)
- new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
+ new_clients = assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
assert_equal 2, new_clients.size
end
@@ -693,7 +784,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_via_block
company = companies(:first_firm)
- new_client = assert_no_queries { company.clients_of_firm.build {|client| client.name = "Another Client" } }
+ new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build {|client| client.name = "Another Client" } }
assert !company.clients_of_firm.loaded?
assert_equal "Another Client", new_client.name
@@ -703,7 +794,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many_via_block
company = companies(:first_firm)
- new_clients = assert_no_queries do
+ new_clients = assert_no_queries(ignore_none: false) do
company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
client.name = "changed"
end
@@ -772,6 +863,36 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal topic.replies.to_a.size, topic.replies_count
end
+ def test_counter_cache_updates_in_memory_after_concat
+ topic = Topic.create title: "Zoom-zoom-zoom"
+
+ topic.replies << Reply.create(title: "re: zoom", content: "speedy quick!")
+ assert_equal 1, topic.replies_count
+ assert_equal 1, topic.replies.size
+ assert_equal 1, topic.reload.replies.size
+ end
+
+ def test_counter_cache_updates_in_memory_after_create
+ topic = Topic.create title: "Zoom-zoom-zoom"
+
+ topic.replies.create!(title: "re: zoom", content: "speedy quick!")
+ assert_equal 1, topic.replies_count
+ assert_equal 1, topic.replies.size
+ assert_equal 1, topic.reload.replies.size
+ end
+
+ def test_counter_cache_updates_in_memory_after_create_with_array
+ topic = Topic.create title: "Zoom-zoom-zoom"
+
+ topic.replies.create!([
+ { title: "re: zoom", content: "speedy quick!" },
+ { title: "re: zoom 2", content: "OMG lol!" },
+ ])
+ assert_equal 2, topic.replies_count
+ assert_equal 2, topic.replies.size
+ assert_equal 2, topic.reload.replies.size
+ end
+
def test_pushing_association_updates_counter_cache
topic = Topic.order("id ASC").first
reply = Reply.create!
@@ -784,14 +905,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_updates_counter_cache_without_dependent_option
post = posts(:welcome)
- assert_difference "post.reload.taggings_count", -1 do
+ assert_difference "post.reload.tags_count", -1 do
post.taggings.delete(post.taggings.first)
end
end
def test_deleting_updates_counter_cache_with_dependent_delete_all
post = posts(:welcome)
- post.update_columns(taggings_with_delete_all_count: post.taggings_count)
+ post.update_columns(taggings_with_delete_all_count: post.tags_count)
assert_difference "post.reload.taggings_with_delete_all_count", -1 do
post.taggings_with_delete_all.delete(post.taggings_with_delete_all.first)
@@ -800,13 +921,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_updates_counter_cache_with_dependent_destroy
post = posts(:welcome)
- post.update_columns(taggings_with_destroy_count: post.taggings_count)
+ post.update_columns(taggings_with_destroy_count: post.tags_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_calling_empty_with_counter_cache
+ post = posts(:welcome)
+ assert_queries(0) do
+ assert_not post.comments.empty?
+ end
+ end
+
def test_custom_named_counter_cache
topic = topics(:first)
@@ -891,7 +1019,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transaction_when_deleting_new_record
- assert_no_queries do
+ assert_no_queries(ignore_none: false) do
firm = Firm.new
client = Client.new("name" => "New Client")
firm.clients_of_firm << client
@@ -1141,7 +1269,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !clients.empty?, "37signals has clients after load"
destroyed = companies(:first_firm).clients_of_firm.destroy_all
assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id)
- assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
+ assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all"
assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
end
@@ -1281,10 +1409,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !account.valid?
assert !orig_accounts.empty?
- assert_raise ActiveRecord::RecordNotSaved do
+ error = assert_raise ActiveRecord::RecordNotSaved do
firm.accounts = [account]
end
+
assert_equal orig_accounts, firm.accounts
+ assert_equal "Failed to replace accounts because one or more of the " \
+ "new records could not be saved.", error.message
end
def test_replace_with_same_content
@@ -1312,7 +1443,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transactions_when_replacing_on_new_record
- assert_no_queries do
+ assert_no_queries(ignore_none: false) do
firm = Firm.new
firm.clients_of_firm = [Client.new("name" => "New Client")]
end
@@ -1450,7 +1581,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.load_target
assert firm.clients.loaded?
- assert_no_queries do
+ assert_no_queries(ignore_none: false) do
firm.clients.first
assert_equal 2, firm.clients.first(2).size
firm.clients.last
@@ -1496,39 +1627,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- def test_custom_primary_key_on_new_record_should_fetch_with_query
- author = Author.new(:name => "David")
- assert !author.essays.loaded?
-
- assert_queries 1 do
- assert_equal 1, author.essays.size
- end
-
- assert_equal author.essays, Essay.where(writer_id: "David")
- end
-
- def test_has_many_custom_primary_key
- david = authors(:david)
- assert_equal david.essays, Essay.where(writer_id: "David")
- end
-
- def test_has_many_assignment_with_custom_primary_key
- david = people(:david)
-
- assert_equal ["A Modest Proposal"], david.essays.map(&:name)
- david.essays = [Essay.create!(name: "Remote Work" )]
- assert_equal ["Remote Work"], david.essays.map(&:name)
- end
-
- def test_blank_custom_primary_key_on_new_record_should_not_run_queries
- author = Author.new
- assert !author.essays.loaded?
-
- assert_queries 0 do
- assert_equal 0, author.essays.size
- end
- end
-
def test_calling_first_or_last_with_integer_on_association_should_not_load_association
firm = companies(:first_firm)
firm.clients.create(:name => 'Foo')
@@ -1702,6 +1800,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [tagging], post.taggings
end
+ def test_with_polymorphic_has_many_with_custom_columns_name
+ post = Post.create! :title => 'foo', :body => 'bar'
+ image = Image.create!
+
+ post.images << image
+
+ assert_equal [image], post.images
+ end
+
def test_build_with_polymorphic_has_many_does_not_allow_to_override_type_and_id
welcome = posts(:welcome)
tagging = welcome.taggings.build(:taggable_id => 99, :taggable_type => 'ShouldNotChange')
@@ -1798,7 +1905,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "has many associations on new records use null relations" do
post = Post.new
- assert_no_queries do
+ assert_no_queries(ignore_none: false) do
assert_equal [], post.comments
assert_equal [], post.comments.where(body: 'omg')
assert_equal [], post.comments.pluck(:body)
@@ -1857,6 +1964,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb1, bulb2], car.all_bulbs.sort_by(&:id)
end
+ test 'unscopes the default scope of associated model when used with include' do
+ car = Car.create!
+ bulb = Bulb.create! name: "other", car: car
+
+ assert_equal bulb, Car.find(car.id).all_bulbs.first
+ assert_equal bulb, Car.includes(:all_bulbs).find(car.id).all_bulbs.first
+ end
+
test "raises RecordNotDestroyed when replaced child can't be destroyed" do
car = Car.create!
original_child = FailedBulb.create!(car: car)
@@ -1904,4 +2019,81 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [], authors(:david).posts_with_signature.map(&:title)
end
+
+ test 'associations autosaves when object is already persited' do
+ bulb = Bulb.create!
+ tyre = Tyre.create!
+
+ car = Car.create! do |c|
+ c.bulbs << bulb
+ c.tyres << tyre
+ end
+
+ assert_equal 1, car.bulbs.count
+ assert_equal 1, car.tyres.count
+ end
+
+ test 'associations replace in memory when records have the same id' do
+ bulb = Bulb.create!
+ car = Car.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+ new_bulb.name = "foo"
+ car.bulbs = [new_bulb]
+
+ assert_equal "foo", car.bulbs.first.name
+ end
+
+ test 'in memory replacement executes no queries' do
+ bulb = Bulb.create!
+ car = Car.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+
+ assert_no_queries do
+ car.bulbs = [new_bulb]
+ end
+ end
+
+ test 'in memory replacements do not execute callbacks' do
+ raise_after_add = false
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = :cars
+ has_many :bulbs, after_add: proc { raise if raise_after_add }
+
+ def self.name
+ "Car"
+ end
+ end
+ bulb = Bulb.create!
+ car = klass.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+ raise_after_add = true
+
+ assert_nothing_raised do
+ car.bulbs = [new_bulb]
+ end
+ end
+
+ test 'in memory replacements sets inverse instance' do
+ bulb = Bulb.create!
+ car = Car.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+ car.bulbs = [new_bulb]
+
+ assert_same car, new_bulb.car
+ end
+
+ test 'in memory replacement maintains order' do
+ first_bulb = Bulb.create!
+ second_bulb = Bulb.create!
+ car = Car.create!(bulbs: [first_bulb, second_bulb])
+
+ same_bulb = Bulb.find(first_bulb.id)
+ car.bulbs = [second_bulb, same_bulb]
+
+ assert_equal [first_bulb, second_bulb], car.bulbs
+ end
end