belongs_to_associations_test.rb 35.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
require "cases/helper"
require "models/developer"
require "models/project"
require "models/company"
require "models/topic"
require "models/reply"
require "models/computer"
require "models/post"
require "models/author"
require "models/tag"
require "models/tagging"
require "models/comment"
require "models/sponsor"
require "models/member"
require "models/essay"
require "models/toy"
require "models/invoice"
require "models/line_item"
require "models/column"
require "models/record"
require "models/admin"
require "models/admin/user"
require "models/ship"
require "models/treasure"
require "models/parrot"
26 27 28

class BelongsToAssociationsTest < ActiveRecord::TestCase
  fixtures :accounts, :companies, :developers, :projects, :topics,
29
           :developers_projects, :computers, :authors, :author_addresses,
30
           :posts, :tags, :taggings, :comments, :sponsors, :members
31 32

  def test_belongs_to
33 34 35
    firm = Client.find(3).firm
    assert_not_nil firm
    assert_equal companies(:first_firm).name, firm.name
36 37
  end

38 39 40 41
  def test_missing_attribute_error_is_raised_when_no_foreign_key_attribute
    assert_raises(ActiveModel::MissingAttributeError) { Client.select(:id).first.firm }
  end

42 43 44 45
  def test_belongs_to_does_not_use_order_by
    ActiveRecord::SQLCounter.clear_log
    Client.find(3).firm
  ensure
46
    assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query"
47 48
  end

49
  def test_belongs_to_with_primary_key
50
    client = Client.create(name: "Primary key client", firm_name: companies(:first_firm).name)
51 52 53
    assert_equal companies(:first_firm).name, client.firm_with_primary_key.name
  end

54 55
  def test_belongs_to_with_primary_key_joins_on_correct_column
    sql = Client.joins(:firm_with_primary_key).to_sql
A
Abdelkader Boudih 已提交
56
    if current_adapter?(:Mysql2Adapter)
57 58
      assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql)
      assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql)
59 60 61 62
    elsif current_adapter?(:OracleAdapter)
      # on Oracle aliases are truncated to 30 characters and are quoted in uppercase
      assert_no_match(/"firm_with_primary_keys_compani"\."id"/i, sql)
      assert_match(/"firm_with_primary_keys_compani"\."name"/i, sql)
E
Emilio Tagua 已提交
63
    else
64 65
      assert_no_match(/"firm_with_primary_keys_companies"\."id"/, sql)
      assert_match(/"firm_with_primary_keys_companies"\."name"/, sql)
E
Emilio Tagua 已提交
66
    end
67 68
  end

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
  def test_optional_relation
    original_value = ActiveRecord::Base.belongs_to_required_by_default
    ActiveRecord::Base.belongs_to_required_by_default = true

    model = Class.new(ActiveRecord::Base) do
      self.table_name = "accounts"
      def self.name; "Temp"; end
      belongs_to :company, optional: true
    end

    account = model.new
    assert account.valid?
  ensure
    ActiveRecord::Base.belongs_to_required_by_default = original_value
  end

  def test_not_optional_relation
    original_value = ActiveRecord::Base.belongs_to_required_by_default
    ActiveRecord::Base.belongs_to_required_by_default = true

    model = Class.new(ActiveRecord::Base) do
      self.table_name = "accounts"
      def self.name; "Temp"; end
      belongs_to :company, optional: false
    end

    account = model.new
96
    assert_not account.valid?
97
    assert_equal [{ error: :blank }], account.errors.details[:company]
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
  ensure
    ActiveRecord::Base.belongs_to_required_by_default = original_value
  end

  def test_required_belongs_to_config
    original_value = ActiveRecord::Base.belongs_to_required_by_default
    ActiveRecord::Base.belongs_to_required_by_default = true

    model = Class.new(ActiveRecord::Base) do
      self.table_name = "accounts"
      def self.name; "Temp"; end
      belongs_to :company
    end

    account = model.new
113
    assert_not account.valid?
114
    assert_equal [{ error: :blank }], account.errors.details[:company]
115 116 117 118
  ensure
    ActiveRecord::Base.belongs_to_required_by_default = original_value
  end

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
  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

139 140 141 142
  def test_default_scope_on_relations_is_not_cached
    counter = 0

    comments = Class.new(ActiveRecord::Base) {
143 144
      self.table_name = "comments"
      self.inheritance_column = "not_there"
145 146

      posts = Class.new(ActiveRecord::Base) {
147 148
        self.table_name = "posts"
        self.inheritance_column = "not_there"
149 150 151

        default_scope -> {
          counter += 1
152
          where("id = :inc", inc: counter)
153 154
        }

155
        has_many :comments, anonymous_class: comments
156
      }
157
      belongs_to :post, anonymous_class: posts, inverse_of: false
158 159 160 161 162 163 164 165 166 167
    }

    assert_equal 0, counter
    comment = comments.first
    assert_equal 0, counter
    sql = capture_sql { comment.post }
    comment.reload
    assert_not_equal sql, capture_sql { comment.post }
  end

