diff options
Diffstat (limited to 'activerecord/test/cases/associations')
21 files changed, 702 insertions, 244 deletions
diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb deleted file mode 100644 index c322333f6d..0000000000 --- a/activerecord/test/cases/associations/association_scope_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require "cases/helper" -require "models/post" -require "models/author" - -module ActiveRecord - module Associations - class AssociationScopeTest < ActiveRecord::TestCase - test "does not duplicate conditions" do - scope = AssociationScope.scope(Author.new.association(:welcome_posts), - Author.connection) - binds = scope.where_clause.binds.map(&:value) - assert_equal binds.uniq, binds - 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 5875a1871f..0f7a249bf3 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/developer" require "models/project" @@ -116,6 +118,44 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase ActiveRecord::Base.belongs_to_required_by_default = original_value end + def test_default + david = developers(:david) + jamis = developers(:jamis) + + model = Class.new(ActiveRecord::Base) do + self.table_name = "ships" + def self.name; "Temp"; end + belongs_to :developer, default: -> { david } + end + + ship = model.create! + assert_equal david, ship.developer + + ship = model.create!(developer: jamis) + assert_equal jamis, ship.developer + + ship.update!(developer: nil) + assert_equal david, ship.developer + end + + def test_default_with_lambda + model = Class.new(ActiveRecord::Base) do + self.table_name = "ships" + def self.name; "Temp"; end + belongs_to :developer, default: -> { default_developer } + + def default_developer + Developer.first + end + end + + ship = model.create! + assert_equal developers(:david), ship.developer + + ship = model.create!(developer: developers(:jamis)) + assert_equal developers(:jamis), ship.developer + end + def test_default_scope_on_relations_is_not_cached counter = 0 @@ -177,6 +217,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase remove_column :admin_users, :region_id if column_exists?(:admin_users, :region_id) drop_table :admin_regions, if_exists: true end + + Admin::User.reset_column_information end def test_natural_assignment @@ -342,7 +384,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase sponsor.sponsorable = Member.new name: "Bert" assert_equal Member, sponsor.association(:sponsorable).send(:klass) - assert_equal "members", sponsor.association(:sponsorable).aliased_table_name end def test_with_polymorphic_and_condition @@ -610,8 +651,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_new_record_with_foreign_key_but_no_object client = Client.new("firm_id" => 1) - # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - assert_equal Firm.all.merge!(order: "id").first, client.firm_with_basic_id + assert_equal Firm.first, client.firm_with_basic_id end def test_setting_foreign_key_after_nil_target_loaded @@ -1131,6 +1171,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase Column.create! record: record assert_equal 1, Column.count end + + def test_multiple_counter_cache_with_after_create_update + post = posts(:welcome) + parent = comments(:greetings) + + assert_difference "parent.reload.children_count", +1 do + assert_difference "post.reload.comments_count", +1 do + CommentWithAfterCreateUpdate.create(body: "foo", post: post, parent: parent) + end + end + end end class BelongsToWithForeignKeyTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb index 8a0e041864..88221b012e 100644 --- a/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb +++ b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/content" diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index 5fd2411f6f..e096cd4a0b 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/post" require "models/author" @@ -7,7 +9,7 @@ require "models/computer" require "models/company" class AssociationCallbacksTest < ActiveRecord::TestCase - fixtures :posts, :authors, :projects, :developers + fixtures :posts, :authors, :author_addresses, :projects, :developers def setup @david = authors(:david) @@ -128,7 +130,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase assert ar.developers_log.empty? alice = Developer.new(name: "alice") ar.developers_with_callbacks << alice - assert_equal"after_adding#{alice.id}", ar.developers_log.last + assert_equal "after_adding#{alice.id}", ar.developers_log.last bob = ar.developers_with_callbacks.create(name: "bob") assert_equal "after_adding#{bob.id}", ar.developers_log.last diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index ddb5c7a4aa..e717621928 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/post" require "models/comment" @@ -12,7 +14,7 @@ require "models/vertex" require "models/edge" class CascadedEagerLoadingTest < ActiveRecord::TestCase - fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, + fixtures :authors, :author_addresses, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations, :people, :categories, :edges, :vertices def test_eager_association_loading_with_cascaded_two_levels @@ -34,18 +36,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations - assert_nothing_raised do - Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a - end authors = Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a - assert_equal 1, assert_no_queries { authors.size } + assert_equal 3, assert_no_queries { authors.size } assert_equal 10, assert_no_queries { authors[0].comments.size } end def test_eager_association_loading_grafts_stashed_associations_to_correct_parent - assert_nothing_raised do - Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").to_a - end assert_equal people(:michael), Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").first end @@ -140,7 +136,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_multiple_stis_and_order - author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first + author = Author.all.merge!(includes: { posts: [ :special_comments, :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first assert_equal authors(:david), author assert_no_queries do author.posts.first.special_comments 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 4f0fe3236e..8754889143 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 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/post" require "models/tagging" @@ -11,25 +13,32 @@ end class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase def setup - generate_test_objects - end - - def generate_test_objects post = Namespaced::Post.create(title: "Great stuff", body: "This is not", author_id: 1) - Tagging.create(taggable: post) + @tagging = Tagging.create(taggable: post) + @old = ActiveRecord::Base.store_full_sti_class end - def test_class_names - old = ActiveRecord::Base.store_full_sti_class + def teardown + ActiveRecord::Base.store_full_sti_class = @old + end + def test_class_names_with_includes ActiveRecord::Base.store_full_sti_class = false post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff") assert_nil post.tagging ActiveRecord::Base.store_full_sti_class = true post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff") - assert_instance_of Tagging, post.tagging - ensure - ActiveRecord::Base.store_full_sti_class = old + assert_equal @tagging, post.tagging + end + + def test_class_names_with_eager_load + ActiveRecord::Base.store_full_sti_class = false + post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff") + assert_nil post.tagging + + ActiveRecord::Base.store_full_sti_class = true + post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff") + assert_equal @tagging, post.tagging end end 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 e9f551b6b2..c5b2b77bd4 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/post" require "models/tag" diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb index 16eff15026..420a5a805b 100644 --- a/activerecord/test/cases/associations/eager_singularization_test.rb +++ b/activerecord/test/cases/associations/eager_singularization_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class EagerSingularizationTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 11f4aae5b3..9830917bc3 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/post" require "models/tagging" @@ -38,6 +40,12 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_nil member.favourite_club end + def test_should_work_inverse_of_with_eager_load + author = authors(:david) + assert_same author, author.posts.first.author + assert_same author, author.posts.eager_load(:comments).first.author + end + def test_loading_with_one_association posts = Post.all.merge!(includes: :comments).to_a post = posts.find { |p| p.id == 1 } @@ -68,6 +76,11 @@ class EagerAssociationTest < ActiveRecord::TestCase "expected to find only david's posts" end + def test_loading_with_scope_including_joins + assert_equal clubs(:boring_club), Member.preload(:general_club).find(1).general_club + assert_equal clubs(:boring_club), Member.eager_load(:general_club).find(1).general_club + end + def test_with_ordering list = Post.all.merge!(includes: :comments, order: "posts.id DESC").to_a [:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other, @@ -271,9 +284,6 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_from_an_association_that_has_a_hash_of_conditions - assert_nothing_raised do - Author.all.merge!(includes: :hello_posts_with_hash_conditions).to_a - end assert !Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty? end @@ -417,7 +427,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name quoted_posts_id = Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id") assert_nothing_raised do - Comment.includes(:post).references(:posts).order(quoted_posts_id) + Comment.includes(:post).references(:posts).order(Arel.sql(quoted_posts_id)) end end @@ -520,6 +530,14 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments } end + def test_preloading_has_many_through_with_implicit_source + authors = Author.includes(:very_special_comments).to_a + assert_no_queries do + special_comment_authors = authors.map { |author| [author.name, author.very_special_comments.size] } + assert_equal [["David", 1], ["Mary", 0], ["Bob", 0]], special_comment_authors + end + end + def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both author = Author.all.merge!(includes: :special_nonexistent_post_comments, order: "authors.id").first assert_equal [], author.special_nonexistent_post_comments @@ -769,7 +787,7 @@ class EagerAssociationTest < ActiveRecord::TestCase Tagging.create!(taggable_type: "Post", taggable_id: post2.id, tag: tag) tag_with_includes = OrderedTag.includes(:tagged_posts).find(tag.id) - assert_equal(tag_with_includes.taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title)) + assert_equal tag_with_includes.ordered_taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title) end def test_eager_has_many_through_multiple_with_order @@ -851,23 +869,19 @@ class EagerAssociationTest < ActiveRecord::TestCase end end - def find_all_ordered(className, include = nil) - className.all.merge!(order: "#{className.table_name}.#{className.primary_key}", includes: include).to_a - end - def test_limited_eager_with_order assert_equal( posts(:thinking, :sti_comments), Post.all.merge!( includes: [:author, :comments], where: { "authors.name" => "David" }, - order: "UPPER(posts.title)", limit: 2, offset: 1 + order: Arel.sql("UPPER(posts.title)"), limit: 2, offset: 1 ).to_a ) assert_equal( posts(:sti_post_and_comments, :sti_comments), Post.all.merge!( includes: [:author, :comments], where: { "authors.name" => "David" }, - order: "UPPER(posts.title) DESC", limit: 2, offset: 1 + order: Arel.sql("UPPER(posts.title) DESC"), limit: 2, offset: 1 ).to_a ) end @@ -877,14 +891,14 @@ class EagerAssociationTest < ActiveRecord::TestCase posts(:thinking, :sti_comments), Post.all.merge!( includes: [:author, :comments], where: { "authors.name" => "David" }, - order: ["UPPER(posts.title)", "posts.id"], limit: 2, offset: 1 + order: [Arel.sql("UPPER(posts.title)"), "posts.id"], limit: 2, offset: 1 ).to_a ) assert_equal( posts(:sti_post_and_comments, :sti_comments), Post.all.merge!( includes: [:author, :comments], where: { "authors.name" => "David" }, - order: ["UPPER(posts.title) DESC", "posts.id"], limit: 2, offset: 1 + order: [Arel.sql("UPPER(posts.title) DESC"), "posts.id"], limit: 2, offset: 1 ).to_a ) end @@ -1059,7 +1073,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_load_with_sti_sharing_association - assert_queries(2) do #should not do 1 query per subclass + assert_queries(2) do # should not do 1 query per subclass Comment.includes(:post).to_a end end @@ -1094,12 +1108,6 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal authors(:david), assert_no_queries { posts[0].author } posts = assert_queries(2) do - Post.all.merge!(select: "distinct posts.*", includes: :author, joins: [:comments], where: "comments.body like 'Thank you%'", order: "posts.id").to_a - end - assert_equal [posts(:welcome)], posts - assert_equal authors(:david), assert_no_queries { posts[0].author } - - posts = assert_queries(2) do Post.all.merge!(includes: :author, joins: { taggings: :tag }, where: "tags.name = 'General'", order: "posts.id").to_a end assert_equal posts(:welcome, :thinking), posts @@ -1295,6 +1303,11 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal projects.last.mentor.developers.first.contracts, projects.last.developers.last.contracts end + def test_preloading_has_many_through_with_custom_scope + project = Project.includes(:developers_named_david_with_hash_conditions).find(projects(:active_record).id) + assert_equal [developers(:david)], project.developers_named_david_with_hash_conditions + end + test "scoping with a circular preload" do assert_equal Comment.find(1), Comment.preload(post: :comments).scoping { Comment.find(1) } end @@ -1363,6 +1376,7 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_nothing_raised do authors(:david).essays.includes(:writer).any? authors(:david).essays.includes(:writer).exists? + authors(:david).essays.includes(:owner).where("name IS NOT NULL").exists? end end @@ -1487,9 +1501,18 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal posts(:welcome), post end + test "eager-loading with a polymorphic association and using the existential predicate" do + assert_equal true, authors(:david).essays.eager_load(:writer).exists? + end + # CollectionProxy#reader is expensive, so the preloader avoids calling it. test "preloading has_many_through association avoids calling association.reader" do ActiveRecord::Associations::HasManyAssociation.any_instance.expects(:reader).never Author.preload(:readonly_comments).first! end + + private + def find_all_ordered(klass, include = nil) + klass.order("#{klass.table_name}.#{klass.primary_key}").includes(include).to_a + end end diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index 974a3080d4..5eacb5a3d8 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/post" require "models/comment" @@ -36,6 +38,11 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase assert_equal comments(:greetings), posts(:welcome).comments.not_again.find_most_recent end + def test_extension_with_dirty_target + comment = posts(:welcome).comments.build(body: "New comment") + assert_equal comment, posts(:welcome).comments.with_content("New comment") + end + def test_marshalling_extensions david = developers(:david) assert_equal projects(:action_controller), david.projects.find_most_recent @@ -73,6 +80,12 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase assert_equal post.association(:comments), post.comments.where("1=1").the_association end + def test_association_with_default_scope + assert_raises OopsError do + posts(:welcome).comments.destroy_all + end + end + private def extend!(model) 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 d6b595d7e7..c817d7267b 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 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/developer" require "models/computer" @@ -86,12 +88,6 @@ class DeveloperWithSymbolClassName < Developer has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys end -ActiveSupport::Deprecation.silence do - class DeveloperWithConstantClassName < Developer - has_and_belongs_to_many :projects, class_name: ProjectWithSymbolsForKeys - end -end - class DeveloperWithExtendOption < Developer module NamedExtension def category @@ -111,6 +107,21 @@ class ProjectUnscopingDavidDefaultScope < ActiveRecord::Base association_foreign_key: "developer_id" end +class Kitchen < ActiveRecord::Base + has_one :sink +end + +class Sink < ActiveRecord::Base + has_and_belongs_to_many :sources, join_table: :edges + belongs_to :kitchen + accepts_nested_attributes_for :kitchen +end + +class Source < ActiveRecord::Base + self.table_name = "men" + has_and_belongs_to_many :sinks, join_table: :edges +end + class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects, :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers @@ -352,19 +363,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase 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) - devel.projects.build(name: "Make bed") - proj2 = devel.projects.build(name: "Lie in it") - assert_equal devel.projects.last, proj2 - assert !proj2.persisted? - devel.save - 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 - def test_creation_respects_hash_condition # in Oracle '' is saved as null therefore need to save ' ' in not null column post = categories(:general).post_with_conditions.build(body: " ") @@ -939,7 +937,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_not_nil Developer._reflections["shared_computers"] # Checking the fixture for named association is important here, because it's the only way # we've been able to reproduce this bug - assert_not_nil File.read(File.expand_path("../../../fixtures/developers.yml", __FILE__)).index("shared_computers") + assert_not_nil File.read(File.expand_path("../../fixtures/developers.yml", __dir__)).index("shared_computers") assert_equal developers(:david).shared_computers.first, computers(:laptop) end @@ -950,13 +948,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end end - def test_with_constant_class_name - assert_nothing_raised do - developer = DeveloperWithConstantClassName.new - developer.projects - end - end - def test_alternate_database professor = Professor.create(name: "Plum") course = Course.create(name: "Forensics") @@ -1021,4 +1012,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase ActiveRecord::Base.partial_writes = original_partial_writes end end + + def test_has_and_belongs_to_many_with_belongs_to + sink = Sink.create! kitchen: Kitchen.new, sources: [Source.new] + assert_equal 1, sink.sources.count + 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 ede3a44090..18548f8516 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/developer" require "models/computer" @@ -40,7 +42,7 @@ require "models/zine" require "models/interest" class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase - fixtures :authors, :posts, :comments + fixtures :authors, :author_addresses, :posts, :comments def test_should_generate_valid_sql author = authors(:david) @@ -51,7 +53,7 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa end class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase - fixtures :authors, :essays, :subscribers, :subscriptions, :people + fixtures :authors, :author_addresses, :essays, :subscribers, :subscriptions, :people def test_custom_primary_key_on_new_record_should_fetch_with_query subscriber = Subscriber.new(nick: "webster132") @@ -61,7 +63,7 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase assert_equal 2, subscriber.subscriptions.size end - assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: "webster132") + assert_equal Subscription.where(subscriber_id: "webster132"), subscriber.subscriptions end def test_association_primary_key_on_new_record_should_fetch_with_query @@ -72,12 +74,23 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase assert_equal 1, author.essays.size end - assert_equal author.essays, Essay.where(writer_id: "David") + assert_equal Essay.where(writer_id: "David"), author.essays end def test_has_many_custom_primary_key david = authors(:david) - assert_equal david.essays, Essay.where(writer_id: "David") + assert_equal Essay.where(writer_id: "David"), david.essays + end + + def test_ids_on_unloaded_association_with_custom_primary_key + david = people(:david) + assert_equal Essay.where(writer_id: "David").pluck(:id), david.essay_ids + end + + def test_ids_on_loaded_association_with_custom_primary_key + david = people(:david) + david.essays.load + assert_equal Essay.where(writer_id: "David").pluck(:id), david.essay_ids end def test_has_many_assignment_with_custom_primary_key @@ -100,7 +113,7 @@ end class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, - :developers_projects, :topics, :authors, :comments, + :developers_projects, :topics, :authors, :author_addresses, :comments, :posts, :readers, :taggings, :cars, :jobs, :tags, :categorizations, :zines, :interests @@ -241,6 +254,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal "defaulty", bulb.name end + def test_build_from_association_sets_inverse_instance + car = Car.new(name: "honda") + + bulb = car.bulbs.build + assert_equal car, bulb.car + end + def test_do_not_call_callbacks_for_delete_all car = Car.create(name: "honda") car.funky_bulbs.create! @@ -456,7 +476,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_create_resets_cached_counters + Reader.delete_all + person = Person.create!(first_name: "tenderlove") + post = Post.first assert_equal [], person.readers @@ -490,21 +513,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_predicate person.references, :exists? end - # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql - assert_equal 3, Firm.all.merge!(order: "id").first.clients.count + assert_equal 3, Firm.first.clients.count end def test_counting - assert_equal 3, Firm.all.merge!(order: "id").first.plain_clients.count + assert_equal 3, Firm.first.plain_clients.count end def test_counting_with_single_hash - assert_equal 1, Firm.all.merge!(order: "id").first.plain_clients.where(name: "Microsoft").count + assert_equal 1, Firm.first.plain_clients.where(name: "Microsoft").count end def test_counting_with_column_name_and_hash - assert_equal 3, Firm.all.merge!(order: "id").first.plain_clients.count(:name) + assert_equal 3, Firm.first.plain_clients.count(:name) end def test_counting_with_association_limit @@ -514,7 +536,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding - assert_equal 3, Firm.all.merge!(order: "id").first.clients.length + assert_equal 3, Firm.first.clients.length end def test_finding_array_compatibility @@ -554,8 +576,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_taking_with_a_number + klass = Class.new(Author) do + has_many :posts, -> { order(:id) } + + def self.name + "Author" + end + end + # taking from unloaded Relation - bob = Author.find(authors(:bob).id) + bob = klass.find(authors(:bob).id) new_post = bob.posts.build assert_not bob.posts.loaded? assert_equal [posts(:misc_by_bob)], bob.posts.take(1) @@ -586,27 +616,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding_default_orders - assert_equal "Summit", Firm.all.merge!(order: "id").first.clients.first.name + assert_equal "Summit", Firm.first.clients.first.name end def test_finding_with_different_class_name_and_order - assert_equal "Apex", Firm.all.merge!(order: "id").first.clients_sorted_desc.first.name + assert_equal "Apex", Firm.first.clients_sorted_desc.first.name end def test_finding_with_foreign_key - assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_of_firm.first.name + assert_equal "Microsoft", Firm.first.clients_of_firm.first.name end def test_finding_with_condition - assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_like_ms.first.name + assert_equal "Microsoft", Firm.first.clients_like_ms.first.name end def test_finding_with_condition_hash - assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_like_ms_with_hash_conditions.first.name + assert_equal "Microsoft", Firm.first.clients_like_ms_with_hash_conditions.first.name end def test_finding_using_primary_key - assert_equal "Summit", Firm.all.merge!(order: "id").first.clients_using_primary_key.first.name + assert_equal "Summit", Firm.first.clients_using_primary_key.first.name end def test_update_all_on_association_accessed_before_save @@ -629,7 +659,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_ids - firm = Firm.all.merge!(order: "id").first + firm = Firm.first assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find } @@ -649,7 +679,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_one_message_on_primary_key - firm = Firm.all.merge!(order: "id").first + firm = Firm.first e = assert_raises(ActiveRecord::RecordNotFound) do firm.clients.find(0) @@ -675,7 +705,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all - firm = Firm.all.merge!(order: "id").first + firm = Firm.first assert_equal 3, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length assert_equal 1, firm.clients.where("name = 'Summit'").to_a.length end @@ -720,25 +750,59 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all_sanitized - # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - firm = Firm.all.merge!(order: "id").first + firm = Firm.first summit = firm.clients.where("name = 'Summit'").to_a assert_equal summit, firm.clients.where("name = ?", "Summit").to_a assert_equal summit, firm.clients.where("name = :name", name: "Summit").to_a end def test_find_first - firm = Firm.all.merge!(order: "id").first + firm = Firm.first client2 = Client.find(2) assert_equal firm.clients.first, firm.clients.order("id").first assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").order("id").first end def test_find_first_sanitized - firm = Firm.all.merge!(order: "id").first + firm = Firm.first client2 = Client.find(2) - assert_equal client2, firm.clients.merge!(where: ["#{QUOTED_TYPE} = ?", "Client"], order: "id").first - assert_equal client2, firm.clients.merge!(where: ["#{QUOTED_TYPE} = :type", { type: "Client" }], order: "id").first + assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = ?", "Client").first + assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = :type", type: "Client").first + end + + def test_find_first_after_reset_scope + firm = Firm.first + collection = firm.clients + + original_object = collection.first + assert_same original_object, collection.first, "Expected second call to #first to cache the same object" + + # It should return a different object, since the association has been reloaded + assert_not_same original_object, firm.clients.first, "Expected #first to return a new object" + end + + def test_find_first_after_reset + firm = Firm.first + collection = firm.clients + + original_object = collection.first + assert_same original_object, collection.first, "Expected second call to #first to cache the same object" + collection.reset + + # It should return a different object, since the association has been reloaded + assert_not_same original_object, collection.first, "Expected #first after #reset to return a new object" + end + + def test_find_first_after_reload + firm = Firm.first + collection = firm.clients + + original_object = collection.first + assert_same original_object, collection.first, "Expected second call to #first to cache the same object" + collection.reload + + # It should return a different object, since the association has been reloaded + assert_not_same original_object, collection.first, "Expected #first after #reload to return a new object" end def test_find_all_with_include_and_conditions @@ -767,7 +831,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_scoped_grouped_having - assert_equal 1, authors(:david).popular_grouped_posts.length + assert_equal 2, authors(:david).popular_grouped_posts.length assert_equal 0, authors(:mary).popular_grouped_posts.length end @@ -783,6 +847,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [1], posts(:welcome).comments.select { |c| c.id == 1 }.map(&:id) end + def test_select_with_block_and_dirty_target + assert_equal 2, posts(:welcome).comments.select { true }.size + posts(:welcome).comments.build + assert_equal 3, posts(:welcome).comments.select { true }.size + end + def test_select_without_foreign_key assert_equal companies(:first_firm).accounts.first.credit_limit, companies(:first_firm).accounts.select(:credit_limit).first.credit_limit end @@ -827,7 +897,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_with_bang_on_has_many_raises_when_record_not_saved assert_raise(ActiveRecord::RecordInvalid) do - firm = Firm.all.merge!(order: "id").first + firm = Firm.first firm.plain_clients.create! end end @@ -1349,7 +1419,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Client.create(client_of: firm.id, name: "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key assert_equal 2, Client.where(client_of: firm.id).size - assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size + assert_equal 1, firm.dependent_hash_conditional_clients_of_firm.size firm.destroy # only the correctly associated client should have been deleted assert_equal 1, Client.where(client_of: firm.id).size @@ -1509,8 +1579,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_destroy_dependent_when_deleted_from_association - # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - firm = Firm.all.merge!(order: "id").first + firm = Firm.first assert_equal 3, firm.clients.size client = firm.clients.first @@ -1620,7 +1689,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_less - firm = Firm.all.merge!(order: "id").first + firm = Firm.first firm.clients = [companies(:first_client)] assert firm.save, "Could not save firm" firm.reload @@ -1634,7 +1703,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_new - firm = Firm.all.merge!(order: "id").first + firm = Firm.first firm.clients = [companies(:second_client), Client.new("name" => "New Client")] firm.save firm.reload @@ -1897,7 +1966,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_many_on_loaded_association_should_not_use_query firm = companies(:first_firm) - firm.clients.collect # force load + firm.clients.load # force load assert_no_queries { assert firm.clients.many? } end @@ -1936,7 +2005,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_none_on_loaded_association_should_not_use_query firm = companies(:first_firm) - firm.clients.collect # force load + firm.clients.load # force load assert_no_queries { assert ! firm.clients.none? } end @@ -1971,7 +2040,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_one_on_loaded_association_should_not_use_query firm = companies(:first_firm) - firm.clients.collect # force load + firm.clients.load # force load assert_no_queries { assert ! firm.clients.one? } end @@ -2031,14 +2100,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal client_association.new.attributes, client_association.send(:new).attributes end - def test_respond_to_private_class_methods - client_association = companies(:first_firm).clients - assert !client_association.respond_to?(:private_method) - assert client_association.respond_to?(:private_method, true) - end - def test_creating_using_primary_key - firm = Firm.all.merge!(order: "id").first + firm = Firm.first client = firm.clients_using_primary_key.create!(name: "test") assert_equal firm.name, client.firm_name end @@ -2140,6 +2203,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal "Post", tagging.taggable_type end + def test_build_from_polymorphic_association_sets_inverse_instance + post = Post.new + tagging = post.taggings.build + + assert_equal post, tagging.taggable + end + def test_dont_call_save_callbacks_twice_on_has_many firm = companies(:first_firm) contract = firm.contracts.create! @@ -2251,7 +2321,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase test "association with extend option with multiple extensions" do post = posts(:welcome) assert_equal "lifo", post.comments_with_extend_2.author - assert_equal "hello", post.comments_with_extend_2.greeting + assert_equal "hullo", post.comments_with_extend_2.greeting + end + + test "extend option affects per association" do + post = posts(:welcome) + assert_equal "lifo", post.comments_with_extend.author + assert_equal "lifo", post.comments_with_extend_2.author + assert_equal "hello", post.comments_with_extend.greeting + assert_equal "hullo", post.comments_with_extend_2.greeting end test "delete record with complex joins" do @@ -2272,7 +2350,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase test "does not duplicate associations when used with natural primary keys" do speedometer = Speedometer.create!(id: "4") - speedometer.minivans.create!(minivan_id: "a-van-red" , name: "a van", color: "red") + speedometer.minivans.create!(minivan_id: "a-van-red", name: "a van", color: "red") assert_equal 1, speedometer.minivans.to_a.size, "Only one association should be present:\n#{speedometer.minivans.to_a}" assert_equal 1, speedometer.reload.minivans.to_a.size @@ -2311,8 +2389,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase 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 + assert_equal [bulb], Car.find(car.id).all_bulbs + assert_equal [bulb], Car.includes(:all_bulbs).find(car.id).all_bulbs + assert_equal [bulb], Car.eager_load(:all_bulbs).find(car.id).all_bulbs end test "raises RecordNotDestroyed when replaced child can't be destroyed" do @@ -2430,6 +2509,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_same car, new_bulb.car end + test "reattach to new objects replaces inverse association and foreign key" do + bulb = Bulb.create!(car: Car.create!) + assert bulb.car_id + car = Car.new + car.bulbs << bulb + assert_equal car, bulb.car + assert_nil bulb.car_id + end + test "in memory replacement maintains order" do first_bulb = Bulb.create! second_bulb = Bulb.create! @@ -2441,15 +2529,37 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [first_bulb, second_bulb], car.bulbs end - test "double insertion of new object to association when same association used in the after create callback of a new object" do + test "association size calculation works with default scoped selects when not previously fetched" do + firm = Firm.create!(name: "Firm") + 5.times { firm.developers_with_select << Developer.create!(name: "Developer") } + + same_firm = Firm.find(firm.id) + assert_equal 5, same_firm.developers_with_select.size + end + + test "prevent double insertion of new object when the parent association loaded in the after save callback" do reset_callbacks(:save, Bulb) do Bulb.after_save { |record| record.car.bulbs.load } + car = Car.create! car.bulbs << Bulb.new + assert_equal 1, car.bulbs.size end end + test "prevent double firing the before save callback of new object when the parent association saved in the callback" do + reset_callbacks(:save, Bulb) do + count = 0 + Bulb.before_save { |record| record.car.save && count += 1 } + + car = Car.create! + car.bulbs.create! + + assert_equal 1, count + end + end + class AuthorWithErrorDestroyingAssociation < ActiveRecord::Base self.table_name = "authors" has_many :posts_with_error_destroying, @@ -2486,11 +2596,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [bulb.id], car.bulb_ids assert_no_queries { car.bulb_ids } + + bulb2 = car.bulbs.create! + + assert_equal [bulb.id, bulb2.id], car.bulb_ids + assert_no_queries { car.bulb_ids } end def test_loading_association_in_validate_callback_doesnt_affect_persistence reset_callbacks(:validation, Bulb) do - Bulb.after_validation { |m| m.car.bulbs.load } + Bulb.after_validation { |record| record.car.bulbs.load } car = Car.create!(name: "Car") bulb = car.bulbs.create! 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 ea52fb5a67..56a4b7c4d1 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/post" require "models/person" @@ -64,10 +66,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase club1.members.sort_by(&:id) end - def make_model(name) - Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } - end - def test_ordered_has_many_through person_prime = Class.new(ActiveRecord::Base) do def self.name; "Person"; end @@ -152,20 +150,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert after_destroy_called, "after destroy should be called" end - def make_no_pk_hm_t - lesson = make_model "Lesson" - student = make_model "Student" - - lesson_student = make_model "LessonStudent" - lesson_student.table_name = "lessons_students" - - lesson_student.belongs_to :lesson, anonymous_class: lesson - lesson_student.belongs_to :student, anonymous_class: student - lesson.has_many :lesson_students, anonymous_class: lesson_student - lesson.has_many :students, through: :lesson_students, anonymous_class: student - [lesson, lesson_student, student] - end - def test_pk_is_not_required_for_join post = Post.includes(:scategories).first post2 = Post.includes(:categories).first @@ -337,6 +321,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_includes post.single_people, person end + def test_build_then_remove_then_save + post = posts(:thinking) + post.people.build(first_name: "Bob") + ted = post.people.build(first_name: "Ted") + post.people.delete(ted) + post.save! + post.reload + + assert_equal ["Bob"], post.people.collect(&:first_name) + end + def test_both_parent_ids_set_when_saving_new post = Post.new(title: "Hello", body: "world") person = Person.new(first_name: "Sean") @@ -822,7 +817,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase 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.flat_map(&:agents), people(:susan).agents_of_agents + assert_equal people(:susan).agents.flat_map(&:agents).sort, people(:susan).agents_of_agents.sort end def test_associate_existing_with_nonstandard_primary_key_on_belongs_to @@ -874,6 +869,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal [dev], company.developers end + def test_collection_singular_ids_setter_with_required_type_cast + company = companies(:rails_core) + dev = Developer.first + + company.developer_ids = [dev.id.to_s] + assert_equal [dev], company.developers + end + def test_collection_singular_ids_setter_with_string_primary_keys assert_nothing_raised do book = books(:awdr) @@ -885,32 +888,20 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end - def test_collection_singular_ids_setter_with_changed_primary_key - company = companies(:first_firm) - client = companies(:first_client) - company.clients_using_primary_key_ids = [client.name] - assert_equal [client], company.clients_using_primary_key - end - def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set company = companies(:rails_core) ids = [Developer.first.id, -9999] e = assert_raises(ActiveRecord::RecordNotFound) { company.developer_ids = ids } - assert_match(/Couldn't find all Developers with 'id'/, e.message) - end - - def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set_with_changed_primary_key - company = companies(:first_firm) - ids = [Client.first.name, "unknown client"] - e = assert_raises(ActiveRecord::RecordNotFound) { company.clients_using_primary_key_ids = ids } - assert_match(/Couldn't find all Clients with 'name'/, e.message) + msg = "Couldn't find all Developers with 'id': (1, -9999) (found 1 results, but was looking for 2). Couldn't find Developer with id -9999." + assert_equal(msg, e.message) end def test_collection_singular_ids_through_setter_raises_exception_when_invalid_ids_set author = authors(:david) ids = [categories(:general).name, "Unknown"] e = assert_raises(ActiveRecord::RecordNotFound) { author.essay_category_ids = ids } - assert_equal "Couldn't find all Categories with 'name': (General, Unknown) (found 1 results, but was looking for 2)", e.message + msg = "Couldn't find all Categories with 'name': (General, Unknown) (found 1 results, but was looking for 2). Couldn't find Category with name Unknown." + assert_equal msg, e.message end def test_build_a_model_from_hm_through_association_with_where_clause @@ -953,6 +944,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + def test_has_many_through_polymorphic_with_rewhere + post = TaggedPost.create!(title: "Tagged", body: "Post") + tag = post.tags.create!(name: "Tag") + assert_equal [tag], TaggedPost.preload(:tags).last.tags + assert_equal [tag], TaggedPost.eager_load(:tags).last.tags + end + def test_has_many_through_polymorphic_with_primary_key_option assert_equal [categories(:general)], authors(:david).essay_categories @@ -1135,6 +1133,32 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name } end + def test_has_many_through_associations_sum_on_columns + post1 = Post.create(title: "active", body: "sample") + post2 = Post.create(title: "inactive", body: "sample") + + person1 = Person.create(first_name: "aaron", followers_count: 1) + person2 = Person.create(first_name: "schmit", followers_count: 2) + person3 = Person.create(first_name: "bill", followers_count: 3) + person4 = Person.create(first_name: "cal", followers_count: 4) + + Reader.create(post_id: post1.id, person_id: person1.id) + Reader.create(post_id: post1.id, person_id: person2.id) + Reader.create(post_id: post1.id, person_id: person3.id) + Reader.create(post_id: post1.id, person_id: person4.id) + + Reader.create(post_id: post2.id, person_id: person1.id) + Reader.create(post_id: post2.id, person_id: person2.id) + Reader.create(post_id: post2.id, person_id: person3.id) + Reader.create(post_id: post2.id, person_id: person4.id) + + active_persons = Person.joins(:readers).joins(:posts).distinct(true).where("posts.title" => "active") + + assert_equal active_persons.map(&:followers_count).reduce(:+), 10 + assert_equal active_persons.sum(:followers_count), 10 + assert_equal active_persons.sum(:followers_count), active_persons.map(&:followers_count).reduce(:+) + end + def test_has_many_through_associations_on_new_records_use_null_relations person = Person.new @@ -1234,6 +1258,18 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase TenantMembership.current_member = nil end + def test_has_many_through_with_scope_that_has_joined_same_table_with_parent_relation + assert_equal authors(:david), Author.joins(:comments_for_first_author).take + end + + def test_has_many_through_with_unscope_should_affect_to_through_scope + assert_equal [comments(:eager_other_comment1)], authors(:mary).unordered_comments + end + + def test_has_many_through_with_scope_should_accept_string_and_hash_join + assert_equal authors(:david), Author.joins({ comments_for_first_author: :post }, "inner join posts posts_alias on authors.id = posts_alias.author_id").eager_load(:categories).take + end + def test_has_many_through_with_scope_should_respect_table_alias family = Family.create! users = 3.times.map { User.create! } @@ -1245,6 +1281,25 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal 0, users[2].family_members.to_a.size end + def test_through_scope_is_affected_by_unscoping + author = authors(:david) + + expected = author.comments.to_a + FirstPost.unscoped do + assert_equal expected.sort_by(&:id), author.comments_on_first_posts.sort_by(&:id) + end + end + + def test_through_scope_isnt_affected_by_scoping + author = authors(:david) + + expected = author.comments_on_first_posts.to_a + FirstPost.where(id: 2).scoping do + author.comments_on_first_posts.reset + assert_equal expected.sort_by(&:id), author.comments_on_first_posts.sort_by(&:id) + end + end + def test_incorrectly_ordered_through_associations assert_raises(ActiveRecord::HasManyThroughOrderError) do DeveloperWithIncorrectlyOrderedHasManyThrough.create( @@ -1252,4 +1307,87 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase ) end end + + def test_has_many_through_update_ids_with_conditions + author = Author.create!(name: "Bill") + category = categories(:general) + + author.update( + special_categories_with_condition_ids: [category.id], + nonspecial_categories_with_condition_ids: [category.id] + ) + + assert_equal [category.id], author.special_categories_with_condition_ids + assert_equal [category.id], author.nonspecial_categories_with_condition_ids + + author.update(nonspecial_categories_with_condition_ids: []) + author.reload + + assert_equal [category.id], author.special_categories_with_condition_ids + assert_equal [], author.nonspecial_categories_with_condition_ids + end + + def test_single_has_many_through_association_with_unpersisted_parent_instance + post_with_single_has_many_through = Class.new(Post) do + def self.name; "PostWithSingleHasManyThrough"; end + has_many :subscriptions, through: :author + end + post = post_with_single_has_many_through.new + + post.author = authors(:mary) + book1 = Book.create!(name: "essays on single has many through associations 1") + post.author.books << book1 + subscription1 = Subscription.first + book1.subscriptions << subscription1 + assert_equal [subscription1], post.subscriptions.to_a + + post.author = authors(:bob) + book2 = Book.create!(name: "essays on single has many through associations 2") + post.author.books << book2 + subscription2 = Subscription.second + book2.subscriptions << subscription2 + assert_equal [subscription2], post.subscriptions.to_a + end + + def test_nested_has_many_through_association_with_unpersisted_parent_instance + post_with_nested_has_many_through = Class.new(Post) do + def self.name; "PostWithNestedHasManyThrough"; end + has_many :books, through: :author + has_many :subscriptions, through: :books + end + post = post_with_nested_has_many_through.new + + post.author = authors(:mary) + book1 = Book.create!(name: "essays on nested has many through associations 1") + post.author.books << book1 + subscription1 = Subscription.first + book1.subscriptions << subscription1 + assert_equal [subscription1], post.subscriptions.to_a + + post.author = authors(:bob) + book2 = Book.create!(name: "essays on nested has many through associations 2") + post.author.books << book2 + subscription2 = Subscription.second + book2.subscriptions << subscription2 + assert_equal [subscription2], post.subscriptions.to_a + end + + private + def make_model(name) + Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } + end + + def make_no_pk_hm_t + lesson = make_model "Lesson" + student = make_model "Student" + + lesson_student = make_model "LessonStudent" + lesson_student.table_name = "lessons_students" + + lesson_student.belongs_to :lesson, anonymous_class: lesson + lesson_student.belongs_to :student, anonymous_class: student + lesson.has_many :lesson_students, anonymous_class: lesson_student + lesson.has_many :students, through: :lesson_students, anonymous_class: student + [lesson, lesson_student, student] + 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 7c11d2e7fc..ec5d95080b 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/developer" require "models/computer" @@ -13,7 +15,7 @@ require "models/post" class HasOneAssociationsTest < ActiveRecord::TestCase self.use_transactional_tests = false unless supports_savepoints? - fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates + fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates, :authors, :author_addresses def setup Account.destroyed_account_ids.clear @@ -28,7 +30,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase ActiveRecord::SQLCounter.clear_log companies(:first_firm).account ensure - assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query" + log_all = ActiveRecord::SQLCounter.log_all + assert log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{log_all}" end def test_has_one_cache_nils @@ -307,6 +310,15 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end end + def test_create_when_parent_is_new_raises + firm = Firm.new + error = assert_raise(ActiveRecord::RecordNotSaved) do + firm.create_account + end + + assert_equal "You cannot call create unless the parent is saved", error.message + end + def test_reload_association odegy = companies(:odegy) @@ -679,4 +691,38 @@ class HasOneAssociationsTest < ActiveRecord::TestCase SpecialAuthor.joins(book: :subscription).where.not(where_clause) end end + + class DestroyByParentBook < ActiveRecord::Base + self.table_name = "books" + belongs_to :author, class_name: "DestroyByParentAuthor" + before_destroy :dont, unless: :destroyed_by_association + + def dont + throw(:abort) + end + end + + class DestroyByParentAuthor < ActiveRecord::Base + self.table_name = "authors" + has_one :book, class_name: "DestroyByParentBook", foreign_key: "author_id", dependent: :destroy + end + + test "destroyed_by_association set in child destroy callback on parent destroy" do + author = DestroyByParentAuthor.create!(name: "Test") + book = DestroyByParentBook.create!(author: author) + + author.destroy + + assert_not DestroyByParentBook.exists?(book.id) + end + + test "destroyed_by_association set in child destroy callback on replace" do + author = DestroyByParentAuthor.create!(name: "Test") + book = DestroyByParentBook.create!(author: author) + + author.book = DestroyByParentBook.create! + author.save! + + assert_not DestroyByParentBook.exists?(book.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 38a729d2d4..1d37457464 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/club" require "models/member_type" @@ -23,7 +25,7 @@ require "models/customer_carrier" class HasOneThroughAssociationsTest < ActiveRecord::TestCase fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, - :dashboards, :speedometers, :authors, :posts, :comments, :categories, :essays, :owners + :dashboards, :speedometers, :authors, :author_addresses, :posts, :comments, :categories, :essays, :owners def setup @member = members(:groucho) @@ -98,7 +100,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_has_one_through_eager_loading - members = assert_queries(3) do #base table, through table, clubs table + members = assert_queries(3) do # base table, through table, clubs table Member.all.merge!(includes: :club, where: ["name = ?", "Groucho Marx"]).to_a end assert_equal 1, members.size @@ -106,7 +108,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_has_one_through_eager_loading_through_polymorphic - members = assert_queries(3) do #base table, through table, clubs table + members = assert_queries(3) do # base table, through table, clubs table Member.all.merge!(includes: :sponsor_club, where: ["name = ?", "Groucho Marx"]).to_a end assert_equal 1, members.size @@ -137,7 +139,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eagerloading members = assert_queries(1) do - Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback + Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a # force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].club } @@ -145,7 +147,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic members = assert_queries(1) do - Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback + Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a # force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } @@ -154,7 +156,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record Sponsor.new(sponsor_club: clubs(:crazy_club), sponsorable: members(:groucho)).save! members = assert_queries(1) do - Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a #force fallback + Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a # force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index 7414869c8f..7be875fec6 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/post" require "models/comment" @@ -10,7 +12,7 @@ require "models/tagging" require "models/tag" class InnerJoinAssociationTest < ActiveRecord::TestCase - fixtures :authors, :essays, :posts, :comments, :categories, :categories_posts, :categorizations, + fixtures :authors, :author_addresses, :essays, :posts, :comments, :categories, :categories_posts, :categorizations, :taggings, :tags def test_construct_finder_sql_applies_aliases_tables_on_association_conditions @@ -25,6 +27,24 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase end end + def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations_with_left_outer_joins + sql = Person.joins(agents: :agents).left_outer_joins(agents: :agents).to_sql + assert_match(/agents_people_4/i, sql) + end + + def test_construct_finder_sql_does_not_table_name_collide_with_string_joins + sql = Person.joins(:agents).joins("JOIN people agents_people ON agents_people.primary_contact_id = people.id").to_sql + assert_match(/agents_people_2/i, sql) + end + + def test_construct_finder_sql_does_not_table_name_collide_with_aliased_joins + people = Person.arel_table + agents = people.alias("agents_people") + constraint = agents[:primary_contact_id].eq(people[:id]) + sql = Person.joins(:agents).joins(agents.create_join(agents, agents.create_on(constraint))).to_sql + assert_match(/agents_people_2/i, sql) + 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 287b3e9ebc..c0d328ca8a 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/man" require "models/face" @@ -16,6 +18,8 @@ require "models/admin/user" require "models/developer" require "models/company" require "models/project" +require "models/author" +require "models/post" class AutomaticInverseFindingTests < ActiveRecord::TestCase fixtures :ratings, :comments, :cars @@ -24,11 +28,9 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase monkey_reflection = MixedCaseMonkey.reflect_on_association(:man) man_reflection = Man.reflect_on_association(:mixed_case_monkey) - assert_respond_to monkey_reflection, :has_inverse? assert monkey_reflection.has_inverse?, "The monkey reflection should have an inverse" assert_equal man_reflection, monkey_reflection.inverse_of, "The monkey reflection's inverse should be the man reflection" - assert_respond_to man_reflection, :has_inverse? assert man_reflection.has_inverse?, "The man reflection should have an inverse" assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection" end @@ -37,7 +39,6 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase account_reflection = Admin::Account.reflect_on_association(:users) user_reflection = Admin::User.reflect_on_association(:account) - assert_respond_to account_reflection, :has_inverse? assert account_reflection.has_inverse?, "The Admin::Account reflection should have an inverse" assert_equal user_reflection, account_reflection.inverse_of, "The Admin::Account reflection's inverse should be the Admin::User reflection" end @@ -46,11 +47,9 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase car_reflection = Car.reflect_on_association(:bulb) bulb_reflection = Bulb.reflect_on_association(:car) - assert_respond_to car_reflection, :has_inverse? assert car_reflection.has_inverse?, "The Car reflection should have an inverse" assert_equal bulb_reflection, car_reflection.inverse_of, "The Car reflection's inverse should be the Bulb reflection" - assert_respond_to bulb_reflection, :has_inverse? assert bulb_reflection.has_inverse?, "The Bulb reflection should have an inverse" assert_equal car_reflection, bulb_reflection.inverse_of, "The Bulb reflection's inverse should be the Car reflection" end @@ -59,11 +58,24 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase comment_reflection = Comment.reflect_on_association(:ratings) rating_reflection = Rating.reflect_on_association(:comment) - assert_respond_to comment_reflection, :has_inverse? assert comment_reflection.has_inverse?, "The Comment reflection should have an inverse" assert_equal rating_reflection, comment_reflection.inverse_of, "The Comment reflection's inverse should be the Rating reflection" end + def test_has_many_and_belongs_to_should_find_inverse_automatically_for_sti + author_reflection = Author.reflect_on_association(:posts) + author_child_reflection = Author.reflect_on_association(:special_posts) + post_reflection = Post.reflect_on_association(:author) + + assert_respond_to author_reflection, :has_inverse? + assert author_reflection.has_inverse?, "The Author reflection should have an inverse" + assert_equal post_reflection, author_reflection.inverse_of, "The Author reflection's inverse should be the Post reflection" + + assert_respond_to author_child_reflection, :has_inverse? + assert author_child_reflection.has_inverse?, "The Author reflection should have an inverse" + assert_equal post_reflection, author_child_reflection.inverse_of, "The Author reflection's inverse should be the Post reflection" + end + def test_has_one_and_belongs_to_automatic_inverse_shares_objects car = Car.first bulb = Bulb.create!(car: car) @@ -107,24 +119,17 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase def test_polymorphic_and_has_many_through_relationships_should_not_have_inverses sponsor_reflection = Sponsor.reflect_on_association(:sponsorable) - assert_respond_to sponsor_reflection, :has_inverse? assert !sponsor_reflection.has_inverse?, "A polymorphic association should not find an inverse automatically" club_reflection = Club.reflect_on_association(:members) - assert_respond_to club_reflection, :has_inverse? assert !club_reflection.has_inverse?, "A has_many_through association should not find an inverse automatically" end - def test_polymorphic_relationships_should_still_not_have_inverses_when_non_polymorphic_relationship_has_the_same_name + def test_polymorphic_has_one_should_find_inverse_automatically man_reflection = Man.reflect_on_association(:polymorphic_face_without_inverse) - face_reflection = Face.reflect_on_association(:man) - assert_respond_to face_reflection, :has_inverse? - assert face_reflection.has_inverse?, "For this test, the non-polymorphic association must have an inverse" - - assert_respond_to man_reflection, :has_inverse? - assert !man_reflection.has_inverse?, "The target of a polymorphic association should not find an inverse automatically" + assert man_reflection.has_inverse? end end @@ -145,41 +150,24 @@ class InverseAssociationTests < ActiveRecord::TestCase def test_should_be_able_to_ask_a_reflection_if_it_has_an_inverse has_one_with_inverse_ref = Man.reflect_on_association(:face) - assert_respond_to has_one_with_inverse_ref, :has_inverse? assert has_one_with_inverse_ref.has_inverse? has_many_with_inverse_ref = Man.reflect_on_association(:interests) - assert_respond_to has_many_with_inverse_ref, :has_inverse? assert has_many_with_inverse_ref.has_inverse? belongs_to_with_inverse_ref = Face.reflect_on_association(:man) - assert_respond_to belongs_to_with_inverse_ref, :has_inverse? assert belongs_to_with_inverse_ref.has_inverse? has_one_without_inverse_ref = Club.reflect_on_association(:sponsor) - assert_respond_to has_one_without_inverse_ref, :has_inverse? assert !has_one_without_inverse_ref.has_inverse? has_many_without_inverse_ref = Club.reflect_on_association(:memberships) - assert_respond_to has_many_without_inverse_ref, :has_inverse? assert !has_many_without_inverse_ref.has_inverse? belongs_to_without_inverse_ref = Sponsor.reflect_on_association(:sponsor_club) - assert_respond_to belongs_to_without_inverse_ref, :has_inverse? assert !belongs_to_without_inverse_ref.has_inverse? end - def test_should_be_able_to_ask_a_reflection_what_it_is_the_inverse_of - has_one_ref = Man.reflect_on_association(:face) - assert_respond_to has_one_ref, :inverse_of - - has_many_ref = Man.reflect_on_association(:interests) - assert_respond_to has_many_ref, :inverse_of - - belongs_to_ref = Face.reflect_on_association(:man) - assert_respond_to belongs_to_ref, :inverse_of - end - def test_inverse_of_method_should_supply_the_actual_reflection_instance_it_is_the_inverse_of has_one_ref = Man.reflect_on_association(:face) assert_equal Face.reflect_on_association(:man), has_one_ref.inverse_of @@ -295,7 +283,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end class InverseHasManyTests < ActiveRecord::TestCase - fixtures :men, :interests + fixtures :men, :interests, :posts, :authors, :author_addresses def test_parent_instance_should_be_shared_with_every_child_on_find m = men(:gordon) @@ -309,6 +297,27 @@ class InverseHasManyTests < ActiveRecord::TestCase end end + def test_parent_instance_should_be_shared_with_every_child_on_find_for_sti + a = authors(:david) + ps = a.posts + ps.each do |p| + assert_equal a.name, p.author.name, "Name of man should be the same before changes to parent instance" + a.name = "Bongo" + assert_equal a.name, p.author.name, "Name of man should be the same after changes to parent instance" + p.author.name = "Mungo" + assert_equal a.name, p.author.name, "Name of man should be the same after changes to child-owned instance" + end + + sps = a.special_posts + sps.each do |sp| + assert_equal a.name, sp.author.name, "Name of man should be the same before changes to parent instance" + a.name = "Bongo" + assert_equal a.name, sp.author.name, "Name of man should be the same after changes to parent instance" + sp.author.name = "Mungo" + assert_equal a.name, sp.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 = Man.all.merge!(where: { name: "Gordon" }, includes: :interests).first is = m.interests @@ -475,7 +484,10 @@ class InverseHasManyTests < ActiveRecord::TestCase def test_raise_record_not_found_error_when_no_ids_are_passed man = Man.create! - assert_raise(ActiveRecord::RecordNotFound) { man.interests.find() } + exception = assert_raise(ActiveRecord::RecordNotFound) { man.interests.load.find() } + + assert_equal exception.model, "Interest" + assert_equal exception.primary_key, "id" end def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error @@ -651,20 +663,6 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase assert_equal face.description, new_man.polymorphic_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 - face = faces(:confused) - new_man = Man.new - - assert_not_nil face.polymorphic_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" - assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance" - new_man.polymorphic_face.description = "Mungo" - assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance" - end - def test_inversed_instance_should_not_be_reloaded_after_stale_state_changed new_man = Man.new face = Face.new @@ -677,6 +675,16 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase assert_equal old_inversed_man.object_id, new_inversed_man.object_id end + def test_inversed_instance_should_not_be_reloaded_after_stale_state_changed_with_validation + face = Face.new man: Man.new + + old_inversed_man = face.man + face.save! + new_inversed_man = face.man + + assert_equal old_inversed_man.object_id, new_inversed_man.object_id + end + def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many i = interests(:llama_wrangling) m = i.polymorphic_man diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index a4345f3857..5d83c9435b 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/tag" require "models/tagging" @@ -19,7 +21,7 @@ require "models/car" class AssociationsJoinModelTest < ActiveRecord::TestCase self.use_transactional_tests = false unless supports_savepoints? - fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books, + fixtures :posts, :authors, :author_addresses, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books, # Reload edges table from fixtures as otherwise repeated test was failing :edges @@ -97,11 +99,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class - post = SubStiPost.create title: "SubStiPost", body: "SubStiPost body" - assert_instance_of SubStiPost, post + post = SubAbstractStiPost.create title: "SubAbstractStiPost", body: "SubAbstractStiPost body" + assert_instance_of SubAbstractStiPost, post tagging = tags(:misc).taggings.create(taggable: post) - assert_equal "SubStiPost", tagging.taggable_type + assert_equal "SubAbstractStiPost", tagging.taggable_type end def test_polymorphic_has_many_going_through_join_model_with_inheritance @@ -402,7 +404,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_polymorphic_has_one - assert_equal Tagging.find(1, 2).sort_by(&:id), authors(:david).taggings_2 + assert_equal Tagging.find(1, 2).sort_by(&:id), authors(:david).taggings_2.sort_by(&:id) end def test_has_many_through_polymorphic_has_many @@ -465,7 +467,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase new_tag = Tag.new(name: "new") saved_post.tags << new_tag - assert new_tag.persisted? #consistent with habtm! + assert new_tag.persisted? # consistent with habtm! assert saved_post.persisted? assert_includes saved_post.tags, new_tag @@ -494,25 +496,25 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase post_thinking = posts(:thinking) assert_nothing_raised { post_thinking.tags << push } assert_nil(wrong = post_thinking.tags.detect { |t| t.class != Tag }, - message = "Expected a Tag in tags collection, got #{wrong.class}.") + "Expected a Tag in tags collection, got #{wrong.class}.") assert_nil(wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, - message = "Expected a Tagging in taggings collection, got #{wrong.class}.") + "Expected a Tagging in taggings collection, got #{wrong.class}.") assert_equal(count + 1, post_thinking.reload.tags.size) assert_equal(count + 1, post_thinking.tags.reload.size) assert_kind_of Tag, post_thinking.tags.create!(name: "foo") assert_nil(wrong = post_thinking.tags.detect { |t| t.class != Tag }, - message = "Expected a Tag in tags collection, got #{wrong.class}.") + "Expected a Tag in tags collection, got #{wrong.class}.") assert_nil(wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, - message = "Expected a Tagging in taggings collection, got #{wrong.class}.") + "Expected a Tagging in taggings collection, got #{wrong.class}.") assert_equal(count + 2, post_thinking.reload.tags.size) assert_equal(count + 2, post_thinking.tags.reload.size) assert_nothing_raised { post_thinking.tags.concat(Tag.create!(name: "abc"), Tag.create!(name: "def")) } assert_nil(wrong = post_thinking.tags.detect { |t| t.class != Tag }, - message = "Expected a Tag in tags collection, got #{wrong.class}.") + "Expected a Tag in tags collection, got #{wrong.class}.") assert_nil(wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, - message = "Expected a Tagging in taggings collection, got #{wrong.class}.") + "Expected a Tagging in taggings collection, got #{wrong.class}.") assert_equal(count + 4, post_thinking.reload.tags.size) assert_equal(count + 4, post_thinking.tags.reload.size) diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb index 42dbbad1c8..c95d0425cd 100644 --- a/activerecord/test/cases/associations/left_outer_join_association_test.rb +++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/post" require "models/comment" @@ -7,7 +9,7 @@ require "models/categorization" require "models/person" class LeftOuterJoinAssociationTest < ActiveRecord::TestCase - fixtures :authors, :essays, :posts, :comments, :categorizations, :people + fixtures :authors, :author_addresses, :essays, :posts, :comments, :categorizations, :people def test_construct_finder_sql_applies_aliases_tables_on_association_conditions result = Author.left_outer_joins(:thinking_posts, :welcome_posts).to_a diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index dc26f6a383..65d30d011b 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/author" require "models/post" @@ -22,9 +24,14 @@ require "models/category" require "models/categorization" require "models/membership" require "models/essay" +require "models/hotel" +require "models/department" +require "models/chef" +require "models/cake_designer" +require "models/drink_designer" class NestedThroughAssociationsTest < ActiveRecord::TestCase - fixtures :authors, :books, :posts, :subscriptions, :subscribers, :tags, :taggings, + fixtures :authors, :author_addresses, :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 @@ -423,6 +430,11 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase assert authors.empty? end + def test_nested_has_many_through_with_scope_on_polymorphic_reflection + authors = Author.joins(:ordered_posts).where("posts.id" => posts(:misc_by_bob).id) + assert_equal [authors(:mary), authors(:bob)], authors.distinct.sort_by(&:id) + 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 @@ -567,6 +579,37 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase assert !c.post_taggings.empty? end + def test_polymorphic_has_many_through_when_through_association_has_not_loaded + cake_designer = CakeDesigner.create!(chef: Chef.new) + drink_designer = DrinkDesigner.create!(chef: Chef.new) + department = Department.create!(chefs: [cake_designer.chef, drink_designer.chef]) + Hotel.create!(departments: [department]) + hotel = Hotel.includes(:cake_designers, :drink_designers).take + + assert_equal [cake_designer], hotel.cake_designers + assert_equal [drink_designer], hotel.drink_designers + end + + def test_polymorphic_has_many_through_when_through_association_has_already_loaded + cake_designer = CakeDesigner.create!(chef: Chef.new) + drink_designer = DrinkDesigner.create!(chef: Chef.new) + department = Department.create!(chefs: [cake_designer.chef, drink_designer.chef]) + Hotel.create!(departments: [department]) + hotel = Hotel.includes(:chefs, :cake_designers, :drink_designers).take + + assert_equal [cake_designer], hotel.cake_designers + assert_equal [drink_designer], hotel.drink_designers + end + + def test_polymorphic_has_many_through_joined_different_table_twice + cake_designer = CakeDesigner.create!(chef: Chef.new) + drink_designer = DrinkDesigner.create!(chef: Chef.new) + department = Department.create!(chefs: [cake_designer.chef, drink_designer.chef]) + hotel = Hotel.create!(departments: [department]) + + assert_equal hotel, Hotel.joins(:cake_designers, :drink_designers).take + end + private def assert_includes_and_joins_equal(query, expected, association) diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb index 45e1803858..65a3bb5efe 100644 --- a/activerecord/test/cases/associations/required_test.rb +++ b/activerecord/test/cases/associations/required_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class RequiredAssociationsTest < ActiveRecord::TestCase |