aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/associations/has_one_associations_test.rb
blob: 46498a6dfba9558d540260b8d6d261f0207ff52d (plain) (blame)
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
# frozen_string_literal: true

require "cases/helper"
require "models/developer"
require "models/computer"
require "models/project"
require "models/company"
require "models/ship"
require "models/pirate"
require "models/car"
require "models/bulb"
require "models/author"
require "models/image"
require "models/post"
require "models/drink_designer"
require "models/chef"
require "models/department"
require "models/club"
require "models/membership"

class HasOneAssociationsTest < ActiveRecord::TestCase
  self.use_transactional_tests = false unless supports_savepoints?
  fixtures :accounts, :companies, :developers, :projects, :developers_projects,
           :ships, :pirates, :authors, :author_addresses, :memberships, :clubs

  def setup
    Account.destroyed_account_ids.clear
  end

  def test_has_one
    firm = companies(:first_firm)
    first_account = Account.find(1)
    assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) do
      assert_equal first_account, firm.account
      assert_equal first_account.credit_limit, firm.account.credit_limit
    end
  end

  def test_has_one_does_not_use_order_by
    sql_log = capture_sql { companies(:first_firm).account }
    assert sql_log.all? { |sql| !/order by/i.match?(sql) }, "ORDER BY was used in the query: #{sql_log}"
  end

  def test_has_one_cache_nils
    firm = companies(:another_firm)
    assert_queries(1) { assert_nil firm.account }
    assert_no_queries { assert_nil firm.account }

    firms = Firm.includes(:account).to_a
    assert_no_queries { firms.each(&:account) }
  end

  def test_with_select
    assert_equal Firm.find(1).account_with_select.attributes.size, 2
    assert_equal Firm.all.merge!(includes: :account_with_select).find(1).account_with_select.attributes.size, 2
  end

  def test_finding_using_primary_key
    firm = companies(:first_firm)
    assert_equal Account.find_by_firm_id(firm.id), firm.account
    firm.firm_id = companies(:rails_core).id
    assert_equal accounts(:rails_core_account), firm.account_using_primary_key
  end

  def test_update_with_foreign_and_primary_keys
    firm = companies(:first_firm)
    account = firm.account_using_foreign_and_primary_keys
    assert_equal Account.find_by_firm_name(firm.name), account
    firm.save
    firm.reload
    assert_equal account, firm.account_using_foreign_and_primary_keys
  end

  def test_can_marshal_has_one_association_with_nil_target
    firm = Firm.new
    assert_nothing_raised do
      assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes
    end

    firm.account
    assert_nothing_raised do
      assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes
    end
  end

  def test_proxy_assignment
    company = companies(:first_firm)
    assert_nothing_raised { company.account = company.account }
  end

  def test_type_mismatch
    assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 }
    assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) }
  end

  def test_natural_assignment
    apple = Firm.create("name" => "Apple")
    citibank = Account.create("credit_limit" => 10)
    apple.account = citibank
    assert_equal apple.id, citibank.firm_id
  end

  def test_natural_assignment_to_nil
    old_account_id = companies(:first_firm).account.id
    companies(:first_firm).account = nil
    companies(:first_firm).save
    assert_nil companies(:first_firm).account
    # account is dependent, therefore is destroyed when reference to owner is lost
    assert_raise(ActiveRecord::RecordNotFound) { Account.find(old_account_id) }
  end

  def test_nullification_on_association_change
    firm = companies(:rails_core)
    old_account_id = firm.account.id
    firm.account = Account.new(credit_limit: 5)
    # account is dependent with nullify, therefore its firm_id should be nil
    assert_nil Account.find(old_account_id).firm_id
  end

  def test_nullify_on_polymorphic_association
    department = Department.create!
    designer = DrinkDesignerWithPolymorphicDependentNullifyChef.create!
    chef = department.chefs.create!(employable: designer)

    assert_equal chef.employable_id, designer.id
    assert_equal chef.employable_type, designer.class.name

    designer.destroy!
    chef.reload

    assert_nil chef.employable_id
    assert_nil chef.employable_type
  end

  def test_nullification_on_destroyed_association
    developer = Developer.create!(name: "Someone")
    ship = Ship.create!(name: "Planet Caravan", developer: developer)
    ship.destroy
    assert_not_predicate ship, :persisted?
    assert_not_predicate developer, :persisted?
  end

  def test_natural_assignment_to_nil_after_destroy
    firm = companies(:rails_core)
    old_account_id = firm.account.id
    firm.account.destroy
    firm.account = nil
    assert_nil companies(:rails_core).account
    assert_raise(ActiveRecord::RecordNotFound) { Account.find(old_account_id) }
  end

  def test_association_change_calls_delete
    companies(:first_firm).deletable_account = Account.new(credit_limit: 5)
    assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id]
  end

  def test_association_change_calls_destroy
    companies(:first_firm).account = Account.new(credit_limit: 5)
    assert_equal [companies(:first_firm).id], Account.destroyed_account_ids[companies(:first_firm).id]
  end

  def test_natural_assignment_to_already_associated_record
    company = companies(:first_firm)
    account = accounts(:signals37)
    assert_equal company.account, account
    company.account = account
    company.reload
    account.reload
    assert_equal company.account, account
  end

  def test_dependence
    num_accounts = Account.count

    firm = Firm.find(1)
    assert_not_nil firm.account
    account_id = firm.account.id
    assert_equal [], Account.destroyed_account_ids[firm.id]

    firm.destroy
    assert_equal num_accounts - 1, Account.count
    assert_equal [account_id], Account.destroyed_account_ids[firm.id]
  end

  def test_exclusive_dependence
    num_accounts = Account.count

    firm = ExclusivelyDependentFirm.find(9)
    assert_not_nil firm.account
    assert_equal [], Account.destroyed_account_ids[firm.id]

    firm.destroy
    assert_equal num_accounts - 1, Account.count
    assert_equal [], Account.destroyed_account_ids[firm.id]
  end

  def test_dependence_with_nil_associate
    firm = DependentFirm.new(name: "nullify")
    firm.save!
    assert_nothing_raised { firm.destroy }
  end

  def test_restrict_with_exception
    firm = RestrictedWithExceptionFirm.create!(name: "restrict")
    firm.create_account(credit_limit: 10)

    assert_not_nil firm.account

    assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
    assert RestrictedWithExceptionFirm.exists?(name: "restrict")
    assert_predicate firm.account, :present?
  end

  def test_restrict_with_error
    firm = RestrictedWithErrorFirm.create!(name: "restrict")
    firm.create_account(credit_limit: 10)

    assert_not_nil firm.account

    firm.destroy

    assert_not_empty firm.errors
    assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first
    assert RestrictedWithErrorFirm.exists?(name: "restrict")
    assert_predicate firm.account, :present?
  end

  def test_restrict_with_error_with_locale
    I18n.backend = I18n::Backend::Simple.new
    I18n.backend.store_translations "en", activerecord: { attributes: { restricted_with_error_firm: { account: "firm account" } } }
    firm = RestrictedWithErrorFirm.create!(name: "restrict")
    firm.create_account(credit_limit: 10)

    assert_not_nil firm.account

    firm.destroy

    assert_not_empty firm.errors
    assert_equal "Cannot delete record because a dependent firm account exists", firm.errors[:base].first
    assert RestrictedWithErrorFirm.exists?(name: "restrict")
    assert_predicate firm.account, :present?
  ensure
    I18n.backend.reload!
  end

  def test_successful_build_association
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save

    account = firm.build_account("credit_limit" => 1000)
    assert account.save
    assert_equal account, firm.account
  end

  def test_build_association_dont_create_transaction
    firm = Firm.new
    assert_queries(0) do
      firm.build_account
    end
  end

  def test_building_the_associated_object_with_implicit_sti_base_class
    firm = DependentFirm.new
    company = firm.build_company
    assert_kind_of Company, company, "Expected #{company.class} to be a Company"
  end

  def test_building_the_associated_object_with_explicit_sti_base_class
    firm = DependentFirm.new
    company = firm.build_company(type: "Company")
    assert_kind_of Company, company, "Expected #{company.class} to be a Company"
  end

  def test_building_the_associated_object_with_sti_subclass
    firm = DependentFirm.new
    company = firm.build_company(type: "Client")
    assert_kind_of Client, company, "Expected #{company.class} to be a Client"
  end

  def test_building_the_associated_object_with_an_invalid_type
    firm = DependentFirm.new
    assert_raise(ActiveRecord::SubclassNotFound) { firm.build_company(type: "Invalid") }
  end

  def test_building_the_associated_object_with_an_unrelated_type
    firm = DependentFirm.new
    assert_raise(ActiveRecord::SubclassNotFound) { firm.build_company(type: "Account") }
  end

  def test_build_and_create_should_not_happen_within_scope
    pirate = pirates(:blackbeard)
    scope = pirate.association(:foo_bulb).scope.where_values_hash

    bulb = pirate.build_foo_bulb
    assert_not_equal scope, bulb.scope_after_initialize.where_values_hash

    bulb = pirate.create_foo_bulb
    assert_not_equal scope, bulb.scope_after_initialize.where_values_hash

    bulb = pirate.create_foo_bulb!
    assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
  end

  def test_create_association
    firm = Firm.create(name: "GlobalMegaCorp")
    account = firm.create_account(credit_limit: 1000)
    assert_equal account, firm.reload.account
  end

  def test_create_association_with_bang
    firm = Firm.create(name: "GlobalMegaCorp")
    account = firm.create_account!(credit_limit: 1000)
    assert_equal account, firm.reload.account
  end

  def test_create_association_with_bang_failing
    firm = Firm.create(name: "GlobalMegaCorp")
    assert_raise ActiveRecord::RecordInvalid do
      firm.create_account!
    end
    account = firm.account
    assert_not_nil account
    account.credit_limit = 5
    account.save
    assert_equal account, firm.reload.account
  end

  def test_create_with_inexistent_foreign_key_failing
    firm = Firm.create(name: "GlobalMegaCorp")

    assert_raises(ActiveRecord::UnknownAttributeError) do
      firm.create_account_with_inexistent_foreign_key
    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)

    assert_equal 53, odegy.account.credit_limit
    Account.where(id: odegy.account.id).update_all(credit_limit: 80)
    assert_equal 53, odegy.account.credit_limit

    assert_equal 80, odegy.reload_account.credit_limit
  end

  def test_reload_association_with_query_cache
    odegy_id = companies(:odegy).id

    connection = ActiveRecord::Base.connection
    connection.enable_query_cache!
    connection.clear_query_cache

    # Populate the cache with a query
    odegy = Company.find(odegy_id)
    # Populate the cache with a second query
    odegy.account

    assert_equal 2, connection.query_cache.size

    # Clear the cache and fetch the account again, populating the cache with a query
    assert_queries(1) { odegy.reload_account }

    # This query is not cached anymore, so it should make a real SQL query
    assert_queries(1) { Company.find(odegy_id) }
  ensure
    ActiveRecord::Base.connection.disable_query_cache!
  end

  def test_build
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save

    firm.account = account = Account.new("credit_limit" => 1000)
    assert_equal account, firm.account
    assert account.save
    assert_equal account, firm.account
  end

  def test_create
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save
    firm.account = account = Account.create("credit_limit" => 1000)
    assert_equal account, firm.account
  end

  def test_create_before_save
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.account = account = Account.create("credit_limit" => 1000)
    assert_equal account, firm.account
  end

  def test_dependence_with_missing_association
    Account.destroy_all
    firm = Firm.find(1)
    assert_nil firm.account
    firm.destroy
  end

  def test_dependence_with_missing_association_and_nullify
    Account.destroy_all
    firm = DependentFirm.first
    assert_nil firm.account
    firm.destroy
  end

  def test_finding_with_interpolated_condition
    firm = Firm.first
    superior = firm.clients.create(name: "SuperiorCo")
    superior.rating = 10
    superior.save
    assert_equal 10, firm.clients_with_interpolated_conditions.first.rating
  end

  def test_assignment_before_child_saved
    firm = Firm.find(1)
    firm.account = a = Account.new("credit_limit" => 1000)
    assert_predicate a, :persisted?
    assert_equal a, firm.account
    assert_equal a, firm.account
    firm.association(:account).reload
    assert_equal a, firm.account
  end

  def test_save_still_works_after_accessing_nil_has_one
    jp = Company.new name: "Jaded Pixel"
    jp.dummy_account.nil?

    assert_nothing_raised do
      jp.save!
    end
  end

  def test_cant_save_readonly_association
    assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_firm).readonly_account.save!  }
    assert_predicate companies(:first_firm).readonly_account, :readonly?
  end

  def test_has_one_proxy_should_not_respond_to_private_methods
    assert_raise(NoMethodError) { accounts(:signals37).private_method }
    assert_raise(NoMethodError) { companies(:first_firm).account.private_method }
  end

  def test_has_one_proxy_should_respond_to_private_methods_via_send
    accounts(:signals37).send(:private_method)
    companies(:first_firm).account.send(:private_method)
  end

  def test_save_of_record_with_loaded_has_one
    @firm = companies(:first_firm)
    assert_not_nil @firm.account

    assert_nothing_raised do
      Firm.find(@firm.id).save!
      Firm.all.merge!(includes: :account).find(@firm.id).save!
    end

    @firm.account.destroy

    assert_nothing_raised do
      Firm.find(@firm.id).save!
      Firm.all.merge!(includes: :account).find(@firm.id).save!
    end
  end

  def test_build_respects_hash_condition
    account = companies(:first_firm).build_account_limit_500_with_hash_conditions
    assert account.save
    assert_equal 500, account.credit_limit
  end

  def test_create_respects_hash_condition
    account = companies(:first_firm).create_account_limit_500_with_hash_conditions
    assert_predicate account, :persisted?
    assert_equal 500, account.credit_limit
  end

  def test_attributes_are_being_set_when_initialized_from_has_one_association_with_where_clause
    new_account = companies(:first_firm).build_account(firm_name: "Account")
    assert_equal new_account.firm_name, "Account"
  end

  def test_creation_failure_without_dependent_option
    pirate = pirates(:blackbeard)
    orig_ship = pirate.ship

    assert_equal ships(:black_pearl), orig_ship
    new_ship = pirate.create_ship
    assert_not_equal ships(:black_pearl), new_ship
    assert_equal new_ship, pirate.ship
    assert_predicate new_ship, :new_record?
    assert_nil orig_ship.pirate_id
    assert_not orig_ship.changed? # check it was saved
  end

  def test_creation_failure_with_dependent_option
    pirate = pirates(:blackbeard).becomes(DestructivePirate)
    orig_ship = pirate.dependent_ship

    new_ship = pirate.create_dependent_ship
    assert_predicate new_ship, :new_record?
    assert_predicate orig_ship, :destroyed?
  end

  def test_creation_failure_due_to_new_record_should_raise_error
    pirate = pirates(:redbeard)
    new_ship = Ship.new

    error = assert_raise(ActiveRecord::RecordNotSaved) do
      pirate.ship = new_ship
    end

    assert_equal "Failed to save the new associated ship.", error.message
    assert_nil pirate.ship
    assert_nil new_ship.pirate_id
  end

  def test_replacement_failure_due_to_existing_record_should_raise_error
    pirate = pirates(:blackbeard)
    pirate.ship.name = nil

    assert_not_predicate pirate.ship, :valid?
    error = assert_raise(ActiveRecord::RecordNotSaved) do
      pirate.ship = ships(:interceptor)
    end

    assert_equal ships(:black_pearl), pirate.ship
    assert_equal pirate.id, pirate.ship.pirate_id
    assert_equal "Failed to remove the existing associated ship. " \
                 "The record failed to save after its foreign key was set to nil.", error.message
  end

  def test_replacement_failure_due_to_new_record_should_raise_error
    pirate = pirates(:blackbeard)
    new_ship = Ship.new

    error = assert_raise(ActiveRecord::RecordNotSaved) do
      pirate.ship = new_ship
    end

    assert_equal "Failed to save the new associated ship.", error.message
    assert_equal ships(:black_pearl), pirate.ship
    assert_equal pirate.id, pirate.ship.pirate_id
    assert_equal pirate.id, ships(:black_pearl).reload.pirate_id
    assert_nil new_ship.pirate_id
  end

  def test_association_keys_bypass_attribute_protection
    car = Car.create(name: "honda")

    bulb = car.build_bulb
    assert_equal car.id, bulb.car_id

    bulb = car.build_bulb car_id: car.id + 1
    assert_equal car.id, bulb.car_id

    bulb = car.create_bulb
    assert_equal car.id, bulb.car_id

    bulb = car.create_bulb car_id: car.id + 1
    assert_equal car.id, bulb.car_id
  end

  def test_association_protect_foreign_key
    pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")

    ship = pirate.build_ship
    assert_equal pirate.id, ship.pirate_id

    ship = pirate.build_ship pirate_id: pirate.id + 1
    assert_equal pirate.id, ship.pirate_id

    ship = pirate.create_ship
    assert_equal pirate.id, ship.pirate_id

    ship = pirate.create_ship pirate_id: pirate.id + 1
    assert_equal pirate.id, ship.pirate_id
  end

  def test_build_with_block
    car = Car.create(name: "honda")

    bulb = car.build_bulb { |b| b.color = "Red" }
    assert_equal "RED!", bulb.color
  end

  def test_create_with_block
    car = Car.create(name: "honda")

    bulb = car.create_bulb { |b| b.color = "Red" }
    assert_equal "RED!", bulb.color
  end

  def test_create_bang_with_block
    car = Car.create(name: "honda")

    bulb = car.create_bulb! { |b| b.color = "Red" }
    assert_equal "RED!", bulb.color
  end

  def test_association_attributes_are_available_to_after_initialize
    car = Car.create(name: "honda")
    bulb = car.create_bulb

    assert_equal car.id, bulb.attributes_after_initialize["car_id"]
  end

  def test_has_one_transaction
    company = companies(:first_firm)
    account = Account.find(1)

    company.account # force loading
    assert_no_queries { company.account = account }

    company.account = nil
    assert_no_queries { company.account = nil }
    account = Account.find(2)
    assert_queries { company.account = account }

    assert_no_queries { Firm.new.account = account }
  end

  def test_has_one_assignment_dont_trigger_save_on_change_of_same_object
    pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
    ship = pirate.build_ship(name: "old name")
    ship.save!

    ship.name = "new name"
    assert_predicate ship, :changed?
    assert_queries(1) do
      # One query for updating name, not triggering query for updating pirate_id
      pirate.ship = ship
    end

    assert_equal "new name", pirate.ship.reload.name
  end

  def test_has_one_assignment_triggers_save_on_change_on_replacing_object
    pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
    ship = pirate.build_ship(name: "old name")
    ship.save!

    new_ship = Ship.create(name: "new name")
    assert_queries(2) do
      # One query to nullify the old ship, one query to update the new ship
      pirate.ship = new_ship
    end

    assert_equal "new name", pirate.ship.reload.name
  end

  def test_has_one_autosave_with_primary_key_manually_set
    post = Post.create(id: 1234, title: "Some title", body: "Some content")
    author = Author.new(id: 33, name: "Hank Moody")

    author.post = post
    author.save
    author.reload

    assert_not_nil author.post
    assert_equal author.post, post
  end

  def test_has_one_loading_for_new_record
    post = Post.create!(author_id: 42, title: "foo", body: "bar")
    author = Author.new(id: 42)
    assert_equal post, author.post
  end

  def test_has_one_relationship_cannot_have_a_counter_cache
    assert_raise(ArgumentError) do
      Class.new(ActiveRecord::Base) do
        has_one :thing, counter_cache: true
      end
    end
  end

  def test_with_polymorphic_has_one_with_custom_columns_name
    post = Post.create! title: "foo", body: "bar"
    image = Image.create!

    post.main_image = image
    post.reload

    assert_equal image, post.main_image
  end

  test "dangerous association name raises ArgumentError" do
    [:errors, "errors", :save, "save"].each do |name|
      assert_raises(ArgumentError, "Association #{name} should not be allowed") do
        Class.new(ActiveRecord::Base) do
          has_one name
        end
      end
    end
  end

  def test_has_one_with_touch_option_on_create
    assert_queries(3) {
      Club.create(name: "1000 Oaks", membership_attributes: { favourite: true })
    }
  end

  def test_has_one_with_touch_option_on_update
    new_club = Club.create(name: "1000 Oaks")
    new_club.create_membership

    assert_queries(2) { new_club.update(name: "Effingut") }
  end

  def test_has_one_with_touch_option_on_touch
    new_club = Club.create(name: "1000 Oaks")
    new_club.create_membership

    assert_queries(1) { new_club.touch }
  end

  def test_has_one_with_touch_option_on_destroy
    new_club = Club.create(name: "1000 Oaks")
    new_club.create_membership

    assert_queries(2) { new_club.destroy }
  end

  def test_has_one_with_touch_option_on_empty_update
    new_club = Club.create(name: "1000 Oaks")
    new_club.create_membership

    assert_no_queries { new_club.save }
  end

  class SpecialBook < ActiveRecord::Base
    self.table_name = "books"
    belongs_to :author, class_name: "SpecialAuthor"
    has_one :subscription, class_name: "SpecialSupscription", foreign_key: "subscriber_id"

    enum status: [:proposed, :written, :published]
  end

  class SpecialAuthor < ActiveRecord::Base
    self.table_name = "authors"
    has_one :book, class_name: "SpecialBook", foreign_key: "author_id"
  end

  class SpecialSupscription < ActiveRecord::Base
    self.table_name = "subscriptions"
    belongs_to :book, class_name: "SpecialBook"
  end

  def test_association_enum_works_properly
    author = SpecialAuthor.create!(name: "Test")
    book = SpecialBook.create!(status: "published")
    author.book = book

    assert_equal "published", book.status
    assert_not_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count
  end

  def test_association_enum_works_properly_with_nested_join
    author = SpecialAuthor.create!(name: "Test")
    book = SpecialBook.create!(status: "published")
    author.book = book

    where_clause = { books: { subscriptions: { subscriber_id: nil } } }
    assert_nothing_raised do
      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

  class UndestroyableBook < ActiveRecord::Base
    self.table_name = "books"
    belongs_to :author, class_name: "DestroyableAuthor"
    before_destroy :dont

    def dont
      throw(:abort)
    end
  end

  class DestroyableAuthor < ActiveRecord::Base
    self.table_name = "authors"
    has_one :book, class_name: "UndestroyableBook", foreign_key: "author_id", dependent: :destroy
  end

  def test_dependency_should_halt_parent_destruction
    author = DestroyableAuthor.create!(name: "Test")
    UndestroyableBook.create!(author: author)

    assert_no_difference ["DestroyableAuthor.count", "UndestroyableBook.count"] do
      assert_not author.destroy
    end
  end
end