168 169 170 171 172 173 174 175 176 177
  def test_proxy_assignment
    account = Account.find(1)
    assert_nothing_raised { account.firm = account.firm }
  end

  def test_type_mismatch
    assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 }
    assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) }
  end

178 179 180 181 182 183 184
  def test_raises_type_mismatch_with_namespaced_class
    assert_nil defined?(Region), "This test requires that there is no top-level Region class"

    ActiveRecord::Base.connection.instance_eval do
      create_table(:admin_regions) { |t| t.string :name }
      add_column :admin_users, :region_id, :integer
    end
185 186
    Admin.const_set "RegionalUser", Class.new(Admin::User) { belongs_to(:region) }
    Admin.const_set "Region", Class.new(ActiveRecord::Base)
187 188

    e = assert_raise(ActiveRecord::AssociationTypeMismatch) {
189
      Admin::RegionalUser.new(region: "wrong value")
190
    }
191
    assert_match(/^Region\([^)]+\) expected, got "wrong value" which is an instance of String\([^)]+\)$/, e.message)
192 193 194
  ensure
    Admin.send :remove_const, "Region" if Admin.const_defined?("Region")
    Admin.send :remove_const, "RegionalUser" if Admin.const_defined?("RegionalUser")
195 196 197 198 199

    ActiveRecord::Base.connection.instance_eval do
      remove_column :admin_users, :region_id if column_exists?(:admin_users, :region_id)
      drop_table :admin_regions, if_exists: true
    end
200 201
  end

202 203 204 205 206 207 208
  def test_natural_assignment
    apple = Firm.create("name" => "Apple")
    citibank = Account.create("credit_limit" => 10)
    citibank.firm = apple
    assert_equal apple.id, citibank.firm_id
  end

209 210 211 212 213 214 215
  def test_id_assignment
    apple = Firm.create("name" => "Apple")
    citibank = Account.create("credit_limit" => 10)
    citibank.firm_id = apple
    assert_nil citibank.firm_id
  end

216 217 218 219 220 221 222
  def test_natural_assignment_with_primary_key
    apple = Firm.create("name" => "Apple")
    citibank = Client.create("name" => "Primary key client")
    citibank.firm_with_primary_key = apple
    assert_equal apple.name, citibank.firm_name
  end

223
  def test_eager_loading_with_primary_key
A
Aaron Patterson 已提交
224 225
    Firm.create("name" => "Apple")
    Client.create("name" => "Citibank", :firm_name => "Apple")
226
    citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key).first
227
    assert citibank_result.association(:firm_with_primary_key).loaded?
228 229
  end

230 231 232
  def test_eager_loading_with_primary_key_as_symbol
    Firm.create("name" => "Apple")
    Client.create("name" => "Citibank", :firm_name => "Apple")
233
    citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key_symbols).first
234
    assert citibank_result.association(:firm_with_primary_key_symbols).loaded?
235 236
  end

237 238 239 240 241 242 243 244 245
  def test_creating_the_belonging_object
    citibank = Account.create("credit_limit" => 10)
    apple    = citibank.create_firm("name" => "Apple")
    assert_equal apple, citibank.firm
    citibank.save
    citibank.reload
    assert_equal apple, citibank.firm
  end

246
  def test_creating_the_belonging_object_with_primary_key
247
    client = Client.create(name: "Primary key client")
248 249 250 251 252 253 254
    apple  = client.create_firm_with_primary_key("name" => "Apple")
    assert_equal apple, client.firm_with_primary_key
    client.save
    client.reload
    assert_equal apple, client.firm_with_primary_key
  end

255 256 257 258 259 260 261
  def test_building_the_belonging_object
    citibank = Account.create("credit_limit" => 10)
    apple    = citibank.build_firm("name" => "Apple")
    citibank.save
    assert_equal apple.id, citibank.firm_id
  end

262 263 264
  def test_building_the_belonging_object_with_implicit_sti_base_class
    account = Account.new
    company = account.build_firm
265
    assert_kind_of Company, company, "Expected #{company.class} to be a Company"
266 267 268 269
  end

  def test_building_the_belonging_object_with_explicit_sti_base_class
    account = Account.new
270
    company = account.build_firm(type: "Company")
271
    assert_kind_of Company, company, "Expected #{company.class} to be a Company"
272 273 274 275
  end

  def test_building_the_belonging_object_with_sti_subclass
    account = Account.new
276
    company = account.build_firm(type: "Firm")
277
    assert_kind_of Firm, company, "Expected #{company.class} to be a Firm"
278 279 280 281
  end

  def test_building_the_belonging_object_with_an_invalid_type
    account = Account.new
282
    assert_raise(ActiveRecord::SubclassNotFound) { account.build_firm(type: "InvalidType") }
283 284 285 286
  end

  def test_building_the_belonging_object_with_an_unrelated_type
    account = Account.new
287
    assert_raise(ActiveRecord::SubclassNotFound) { account.build_firm(type: "Account") }
288 289
  end

290
  def test_building_the_belonging_object_with_primary_key
291
    client = Client.create(name: "Primary key client")
292 293 294 295 296
    apple  = client.build_firm_with_primary_key("name" => "Apple")
    client.save
    assert_equal apple.name, client.firm_name
  end

