aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/associations_test.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test/cases/associations_test.rb')
-rw-r--r--activerecord/test/cases/associations_test.rb370
1 files changed, 370 insertions, 0 deletions
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
new file mode 100644
index 0000000000..081da95df7
--- /dev/null
+++ b/activerecord/test/cases/associations_test.rb
@@ -0,0 +1,370 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/computer"
+require "models/developer"
+require "models/project"
+require "models/company"
+require "models/categorization"
+require "models/category"
+require "models/post"
+require "models/author"
+require "models/comment"
+require "models/tag"
+require "models/tagging"
+require "models/person"
+require "models/reader"
+require "models/ship_part"
+require "models/ship"
+require "models/liquid"
+require "models/molecule"
+require "models/electron"
+require "models/man"
+require "models/interest"
+
+class AssociationsTest < ActiveRecord::TestCase
+ fixtures :accounts, :companies, :developers, :projects, :developers_projects,
+ :computers, :people, :readers, :authors, :author_addresses, :author_favorites
+
+ def test_eager_loading_should_not_change_count_of_children
+ liquid = Liquid.create(name: "salty")
+ molecule = liquid.molecules.create(name: "molecule_1")
+ molecule.electrons.create(name: "electron_1")
+ molecule.electrons.create(name: "electron_2")
+
+ liquids = Liquid.includes(molecules: :electrons).references(:molecules).where("molecules.id is not null")
+ assert_equal 1, liquids[0].molecules.length
+ end
+
+ def test_subselect
+ author = authors :david
+ favs = author.author_favorites
+ fav2 = author.author_favorites.where(author: Author.where(id: author.id)).to_a
+ assert_equal favs, fav2
+ end
+
+ def test_loading_the_association_target_should_keep_child_records_marked_for_destruction
+ ship = Ship.create!(name: "The good ship Dollypop")
+ part = ship.parts.create!(name: "Mast")
+ part.mark_for_destruction
+ assert_predicate ship.parts[0], :marked_for_destruction?
+ end
+
+ def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction
+ ship = Ship.create!(name: "The good ship Dollypop")
+ part = ship.parts.create!(name: "Mast")
+ part.mark_for_destruction
+ ShipPart.find(part.id).update_columns(name: "Deck")
+ assert_equal "Deck", ship.parts[0].name
+ end
+
+ def test_include_with_order_works
+ assert_nothing_raised { Account.all.merge!(order: "id", includes: :firm).first }
+ assert_nothing_raised { Account.all.merge!(order: :id, includes: :firm).first }
+ end
+
+ def test_bad_collection_keys
+ assert_raise(ArgumentError, "ActiveRecord should have barked on bad collection keys") do
+ Class.new(ActiveRecord::Base).has_many(:wheels, name: "wheels")
+ end
+ end
+
+ def test_should_construct_new_finder_sql_after_create
+ person = Person.new first_name: "clark"
+ assert_equal [], person.readers.to_a
+ person.save!
+ reader = Reader.create! person: person, post: Post.new(title: "foo", body: "bar")
+ assert person.readers.find(reader.id)
+ end
+
+ def test_force_reload
+ firm = Firm.new("name" => "A New Firm, Inc")
+ firm.save
+ firm.clients.each { } # forcing to load all clients
+ assert firm.clients.empty?, "New firm shouldn't have client objects"
+ assert_equal 0, firm.clients.size, "New firm should have 0 clients"
+
+ client = Client.new("name" => "TheClient.com", "firm_id" => firm.id)
+ client.save
+
+ assert firm.clients.empty?, "New firm should have cached no client objects"
+ assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count"
+
+ firm.clients.reload
+
+ assert_not firm.clients.empty?, "New firm should have reloaded client objects"
+ assert_equal 1, firm.clients.size, "New firm should have reloaded clients count"
+ end
+
+ def test_using_limitable_reflections_helper
+ using_limitable_reflections = lambda { |reflections| Tagging.all.send :using_limitable_reflections?, reflections }
+ belongs_to_reflections = [Tagging.reflect_on_association(:tag), Tagging.reflect_on_association(:super_tag)]
+ has_many_reflections = [Tag.reflect_on_association(:taggings), Developer.reflect_on_association(:projects)]
+ mixed_reflections = (belongs_to_reflections + has_many_reflections).uniq
+ assert using_limitable_reflections.call(belongs_to_reflections), "Belong to associations are limitable"
+ assert_not using_limitable_reflections.call(has_many_reflections), "All has many style associations are not limitable"
+ assert_not using_limitable_reflections.call(mixed_reflections), "No collection associations (has many style) should pass"
+ end
+
+ def test_association_with_references
+ firm = companies(:first_firm)
+ assert_includes firm.association_with_references.references_values, "foo"
+ end
+end
+
+class AssociationProxyTest < ActiveRecord::TestCase
+ fixtures :authors, :author_addresses, :posts, :categorizations, :categories, :developers, :projects, :developers_projects
+
+ def test_push_does_not_load_target
+ david = authors(:david)
+
+ david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!"))
+ assert_not_predicate david.posts, :loaded?
+ assert_includes david.posts, post
+ end
+
+ def test_push_has_many_through_does_not_load_target
+ david = authors(:david)
+
+ david.categories << categories(:technology)
+ assert_not_predicate david.categories, :loaded?
+ assert_includes david.categories, categories(:technology)
+ end
+
+ def test_push_followed_by_save_does_not_load_target
+ david = authors(:david)
+
+ david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!"))
+ assert_not_predicate david.posts, :loaded?
+ david.save
+ assert_not_predicate david.posts, :loaded?
+ assert_includes david.posts, post
+ end
+
+ def test_push_does_not_lose_additions_to_new_record
+ josh = Author.new(name: "Josh")
+ josh.posts << Post.new(title: "New on Edge", body: "More cool stuff!")
+ assert_predicate josh.posts, :loaded?
+ assert_equal 1, josh.posts.size
+ end
+
+ def test_append_behaves_like_push
+ josh = Author.new(name: "Josh")
+ josh.posts.append Post.new(title: "New on Edge", body: "More cool stuff!")
+ assert_predicate josh.posts, :loaded?
+ assert_equal 1, josh.posts.size
+ end
+
+ def test_prepend_is_not_defined
+ josh = Author.new(name: "Josh")
+ assert_raises(NoMethodError) { josh.posts.prepend Post.new }
+ end
+
+ def test_save_on_parent_does_not_load_target
+ david = developers(:david)
+
+ assert_not_predicate david.projects, :loaded?
+ david.update_columns(created_at: Time.now)
+ assert_not_predicate david.projects, :loaded?
+ end
+
+ def test_load_does_load_target
+ david = developers(:david)
+
+ assert_not_predicate david.projects, :loaded?
+ david.projects.load
+ assert_predicate david.projects, :loaded?
+ end
+
+ def test_inspect_does_not_reload_a_not_yet_loaded_target
+ andreas = Developer.new name: "Andreas", log: "new developer added"
+ assert_not_predicate andreas.audit_logs, :loaded?
+ assert_match(/message: "new developer added"/, andreas.audit_logs.inspect)
+ end
+
+ def test_save_on_parent_saves_children
+ developer = Developer.create name: "Bryan", salary: 50_000
+ assert_equal 1, developer.reload.audit_logs.size
+ end
+
+ def test_create_via_association_with_block
+ post = authors(:david).posts.create(title: "New on Edge") { |p| p.body = "More cool stuff!" }
+ assert_equal post.title, "New on Edge"
+ assert_equal post.body, "More cool stuff!"
+ end
+
+ def test_create_with_bang_via_association_with_block
+ post = authors(:david).posts.create!(title: "New on Edge") { |p| p.body = "More cool stuff!" }
+ assert_equal post.title, "New on Edge"
+ assert_equal post.body, "More cool stuff!"
+ end
+
+ def test_reload_returns_association
+ david = developers(:david)
+ assert_nothing_raised do
+ assert_equal david.projects, david.projects.reload.reload
+ end
+ end
+
+ def test_proxy_association_accessor
+ david = developers(:david)
+ assert_equal david.association(:projects), david.projects.proxy_association
+ end
+
+ def test_scoped_allows_conditions
+ assert developers(:david).projects.merge(where: "foo").to_sql.include?("foo")
+ end
+
+ test "getting a scope from an association" do
+ david = developers(:david)
+
+ assert david.projects.scope.is_a?(ActiveRecord::Relation)
+ assert_equal david.projects, david.projects.scope
+ end
+
+ test "proxy object is cached" do
+ david = developers(:david)
+ assert_same david.projects, david.projects
+ end
+
+ test "proxy object can be stubbed" do
+ david = developers(:david)
+ david.projects.define_singleton_method(:extra_method) { 42 }
+
+ assert_equal 42, david.projects.extra_method
+ end
+
+ test "inverses get set of subsets of the association" do
+ man = Man.create
+ man.interests.create
+
+ man = Man.find(man.id)
+
+ assert_queries(1) do
+ assert_equal man, man.interests.where("1=1").first.man
+ end
+ end
+
+ test "first! works on loaded associations" do
+ david = authors(:david)
+ assert_equal david.first_posts.first, david.first_posts.reload.first!
+ assert_predicate david.first_posts, :loaded?
+ assert_no_queries { david.first_posts.first! }
+ end
+
+ def test_pluck_uses_loaded_target
+ david = authors(:david)
+ assert_equal david.first_posts.pluck(:title), david.first_posts.load.pluck(:title)
+ assert_predicate david.first_posts, :loaded?
+ assert_no_queries { david.first_posts.pluck(:title) }
+ end
+
+ def test_reset_unloads_target
+ david = authors(:david)
+ david.posts.reload
+
+ assert_predicate david.posts, :loaded?
+ david.posts.reset
+ assert_not_predicate david.posts, :loaded?
+ end
+end
+
+class OverridingAssociationsTest < ActiveRecord::TestCase
+ class DifferentPerson < ActiveRecord::Base; end
+
+ class PeopleList < ActiveRecord::Base
+ has_and_belongs_to_many :has_and_belongs_to_many, before_add: :enlist
+ has_many :has_many, before_add: :enlist
+ belongs_to :belongs_to
+ has_one :has_one
+ end
+
+ class DifferentPeopleList < PeopleList
+ # Different association with the same name, callbacks should be omitted here.
+ has_and_belongs_to_many :has_and_belongs_to_many, class_name: "DifferentPerson"
+ has_many :has_many, class_name: "DifferentPerson"
+ belongs_to :belongs_to, class_name: "DifferentPerson"
+ has_one :has_one, class_name: "DifferentPerson"
+ end
+
+ def test_habtm_association_redefinition_callbacks_should_differ_and_not_inherited
+ # redeclared association on AR descendant should not inherit callbacks from superclass
+ callbacks = PeopleList.before_add_for_has_and_belongs_to_many
+ assert_equal(1, callbacks.length)
+ callbacks = DifferentPeopleList.before_add_for_has_and_belongs_to_many
+ assert_equal([], callbacks)
+ end
+
+ def test_has_many_association_redefinition_callbacks_should_differ_and_not_inherited
+ # redeclared association on AR descendant should not inherit callbacks from superclass
+ callbacks = PeopleList.before_add_for_has_many
+ assert_equal(1, callbacks.length)
+ callbacks = DifferentPeopleList.before_add_for_has_many
+ assert_equal([], callbacks)
+ end
+
+ def test_habtm_association_redefinition_reflections_should_differ_and_not_inherited
+ assert_not_equal(
+ PeopleList.reflect_on_association(:has_and_belongs_to_many),
+ DifferentPeopleList.reflect_on_association(:has_and_belongs_to_many)
+ )
+ end
+
+ def test_has_many_association_redefinition_reflections_should_differ_and_not_inherited
+ assert_not_equal(
+ PeopleList.reflect_on_association(:has_many),
+ DifferentPeopleList.reflect_on_association(:has_many)
+ )
+ end
+
+ def test_belongs_to_association_redefinition_reflections_should_differ_and_not_inherited
+ assert_not_equal(
+ PeopleList.reflect_on_association(:belongs_to),
+ DifferentPeopleList.reflect_on_association(:belongs_to)
+ )
+ end
+
+ def test_has_one_association_redefinition_reflections_should_differ_and_not_inherited
+ assert_not_equal(
+ PeopleList.reflect_on_association(:has_one),
+ DifferentPeopleList.reflect_on_association(:has_one)
+ )
+ end
+
+ def test_requires_symbol_argument
+ assert_raises ArgumentError do
+ Class.new(Post) do
+ belongs_to "author"
+ end
+ end
+ end
+end
+
+class GeneratedMethodsTest < ActiveRecord::TestCase
+ fixtures :developers, :computers, :posts, :comments
+ def test_association_methods_override_attribute_methods_of_same_name
+ assert_equal(developers(:david), computers(:workstation).developer)
+ # this next line will fail if the attribute methods module is generated lazily
+ # after the association methods module is generated
+ assert_equal(developers(:david), computers(:workstation).developer)
+ assert_equal(developers(:david).id, computers(:workstation)[:developer])
+ end
+
+ def test_model_method_overrides_association_method
+ assert_equal(comments(:greetings).body, posts(:welcome).first_comment)
+ end
+
+ module MyModule
+ def comments; :none end
+ end
+
+ class MyArticle < ActiveRecord::Base
+ self.table_name = "articles"
+ include MyModule
+ has_many :comments, inverse_of: false
+ end
+
+ def test_included_module_overwrites_association_methods
+ assert_equal :none, MyArticle.new.comments
+ end
+end