297
  def test_create!
298 299
    client  = Client.create!(name: "Jimmy")
    account = client.create_account!(credit_limit: 10)
300 301 302 303 304 305 306 307
    assert_equal account, client.account
    assert account.persisted?
    client.save
    client.reload
    assert_equal account, client.account
  end

  def test_failing_create!
308
    client = Client.create!(name: "Jimmy")
309 310 311 312 313
    assert_raise(ActiveRecord::RecordInvalid) { client.create_account! }
    assert_not_nil client.account
    assert client.account.new_record?
  end

314 315 316 317 318 319 320 321 322 323
  def test_reloading_the_belonging_object
    odegy_account = accounts(:odegy_account)

    assert_equal "Odegy", odegy_account.firm.name
    Company.where(id: odegy_account.firm_id).update_all(name: "ODEGY")
    assert_equal "Odegy", odegy_account.firm.name

    assert_equal "ODEGY", odegy_account.reload_firm.name
  end

324 325 326 327
  def test_natural_assignment_to_nil
    client = Client.find(3)
    client.firm = nil
    client.save
328 329
    client.association(:firm).reload
    assert_nil client.firm
330 331 332
    assert_nil client.client_of
  end

333
  def test_natural_assignment_to_nil_with_primary_key
334
    client = Client.create(name: "Primary key client", firm_name: companies(:first_firm).name)
335 336
    client.firm_with_primary_key = nil
    client.save
337 338
    client.association(:firm_with_primary_key).reload
    assert_nil client.firm_with_primary_key
339 340 341
    assert_nil client.client_of
  end

342 343 344 345 346 347 348 349 350 351
  def test_with_different_class_name
    assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name
    assert_not_nil Company.find(3).firm_with_other_name, "Microsoft should have a firm"
  end

  def test_with_condition
    assert_equal Company.find(1).name, Company.find(3).firm_with_condition.name
    assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm"
  end

352 353 354
  def test_polymorphic_association_class
    sponsor = Sponsor.new
    assert_nil sponsor.association(:sponsorable).send(:klass)
355 356
    sponsor.association(:sponsorable).reload
    assert_nil sponsor.sponsorable
357

358
    sponsor.sponsorable_type = "" # the column doesn't have to be declared NOT NULL
359
    assert_nil sponsor.association(:sponsorable).send(:klass)
360 361
    sponsor.association(:sponsorable).reload
    assert_nil sponsor.sponsorable
362

363
    sponsor.sponsorable = Member.new name: "Bert"
364
    assert_equal Member, sponsor.association(:sponsorable).send(:klass)
365
    assert_equal "members", sponsor.association(:sponsorable).aliased_table_name
366 367
  end

368 369
  def test_with_polymorphic_and_condition
    sponsor = Sponsor.create
370
    member = Member.create name: "Bert"
371 372 373 374 375 376
    sponsor.sponsorable = member

    assert_equal member, sponsor.sponsorable
    assert_nil sponsor.sponsorable_with_conditions
  end

377
  def test_with_select
J
Jon Leighton 已提交
378
    assert_equal 1, Company.find(2).firm_with_select.attributes.size
379
    assert_equal 1, Company.all.merge!(includes: :firm_with_select).find(2).firm_with_select.attributes.size
380 381
  end

382 383 384
  def test_belongs_to_without_counter_cache_option
    # Ship has a conventionally named `treasures_count` column, but the counter_cache
    # option is not given on the association.
385
    ship = Ship.create(name: "Countless")
386 387

    assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed unless counter_cache is given on the relation" do
388
      treasure = Treasure.new(name: "Gold", ship: ship)
389 390 391 392 393 394 395 396 397
      treasure.save
    end

    assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed unless counter_cache is given on the relation" do
      treasure = ship.treasures.first
      treasure.destroy
    end
  end

398 399
  def test_belongs_to_counter
    debate = Topic.create("title" => "debate")
400
    assert_equal 0, debate.read_attribute("replies_count"), "No replies yet"
401 402

    trash = debate.replies.create("title" => "blah!", "content" => "world around!")
403
    assert_equal 1, Topic.find(debate.id).read_attribute("replies_count"), "First reply created"
404 405

    trash.destroy
406
    assert_equal 0, Topic.find(debate.id).read_attribute("replies_count"), "First reply deleted"
407 408 409
  end

  def test_belongs_to_counter_with_assigning_nil
410 411
    post = Post.find(1)
    comment = Comment.find(1)
412

413 414
    assert_equal post.id, comment.post_id
    assert_equal 2, Post.find(post.id).comments.size
415

416
    comment.post = nil
417

418
    assert_equal 1, Post.find(post.id).comments.size
419 420
  end

421 422 423 424 425 426 427 428 429
  def test_belongs_to_with_primary_key_counter
    debate  = Topic.create("title" => "debate")
    debate2 = Topic.create("title" => "debate2")
    reply   = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate")

    assert_equal 1, debate.reload.replies_count
    assert_equal 0, debate2.reload.replies_count

    reply.topic_with_primary_key = debate2
430

431 432
    assert_equal 0, debate.reload.replies_count
    assert_equal 1, debate2.reload.replies_count
433 434 435

    reply.topic_with_primary_key = nil

436 437
    assert_equal 0, debate.reload.replies_count
    assert_equal 0, debate2.reload.replies_count
438 439
  end

440
  def test_belongs_to_counter_with_reassigning
441 442 443 444
    topic1 = Topic.create("title" => "t1")
    topic2 = Topic.create("title" => "t2")
    reply1 = Reply.new("title" => "r1", "content" => "r1")
    reply1.topic = topic1
445

446 447 448
    assert reply1.save
    assert_equal 1, Topic.find(topic1.id).replies.size
    assert_equal 0, Topic.find(topic2.id).replies.size
449

450
    reply1.topic = Topic.find(topic2.id)
451

452
    assert_no_queries do
453
      reply1.topic = topic2
454 455
    end

456 457 458
    assert reply1.save
    assert_equal 0, Topic.find(topic1.id).replies.size
    assert_equal 1, Topic.find(topic2.id).replies.size
459

460
    reply1.topic = nil
461

462 463
    assert_equal 0, Topic.find(topic1.id).replies.size
    assert_equal 0, Topic.find(topic2.id).replies.size
464

465
    reply1.topic = topic1
466

467 468
    assert_equal 1, Topic.find(topic1.id).replies.size
    assert_equal 0, Topic.find(topic2.id).replies.size
469

470
    reply1.destroy
471

472 473
    assert_equal 0, Topic.find(topic1.id).replies.size
    assert_equal 0, Topic.find(topic2.id).replies.size
474 475
  end

476
  def test_belongs_to_reassign_with_namespaced_models_and_counters
477 478 479 480
    topic1 = Web::Topic.create("title" => "t1")
    topic2 = Web::Topic.create("title" => "t2")
    reply1 = Web::Reply.new("title" => "r1", "content" => "r1")
    reply1.topic = topic1
481

482 483 484
    assert reply1.save
    assert_equal 1, Web::Topic.find(topic1.id).replies.size
    assert_equal 0, Web::Topic.find(topic2.id).replies.size
485

486
    reply1.topic = Web::Topic.find(topic2.id)
487

488 489 490
    assert reply1.save
    assert_equal 0, Web::Topic.find(topic1.id).replies.size
    assert_equal 1, Web::Topic.find(topic2.id).replies.size
491 492
  end

493
  def test_belongs_to_counter_after_save
494 495
    topic = Topic.create!(title: "monday night")
    topic.replies.create!(title: "re: monday night", content: "football")
496 497 498 499 500 501
    assert_equal 1, Topic.find(topic.id)[:replies_count]

    topic.save!
    assert_equal 1, Topic.find(topic.id)[:replies_count]
  end

502 503 504 505 506 507 508
  def test_belongs_to_with_touch_option_on_touch
    line_item = LineItem.create!
    Invoice.create!(line_items: [line_item])

    assert_queries(1) { line_item.touch }
  end

A
Arthur Neves 已提交
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
  def test_belongs_to_with_touch_on_multiple_records
    line_item = LineItem.create!(amount: 1)
    line_item2 = LineItem.create!(amount: 2)
    Invoice.create!(line_items: [line_item, line_item2])

    assert_queries(1) do
      LineItem.transaction do
        line_item.touch
        line_item2.touch
      end
    end

    assert_queries(2) do
      line_item.touch
      line_item2.touch
    end
  end

527
  def test_belongs_to_with_touch_option_on_touch_without_updated_at_attributes
R
Rafael Mendonça França 已提交
528
    assert_not LineItem.column_names.include?("updated_at")
529 530 531 532

    line_item = LineItem.create!
    invoice = Invoice.create!(line_items: [line_item])
    initial = invoice.updated_at
533 534 535
    travel(1.second) do
      line_item.touch
    end
536

R
Rafael Mendonça França 已提交
537
    assert_not_equal initial, invoice.reload.updated_at
538 539
  end

540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
  def test_belongs_to_with_touch_option_on_touch_and_removed_parent
    line_item = LineItem.create!
    Invoice.create!(line_items: [line_item])

    line_item.invoice = nil

    assert_queries(2) { line_item.touch }
  end

  def test_belongs_to_with_touch_option_on_update
    line_item = LineItem.create!
    Invoice.create!(line_items: [line_item])

    assert_queries(2) { line_item.update amount: 10 }
  end

556 557 558 559 560 561 562
  def test_belongs_to_with_touch_option_on_empty_update
    line_item = LineItem.create!
    Invoice.create!(line_items: [line_item])

    assert_queries(0) { line_item.save }
  end

563 564 565 566 567 568 569
  def test_belongs_to_with_touch_option_on_destroy
    line_item = LineItem.create!
    Invoice.create!(line_items: [line_item])

    assert_queries(2) { line_item.destroy }
  end

570 571 572 573 574 575 576 577
  def test_belongs_to_with_touch_option_on_destroy_with_destroyed_parent
    line_item = LineItem.create!
    invoice   = Invoice.create!(line_items: [line_item])
    invoice.destroy

    assert_queries(1) { line_item.destroy }
  end

578 579 580 581 582 583 584 585 586
  def test_belongs_to_with_touch_option_on_touch_and_reassigned_parent
    line_item = LineItem.create!
    Invoice.create!(line_items: [line_item])

    line_item.invoice = Invoice.create!

    assert_queries(3) { line_item.touch }
  end

587 588 589
  def test_belongs_to_counter_after_update
    topic = Topic.create!(title: "37s")
    topic.replies.create!(title: "re: 37s", content: "rails")
590 591
    assert_equal 1, Topic.find(topic.id)[:replies_count]

592
    topic.update(title: "37signals")
593 594 595
    assert_equal 1, Topic.find(topic.id)[:replies_count]
  end

596
  def test_belongs_to_counter_when_update_columns
597 598
    topic = Topic.create!(title: "37s")
    topic.replies.create!(title: "re: 37s", content: "rails")
G
ganesh 已提交
599 600
    assert_equal 1, Topic.find(topic.id)[:replies_count]

V
Vipul A M 已提交
601
    topic.update_columns(content: "rails is wonderful")
G
ganesh 已提交
602 603 604
    assert_equal 1, Topic.find(topic.id)[:replies_count]
  end

605 606 607 608
  def test_assignment_before_child_saved
    final_cut = Client.new("name" => "Final Cut")
    firm = Firm.find(1)
    final_cut.firm = firm
609
    assert !final_cut.persisted?
610
    assert final_cut.save
611 612
    assert final_cut.persisted?
    assert firm.persisted?
613
    assert_equal firm, final_cut.firm
614 615
    final_cut.association(:firm).reload
    assert_equal firm, final_cut.firm
616 617
  end

618 619 620 621
  def test_assignment_before_child_saved_with_primary_key
    final_cut = Client.new("name" => "Final Cut")
    firm = Firm.find(1)
    final_cut.firm_with_primary_key = firm
622
    assert !final_cut.persisted?
623
    assert final_cut.save
624 625
    assert final_cut.persisted?
    assert firm.persisted?
626
    assert_equal firm, final_cut.firm_with_primary_key
627 628
    final_cut.association(:firm_with_primary_key).reload
    assert_equal firm, final_cut.firm_with_primary_key
629 630
  end

631
  def test_new_record_with_foreign_key_but_no_object
632
    client = Client.new("firm_id" => 1)
633
    # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
634
    assert_equal Firm.all.merge!(order: "id").first, client.firm_with_basic_id
635 636
  end

637 638 639 640
  def test_setting_foreign_key_after_nil_target_loaded
    client = Client.new
    client.firm_with_basic_id
    client.firm_id = 1
641

642 643 644 645 646 647 648 649 650 651
    assert_equal companies(:first_firm), client.firm_with_basic_id
  end

  def test_polymorphic_setting_foreign_key_after_nil_target_loaded
    sponsor = Sponsor.new
    sponsor.sponsorable
    sponsor.sponsorable_id = 1
    sponsor.sponsorable_type = "Member"

    assert_equal members(:groucho), sponsor.sponsorable
652 653
  end

654 655
  def test_dont_find_target_when_foreign_key_is_null
    tagging = taggings(:thinking_general)
P
Paul Nikitochkin 已提交
656
    assert_queries(0) { tagging.super_tag }
657 658
  end

659 660 661 662 663 664
  def test_dont_find_target_when_saving_foreign_key_after_stale_association_loaded
    client = Client.create!(name: "Test client", firm_with_basic_id: Firm.find(1))
    client.firm_id = Firm.create!(name: "Test firm").id
    assert_queries(1) { client.save! }
  end

665 666 667 668 669 670
  def test_field_name_same_as_foreign_key
    computer = Computer.find(1)
    assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # '
  end

  def test_counter_cache
671
    topic = Topic.create title: "Zoom-zoom-zoom"
672 673
    assert_equal 0, topic[:replies_count]

674
    reply = Reply.create(title: "re: zoom", content: "speedy quick!")
675 676 677 678 679 680 681 682 683
    reply.topic = topic

    assert_equal 1, topic.reload[:replies_count]
    assert_equal 1, topic.replies.size

    topic[:replies_count] = 15
    assert_equal 15, topic.replies.size
  end

684
  def test_counter_cache_double_destroy
685
    topic = Topic.create title: "Zoom-zoom-zoom"
686 687

    5.times do
688
      topic.replies.create(title: "re: zoom", content: "speedy quick!")
689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
    end

    assert_equal 5, topic.reload[:replies_count]
    assert_equal 5, topic.replies.size

    reply = topic.replies.first

    reply.destroy
    assert_equal 4, topic.reload[:replies_count]

    reply.destroy
    assert_equal 4, topic.reload[:replies_count]
    assert_equal 4, topic.replies.size
  end

704
  def test_concurrent_counter_cache_double_destroy
705
    topic = Topic.create title: "Zoom-zoom-zoom"
706 707

    5.times do
708
      topic.replies.create(title: "re: zoom", content: "speedy quick!")
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
    end

    assert_equal 5, topic.reload[:replies_count]
    assert_equal 5, topic.replies.size

    reply = topic.replies.first
    reply_clone = Reply.find(reply.id)

    reply.destroy
    assert_equal 4, topic.reload[:replies_count]

    reply_clone.destroy
    assert_equal 4, topic.reload[:replies_count]
    assert_equal 4, topic.replies.size
  end

725
  def test_custom_counter_cache
726
    reply = Reply.create(title: "re: zoom", content: "speedy quick!")
727 728
    assert_equal 0, reply[:replies_count]

729
    silly = SillyReply.create(title: "gaga", content: "boo-boo")
730 731 732 733 734 735 736 737 738
    silly.reply = reply

    assert_equal 1, reply.reload[:replies_count]
    assert_equal 1, reply.replies.size

    reply[:replies_count] = 17
    assert_equal 17, reply.replies.size
  end

739 740 741 742 743 744 745 746 747 748 749
  def test_replace_counter_cache
    topic = Topic.create(title: "Zoom-zoom-zoom")
    reply = Reply.create(title: "re: zoom", content: "speedy quick!")

    reply.topic = topic
    reply.save
    topic.reload

    assert_equal 1, topic.replies_count
  end

750
  def test_association_assignment_sticks
751
    post = Post.first
752

753
    author1, author2 = Author.all.merge!(limit: 2).to_a
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
    assert_not_nil author1
    assert_not_nil author2

    # make sure the association is loaded
    post.author

    # set the association by id, directly
    post.author_id = author2.id

    # save and reload
    post.save!
    post.reload

    # the author id of the post should be the id we set
    assert_equal post.author_id, author2.id
  end

  def test_cant_save_readonly_association
    assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_client).readonly_firm.save! }
    assert companies(:first_client).readonly_firm.readonly?
  end
775

776
  def test_polymorphic_assignment_foreign_key_type_string
777 778 779 780 781 782 783 784 785 786 787 788
    comment = Comment.first
    comment.author   = Author.first
    comment.resource = Member.first
    comment.save

    assert_equal Comment.all.to_a,
      Comment.includes(:author).to_a

    assert_equal Comment.all.to_a,
      Comment.includes(:resource).to_a
  end

789 790 791 792 793 794
  def test_polymorphic_assignment_foreign_type_field_updating
    # should update when assigning a saved record
    sponsor = Sponsor.new
    member = Member.create
    sponsor.sponsorable = member
    assert_equal "Member", sponsor.sponsorable_type
795

796 797 798 799 800 801
    # should update when assigning a new record
    sponsor = Sponsor.new
    member = Member.new
    sponsor.sponsorable = member
    assert_equal "Member", sponsor.sponsorable_type
  end
802 803 804 805

  def test_polymorphic_assignment_with_primary_key_foreign_type_field_updating
    # should update when assigning a saved record
    essay = Essay.new
806
    writer = Author.create(name: "David")
807 808 809 810 811 812 813 814 815
    essay.writer = writer
    assert_equal "Author", essay.writer_type

    # should update when assigning a new record
    essay = Essay.new
    writer = Author.new
    essay.writer = writer
    assert_equal "Author", essay.writer_type
  end
816

817 818 819 820
  def test_polymorphic_assignment_updates_foreign_id_field_for_new_and_saved_records
    sponsor = Sponsor.new
    saved_member = Member.create
    new_member = Member.new
821

822 823
    sponsor.sponsorable = saved_member
    assert_equal saved_member.id, sponsor.sponsorable_id
824

825
    sponsor.sponsorable = new_member
826
    assert_nil sponsor.sponsorable_id
827
  end
828

829 830
  def test_assignment_updates_foreign_id_field_for_new_and_saved_records
    client = Client.new
831
    saved_firm = Firm.create name: "Saved"
832 833 834 835 836 837 838 839 840
    new_firm = Firm.new

    client.firm = saved_firm
    assert_equal saved_firm.id, client.client_of

    client.firm = new_firm
    assert_nil client.client_of
  end

841 842
  def test_polymorphic_assignment_with_primary_key_updates_foreign_id_field_for_new_and_saved_records
    essay = Essay.new
843
    saved_writer = Author.create(name: "David")
844 845 846 847 848 849
    new_writer = Author.new

    essay.writer = saved_writer
    assert_equal saved_writer.name, essay.writer_id

    essay.writer = new_writer
850
    assert_nil essay.writer_id
851 852
  end

853 854 855 856 857 858
  def test_polymorphic_assignment_with_nil
    essay = Essay.new
    assert_nil essay.writer_id
    assert_nil essay.writer_type

    essay.writer_id = 1
859
    essay.writer_type = "Author"
860 861 862 863 864 865

    essay.writer = nil
    assert_nil essay.writer_id
    assert_nil essay.writer_type
  end

866
  def test_belongs_to_proxy_should_not_respond_to_private_methods
867 868
    assert_raise(NoMethodError) { companies(:first_firm).private_method }
    assert_raise(NoMethodError) { companies(:second_client).firm.private_method }
869 870 871 872 873 874
  end

  def test_belongs_to_proxy_should_respond_to_private_methods_via_send
    companies(:first_firm).send(:private_method)
    companies(:second_client).firm.send(:private_method)
  end
875 876 877 878 879 880

  def test_save_of_record_with_loaded_belongs_to
    @account = companies(:first_firm).account

    assert_nothing_raised do
      Account.find(@account.id).save!
881
      Account.all.merge!(includes: :firm).find(@account.id).save!
882 883 884 885 886 887
    end

    @account.firm.delete

    assert_nothing_raised do
      Account.find(@account.id).save!
888
      Account.all.merge!(includes: :firm).find(@account.id).save!
889 890
    end
  end
891 892

  def test_dependent_delete_and_destroy_with_belongs_to
A
Akira Matsuda 已提交
893 894
    AuthorAddress.destroyed_author_address_ids.clear

895 896 897 898 899 900 901 902
    author_address = author_addresses(:david_address)
    author_address_extra = author_addresses(:david_address_extra)
    assert_equal [], AuthorAddress.destroyed_author_address_ids

    assert_difference "AuthorAddress.count", -2 do
      authors(:david).destroy
    end

J
Jon Leighton 已提交
903
    assert_equal [], AuthorAddress.where(id: [author_address.id, author_address_extra.id])
904 905 906
    assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids
  end

907 908
  def test_belongs_to_invalid_dependent_option_raises_exception
    error = assert_raise ArgumentError do
909
      Class.new(Author).belongs_to :special_author_address, dependent: :nullify
910
    end
911
    assert_equal error.message, "The :dependent option must be one of [:destroy, :delete], but is :nullify"
912
  end
913 914

  def test_attributes_are_being_set_when_initialized_from_belongs_to_association_with_where_clause
915
    new_firm = accounts(:signals37).build_firm(name: "Apple")
916 917
    assert_equal new_firm.name, "Apple"
  end
918

919
  def test_attributes_are_set_without_error_when_initialized_from_belongs_to_association_with_array_in_where_clause
920
    new_account = Account.where(credit_limit: [ 50, 60 ]).new
921 922 923
    assert_nil new_account.credit_limit
  end

924
  def test_reassigning_the_parent_id_updates_the_object
925
    client = companies(:second_client)
926

927 928 929 930
    client.firm
    client.firm_with_condition
    firm_proxy                = client.send(:association_instance_get, :firm)
    firm_with_condition_proxy = client.send(:association_instance_get, :firm_with_condition)
931

932 933 934 935 936 937 938 939 940 941 942
    assert !firm_proxy.stale_target?
    assert !firm_with_condition_proxy.stale_target?
    assert_equal companies(:first_firm), client.firm
    assert_equal companies(:first_firm), client.firm_with_condition

    client.client_of = companies(:another_firm).id

    assert firm_proxy.stale_target?
    assert firm_with_condition_proxy.stale_target?
    assert_equal companies(:another_firm), client.firm
    assert_equal companies(:another_firm), client.firm_with_condition
943 944 945
  end

  def test_polymorphic_reassignment_of_associated_id_updates_the_object
946 947 948 949 950 951 952
    sponsor = sponsors(:moustache_club_sponsor_for_groucho)

    sponsor.sponsorable
    proxy = sponsor.send(:association_instance_get, :sponsorable)

    assert !proxy.stale_target?
    assert_equal members(:groucho), sponsor.sponsorable
953

954
    sponsor.sponsorable_id = members(:some_other_guy).id
955

956 957
    assert proxy.stale_target?
    assert_equal members(:some_other_guy), sponsor.sponsorable
958 959 960
  end

  def test_polymorphic_reassignment_of_associated_type_updates_the_object
961
    sponsor = sponsors(:moustache_club_sponsor_for_groucho)
962

963 964
    sponsor.sponsorable
    proxy = sponsor.send(:association_instance_get, :sponsorable)
965

966 967 968
    assert !proxy.stale_target?
    assert_equal members(:groucho), sponsor.sponsorable

969
    sponsor.sponsorable_type = "Firm"
970

971 972 973
    assert proxy.stale_target?
    assert_equal companies(:first_firm), sponsor.sponsorable
  end
974 975 976

  def test_reloading_association_with_key_change
    client = companies(:second_client)
977
    firm = client.association(:firm)
978 979

    client.firm = companies(:another_firm)
980 981
    firm.reload
    assert_equal companies(:another_firm), firm.target
982 983

    client.client_of = companies(:first_firm).id
984 985
    firm.reload
    assert_equal companies(:first_firm), firm.target
986
  end
987 988

  def test_polymorphic_counter_cache
989 990 991
    tagging = taggings(:welcome_general)
    post    = posts(:welcome)
    comment = comments(:greetings)
992

993
    assert_difference lambda { post.reload.tags_count }, -1 do
994
      assert_difference "comment.reload.tags_count", +1 do
995 996 997 998
        tagging.taggable = comment
      end
    end
  end
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017

  def test_polymorphic_with_custom_foreign_type
    sponsor = sponsors(:moustache_club_sponsor_for_groucho)
    groucho = members(:groucho)
    other   = members(:some_other_guy)

    assert_equal groucho, sponsor.sponsorable
    assert_equal groucho, sponsor.thing

    sponsor.thing = other

    assert_equal other, sponsor.sponsorable
    assert_equal other, sponsor.thing

    sponsor.sponsorable = groucho

    assert_equal groucho, sponsor.sponsorable
    assert_equal groucho, sponsor.thing
  end
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038

  def test_build_with_conditions
    client = companies(:second_client)
    firm   = client.build_bob_firm

    assert_equal "Bob", firm.name
  end

  def test_create_with_conditions
    client = companies(:second_client)
    firm   = client.create_bob_firm

    assert_equal "Bob", firm.name
  end

  def test_create_bang_with_conditions
    client = companies(:second_client)
    firm   = client.create_bob_firm!

    assert_equal "Bob", firm.name
  end
1039 1040

  def test_build_with_block
1041
    client = Client.create(name: "Client Company")
1042

1043
    firm = client.build_firm { |f| f.name = "Agency Company" }
1044
    assert_equal "Agency Company", firm.name
1045 1046 1047
  end

  def test_create_with_block
1048
    client = Client.create(name: "Client Company")
1049

1050
    firm = client.create_firm { |f| f.name = "Agency Company" }
1051
    assert_equal "Agency Company", firm.name
1052 1053 1054
  end

  def test_create_bang_with_block
1055
    client = Client.create(name: "Client Company")
1056

1057
    firm = client.create_firm! { |f| f.name = "Agency Company" }
1058
    assert_equal "Agency Company", firm.name
1059
  end
1060 1061

  def test_should_set_foreign_key_on_create_association
1062
    client = Client.create! name: "fuu"
1063

1064
    firm = client.create_firm name: "baa"
1065 1066 1067 1068
    assert_equal firm.id, client.client_of
  end

  def test_should_set_foreign_key_on_create_association!
1069
    client = Client.create! name: "fuu"
1070

1071
    firm = client.create_firm! name: "baa"
1072 1073
    assert_equal firm.id, client.client_of
  end
1074 1075

  def test_self_referential_belongs_to_with_counter_cache_assigning_nil
1076
    comment = Comment.create! post: posts(:thinking), body: "fuu"
1077 1078 1079
    comment.parent = nil
    comment.save!

1080
    assert_nil comment.reload.parent
1081 1082
    assert_equal 0, comments(:greetings).reload.children_count
  end
1083

1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
  def test_belongs_to_with_id_assigning
    post = posts(:welcome)
    comment = Comment.create! body: "foo", post: post
    parent = comments(:greetings)
    assert_equal 0, parent.reload.children_count
    comment.parent_id = parent.id

    comment.save!
    assert_equal 1, parent.reload.children_count
  end

1095 1096 1097 1098 1099 1100 1101
  def test_belongs_to_with_out_of_range_value_assigning
    model = Class.new(Comment) do
      def self.name; "Temp"; end
      validates :post, presence: true
    end

    comment = model.new
1102
    comment.post_id = 9223372036854775808 # out of range in the bigint
1103 1104 1105 1106 1107 1108

    assert_nil comment.post
    assert_not comment.valid?
    assert_equal [{ error: :blank }], comment.errors.details[:post]
  end

1109 1110
  def test_polymorphic_with_custom_primary_key
    toy = Toy.create!
1111
    sponsor = Sponsor.create!(sponsorable: toy)
1112 1113 1114

    assert_equal toy, sponsor.reload.sponsorable
  end
J
Jon Leighton 已提交
1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126

  test "stale tracking doesn't care about the type" do
    apple = Firm.create("name" => "Apple")
    citibank = Account.create("credit_limit" => 10)

    citibank.firm_id = apple.id
    citibank.firm # load it

    citibank.firm_id = apple.id.to_s

    assert !citibank.association(:firm).stale_target?
  end
1127

1128 1129
  def test_reflect_the_most_recent_change
    author1, author2 = Author.limit(2)
1130
    post = Post.new(title: "foo", body: "bar")
1131 1132 1133 1134 1135 1136 1137

    post.author    = author1
    post.author_id = author2.id

    assert post.save
    assert_equal post.author_id, author2.id
  end
1138

1139 1140
  test "dangerous association name raises ArgumentError" do
    [:errors, "errors", :save, "save"].each do |name|
1141 1142 1143 1144 1145 1146 1147
      assert_raises(ArgumentError, "Association #{name} should not be allowed") do
        Class.new(ActiveRecord::Base) do
          belongs_to name
        end
      end
    end
  end
1148

1149
  test "belongs_to works with model called Record" do
1150
    record = Record.create!
1151 1152
    Column.create! record: record
    assert_equal 1, Column.count
1153
  end
1154
end
1155 1156

class BelongsToWithForeignKeyTest < ActiveRecord::TestCase
1157 1158
  fixtures :authors, :author_addresses

1159 1160
  def test_destroy_linked_models
    address = AuthorAddress.create!
1161
    author = Author.create! name: "Author", author_address_id: address.id
1162 1163 1164 1165

    author.destroy!
  end
end