forked from rails/rails
-
Notifications
You must be signed in to change notification settings - Fork 7
/
associations_test.rb
1707 lines (1352 loc) · 59.2 KB
/
associations_test.rb
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
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# frozen_string_literal: true
require "pp"
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/book"
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/human"
require "models/interest"
require "models/pirate"
require "models/parrot"
require "models/bird"
require "models/treasure"
require "models/price_estimate"
require "models/invoice"
require "models/discount"
require "models/line_item"
require "models/shipping_line"
require "models/essay"
require "models/member"
require "models/membership"
require "models/sharded"
require "models/cpk"
require "models/member_detail"
require "models/organization"
require "models/dog"
require "models/other_dog"
class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
:computers, :people, :readers, :authors, :author_addresses, :author_favorites,
:comments, :posts, :sharded_blogs, :sharded_blog_posts, :sharded_comments, :sharded_tags, :sharded_blog_posts_tags,
:cpk_orders, :cpk_books, :cpk_reviews
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_loading_cpk_association_when_persisted_and_in_memory_differ
order = Cpk::Order.create!(id: [1, 2], status: "paid")
book = order.books.create!(id: [3, 4], title: "Book")
Cpk::Book.find(book.id).update_columns(title: "A different title")
order.books.load
assert_equal [3, 4], book.id
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_predicate 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_predicate 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_equal [:foo], firm.association_with_references.references_values
end
def test_belongs_to_a_model_with_composite_foreign_key_finds_associated_record
comment = sharded_comments(:great_comment_blog_post_one)
blog_post = sharded_blog_posts(:great_post_blog_one)
assert_equal(blog_post, comment.blog_post)
end
def test_belongs_to_a_cpk_model_by_id_attribute
order = cpk_orders(:cpk_groceries_order_1)
_order_shop_id, order_id = order.id
agreement = Cpk::OrderAgreement.create(order_id: order_id, signature: "signed")
assert_equal(order, agreement.order)
end
def test_belongs_to_a_model_with_composite_primary_key_uses_composite_pk_in_sql
comment = sharded_comments(:great_comment_blog_post_one)
sql = capture_sql do
comment.blog_post
end.first
assert_match(/#{Regexp.escape(Sharded::BlogPost.lease_connection.quote_table_name("sharded_blog_posts.blog_id"))} =/, sql)
assert_match(/#{Regexp.escape(Sharded::BlogPost.lease_connection.quote_table_name("sharded_blog_posts.id"))} =/, sql)
end
def test_querying_by_whole_associated_records_using_query_constraints
comments = [sharded_comments(:great_comment_blog_post_one), sharded_comments(:great_comment_blog_post_two)]
blog_posts = Sharded::BlogPost.where(comments: comments).to_a
expected_posts = [sharded_blog_posts(:great_post_blog_one), sharded_blog_posts(:great_post_blog_two)]
assert_equal(expected_posts.map(&:id).sort, blog_posts.map(&:id).sort)
end
def test_querying_by_single_associated_record_works_using_query_constraints
comments = [sharded_comments(:great_comment_blog_post_one), sharded_comments(:great_comment_blog_post_two)]
blog_posts = Sharded::BlogPost.where(comments: comments.last).to_a
expected_posts = [sharded_blog_posts(:great_post_blog_two)]
assert_equal(expected_posts.map(&:id).sort, blog_posts.map(&:id).sort)
end
def test_querying_by_relation_with_composite_key
expected_posts = [sharded_blog_posts(:great_post_blog_one), sharded_blog_posts(:great_post_blog_two)]
blog_posts = Sharded::BlogPost.where(comments: Sharded::Comment.where(body: "I really enjoyed the post!")).to_a
assert_equal(expected_posts.map(&:id).sort, blog_posts.map(&:id).sort)
end
def test_has_many_association_with_composite_foreign_key_loads_records
blog_post = sharded_blog_posts(:great_post_blog_one)
comments = blog_post.comments.to_a
assert_includes(comments, sharded_comments(:wow_comment_blog_post_one))
assert_includes(comments, sharded_comments(:great_comment_blog_post_one))
end
def test_belongs_to_with_explicit_composite_foreign_key
car = Cpk::Car.create(make: "Tesla", model: "Model S")
review = Cpk::CarReview.create(car: car, comment: "Great car!", rating: 5)
review.reload
sql = capture_sql do
assert_equal(car, review.car)
end
assert_match(/#{Regexp.escape(Cpk::Car.lease_connection.quote_table_name("cpk_cars.make"))} =/, sql.first)
assert_match(/#{Regexp.escape(Cpk::Car.lease_connection.quote_table_name("cpk_cars.model"))} =/, sql.first)
end
def test_cpk_model_has_many_records_by_id_attribute
order = cpk_orders(:cpk_groceries_order_1)
_order_shop_id, order_id = order.id
agreements = 2.times.map { Cpk::OrderAgreement.create(order_id: order_id, signature: "signed") }
assert_equal(agreements.sort, order.order_agreements.to_a.sort)
end
def test_has_many_association_from_a_model_with_query_constraints_different_from_the_association
blog_post = sharded_blog_posts(:great_post_blog_one)
blog_post = Sharded::BlogPostWithRevision.find(blog_post.id)
comments = []
expected_comments = Sharded::Comment.where(blog_id: blog_post.blog_id, blog_post_id: blog_post.id).to_a
sql = capture_sql do
comments = blog_post.comments.to_a
end.first
assert_match(/WHERE .*#{Regexp.escape(Sharded::Comment.lease_connection.quote_table_name("sharded_comments.blog_id"))} =/, sql)
assert_not_empty(comments)
assert_equal(expected_comments.sort, comments.sort)
end
def test_query_constraints_over_three_without_defining_explicit_foreign_key_query_constraints_raises
Sharded::BlogPostWithRevision.has_many :comments_without_query_constraints, primary_key: [:blog_id, :id], class_name: "Comment"
blog_post = sharded_blog_posts(:great_post_blog_one)
blog_post = Sharded::BlogPostWithRevision.find(blog_post.id)
error = assert_raises ArgumentError do
blog_post.comments_without_query_constraints.to_a
end
assert_equal "The query constraints list on the `Sharded::BlogPostWithRevision` model has more than 2 attributes. Active Record is unable to derive the query constraints for the association. You need to explicitly define the query constraints for this association.", error.message
end
def test_model_with_composite_query_constraints_has_many_association_sql
blog_post = sharded_blog_posts(:great_post_blog_one)
sql = capture_sql do
blog_post.comments.to_a
end.first
assert_match(/#{Regexp.escape(Sharded::Comment.lease_connection.quote_table_name("sharded_comments.blog_post_id"))} =/, sql)
assert_match(/#{Regexp.escape(Sharded::Comment.lease_connection.quote_table_name("sharded_comments.blog_id"))} =/, sql)
end
def test_belongs_to_association_does_not_use_parent_query_constraints_if_not_configured_to
comment = sharded_comments(:great_comment_blog_post_one)
blog_post = Sharded::BlogPost.new(blog_id: comment.blog_id, title: "Following best practices")
comment.blog_post_by_id = blog_post
comment.save
assert_predicate blog_post, :persisted?
assert_equal(blog_post, comment.blog_post_by_id)
end
def test_polymorphic_belongs_to_uses_parent_query_constraints
parent_post = sharded_blog_posts(:great_post_blog_one)
child_post = Sharded::BlogPost.create!(title: "Child post", blog_id: parent_post.blog_id, parent: parent_post)
child_post.reload # reload to forget the parent association
assert_equal parent_post, child_post.parent
end
def test_preloads_model_with_query_constraints_by_explicitly_configured_fk_and_pk
comment = sharded_comments(:great_comment_blog_post_one)
comments = Sharded::Comment.where(id: comment.id).preload(:blog_post_by_id).to_a
comment = comments.first
assert_equal(comment.blog_post_by_id, comment.blog_post)
end
def test_append_composite_foreign_key_has_many_association
blog_post = sharded_blog_posts(:great_post_blog_one)
comment = Sharded::Comment.new(body: "Great post! :clap:")
comment.save
blog_post.comments << comment
assert_includes(blog_post.comments, comment)
assert_equal(blog_post.id, comment.blog_post_id)
assert_equal(blog_post.blog_id, comment.blog_id)
end
def test_nullify_composite_foreign_key_has_many_association
blog_post = sharded_blog_posts(:great_post_blog_one)
comment = sharded_comments(:great_comment_blog_post_one)
assert_not_empty(blog_post.comments)
blog_post.comments = []
comment = Sharded::Comment.find(comment.id)
assert_nil(comment.blog_post_id)
assert_nil(comment.blog_id)
assert_empty(blog_post.comments)
assert_empty(blog_post.reload.comments)
end
def test_assign_persisted_composite_foreign_key_belongs_to_association
comment = sharded_comments(:great_comment_blog_post_one)
another_blog = sharded_blogs(:sharded_blog_two)
assert_not_equal(comment.blog_id, another_blog.id)
blog_post = Sharded::BlogPost.new(title: "New post", blog_id: another_blog.id)
blog_post.save
comment.blog_post = blog_post
assert_equal(blog_post, comment.blog_post)
assert_equal(comment.blog_id, blog_post.blog_id)
assert_equal(another_blog.id, comment.blog_id)
assert_equal(comment.blog_post_id, blog_post.id)
end
def test_nullify_composite_foreign_key_belongs_to_association
comment = sharded_comments(:great_comment_blog_post_one)
assert_not_nil(comment.blog_post)
comment.blog_post = nil
assert_nil(comment.blog_id)
assert_nil(comment.blog_post_id)
comment.save
assert_nil(comment.blog_post)
assert_nil(comment.reload.blog_post)
end
def test_assign_composite_foreign_key_belongs_to_association
comment = sharded_comments(:great_comment_blog_post_one)
another_blog = sharded_blogs(:sharded_blog_two)
assert_not_equal(comment.blog_id, another_blog.id)
blog_post = Sharded::BlogPost.new(title: "New post", blog_id: another_blog.id)
comment.blog_post = blog_post
assert_equal(blog_post, comment.blog_post)
assert_equal(comment.blog_id, blog_post.blog_id)
assert_equal(another_blog.id, comment.blog_id)
end
def test_query_constraints_that_dont_include_the_primary_key_raise_with_a_single_column
original = Sharded::BlogPost.instance_variable_get(:@query_constraints_list)
Sharded::BlogPost.query_constraints :title
Sharded::BlogPost.has_many :comments_without_single_column_query_constraints, primary_key: [:blog_id, :id], class_name: "Comment"
blog_post = sharded_blog_posts(:great_post_blog_one)
error = assert_raises ArgumentError do
blog_post.comments_without_single_column_query_constraints.to_a
end
assert_equal "The query constraints on the `Sharded::BlogPost` model does not include the primary key so Active Record is unable to derive the foreign key constraints for the association. You need to explicitly define the query constraints for this association.", error.message
ensure
Sharded::BlogPost.instance_variable_set(:@query_constraints_list, original)
end
def test_query_constraints_that_dont_include_the_primary_key_raise_with_multiple_columns
original = Sharded::BlogPost.instance_variable_get(:@query_constraints_list)
Sharded::BlogPost.query_constraints :title, :revision
Sharded::BlogPost.has_many :comments_without_multiple_column_query_constraints, primary_key: [:blog_id, :id], class_name: "Comment"
blog_post = sharded_blog_posts(:great_post_blog_one)
error = assert_raises ArgumentError do
blog_post.comments_without_multiple_column_query_constraints.to_a
end
assert_equal "The query constraints on the `Sharded::BlogPost` model does not include the primary key so Active Record is unable to derive the foreign key constraints for the association. You need to explicitly define the query constraints for this association.", error.message
ensure
Sharded::BlogPost.instance_variable_set(:@query_constraints_list, original)
end
def test_assign_belongs_to_cpk_model_by_id_attribute
order = cpk_orders(:cpk_groceries_order_1)
agreement = Cpk::OrderAgreement.new(signature: "signed")
agreement.order = order
agreement.save
assert_not_nil(agreement.reload.order)
assert_not_nil(agreement.order_id)
assert_equal(order, agreement.order)
_shop_id, order_id = order.id
assert_equal(order_id, agreement.order_id)
end
def test_append_composite_foreign_key_has_many_association_with_autosave
blog_post = sharded_blog_posts(:great_post_blog_one)
comment = Sharded::Comment.new(body: "Great post! :clap:")
blog_post.comments << comment
assert_predicate(comment, :persisted?)
assert_includes(blog_post.comments, comment)
assert_equal(blog_post.id, comment.blog_post_id)
assert_equal(blog_post.blog_id, comment.blog_id)
end
def test_assign_composite_foreign_key_belongs_to_association_with_autosave
comment = sharded_comments(:great_comment_blog_post_one)
another_blog = sharded_blogs(:sharded_blog_two)
assert_not_equal(comment.blog_id, another_blog.id)
blog_post = Sharded::BlogPost.new(title: "New post", blog_id: another_blog.id)
comment.blog_post = blog_post
comment.save
assert_predicate(blog_post, :persisted?)
assert_equal(blog_post, comment.blog_post)
assert_equal(comment.blog_id, blog_post.blog_id)
assert_equal(another_blog.id, comment.blog_id)
assert_equal(comment.blog_post_id, blog_post.id)
end
def test_append_composite_has_many_through_association
blog_post = sharded_blog_posts(:great_post_blog_one)
tag = Sharded::Tag.new(name: "Ruby on Rails", blog_id: blog_post.blog_id)
tag.save
blog_post.tags << tag
assert_includes(blog_post.reload.tags, tag)
assert_predicate Sharded::BlogPostTag.where(blog_post_id: blog_post.id, blog_id: blog_post.blog_id, tag_id: tag.id), :exists?
end
def test_append_composite_has_many_through_association_with_autosave
blog_post = sharded_blog_posts(:great_post_blog_one)
tag = Sharded::Tag.new(name: "Ruby on Rails", blog_id: blog_post.blog_id)
blog_post.tags << tag
assert_includes(blog_post.reload.tags, tag)
assert_predicate Sharded::BlogPostTag.where(blog_post_id: blog_post.id, blog_id: blog_post.blog_id, tag_id: tag.id), :exists?
end
def test_nullify_composite_has_many_through_association
blog_post = sharded_blog_posts(:great_post_blog_one)
assert_not_empty(blog_post.tags)
blog_post.tags = []
assert_empty(blog_post.tags)
assert_empty(blog_post.reload.tags)
assert_not_predicate Sharded::BlogPostTag.where(blog_post_id: blog_post.id, blog_id: blog_post.blog_id), :exists?
end
def test_using_query_constraints_warns_about_changing_behavior
has_many_expected_message = <<~MSG.squish
Setting `query_constraints:` option on `Sharded::BlogPost.has_many :qc_deprecated_comments` is deprecated.
To maintain current behavior, use the `foreign_key` option instead.
MSG
assert_deprecated(has_many_expected_message, ActiveRecord.deprecator) do
Sharded::BlogPost.has_many :qc_deprecated_comments,
class_name: "Sharded::Comment", query_constraints: [:blog_id, :blog_post_id]
end
belongs_to_expected_message = <<~MSG.squish
Setting `query_constraints:` option on `Sharded::Comment.belongs_to :qc_deprecated_blog_post` is deprecated.
To maintain current behavior, use the `foreign_key` option instead.
MSG
assert_deprecated(belongs_to_expected_message, ActiveRecord.deprecator) do
Sharded::Comment.belongs_to :qc_deprecated_blog_post,
class_name: "Sharded::Blog", query_constraints: [:blog_id, :blog_post_id]
end
end
end
class AssociationProxyTest < ActiveRecord::TestCase
fixtures :authors, :author_addresses, :posts, :categorizations, :categories, :developers, :projects, :developers_projects, :members
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)
assert_predicate andreas.audit_logs, :loaded?
end
def test_pretty_print_does_not_reload_a_not_yet_loaded_target
andreas = Developer.new(log: "new developer added")
assert_not_predicate andreas.audit_logs, :loaded?
out = StringIO.new
PP.pp(andreas.audit_logs, out)
assert_match(/message: "new developer added"/, out.string)
assert_predicate andreas.audit_logs, :loaded?
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 "New on Edge", post.title
assert_equal "More cool stuff!", post.body
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 "New on Edge", post.title
assert_equal "More cool stuff!", post.body
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
human = Human.create
human.interests.create
human = Human.find(human.id)
assert_queries_count(1) do
assert_equal human, human.interests.where("1=1").first.human
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_pick_uses_loaded_target
david = authors(:david)
assert_equal david.first_posts.pick(:title), david.first_posts.load.pick(:title)
assert_predicate david.first_posts, :loaded?
assert_no_queries { david.first_posts.pick(:title) }
end
def test_reset_unloads_target
david = authors(:david)
david.posts.reload
assert_predicate david.posts, :loaded?
assert_predicate david.posts, :loaded
david.posts.reset
assert_not_predicate david.posts, :loaded?
assert_not_predicate david.posts, :loaded
end
def test_target_merging_ignores_persisted_in_memory_records
david = authors(:david)
assert david.thinking_posts.include?(posts(:thinking))
david.thinking_posts.create!(title: "Something else entirely", body: "Does not matter.")
assert_equal 1, david.thinking_posts.size
assert_equal 1, david.thinking_posts.to_a.size
end
def test_target_merging_ignores_persisted_in_memory_records_when_loaded_records_are_empty
member = members(:blarpy_winkup)
assert_empty member.favorite_memberships
membership = member.favorite_memberships.create!
membership.update!(favorite: false)
assert_empty member.favorite_memberships.to_a
end
def test_target_merging_recognizes_updated_in_memory_records
member = members(:blarpy_winkup)
membership = member.create_membership!(favorite: false)
assert_empty member.favorite_memberships
membership.update!(favorite: true)
assert_not_empty member.favorite_memberships.to_a
end
def test_size_differentiates_between_new_and_persisted_in_memory_records_when_loaded_records_are_empty
member = members(:blarpy_winkup)
assert_empty member.favorite_memberships
membership = member.favorite_memberships.create!
membership.update!(favorite: false)
# CollectionAssociation#size has different behavior when loaded vs. non-loaded
# the first call will mark the association as loaded and the second call will
# take a different code path, so it's important to keep both assertions
assert_equal 0, member.favorite_memberships.size
assert_equal 0, member.favorite_memberships.size
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
class ModelAssociatedToClassesThatDoNotExist < ActiveRecord::Base
self.table_name = "accounts" # this is just to avoid adding a new model just for this test
has_one :non_existent_has_one_class
belongs_to :non_existent_belongs_to_class
has_many :non_existent_has_many_classes
end
def test_associations_raise_with_name_error_if_associated_to_classes_that_do_not_exist
assert_raises NameError do
ModelAssociatedToClassesThatDoNotExist.new.non_existent_has_one_class
end
assert_raises NameError do
ModelAssociatedToClassesThatDoNotExist.new.non_existent_belongs_to_class
end
assert_raises NameError do
ModelAssociatedToClassesThatDoNotExist.new.non_existent_has_many_classes
end
end
end
class PreloaderTest < ActiveRecord::TestCase
fixtures :posts, :comments, :books, :authors, :tags, :taggings, :essays, :categories, :author_addresses,
:sharded_blog_posts, :sharded_comments, :sharded_blog_posts_tags, :sharded_tags,
:members, :member_details, :organizations, :cpk_orders, :cpk_order_agreements,
:dogs, :other_dogs
def test_preload_with_scope
post = posts(:welcome)
preloader = ActiveRecord::Associations::Preloader.new(records: [post], associations: :comments, scope: Comment.where(body: "Thank you for the welcome"))
preloader.call
assert_predicate post.comments, :loaded?
assert_equal [comments(:greetings)], post.comments
end
def test_preload_makes_correct_number_of_queries_on_array
post = posts(:welcome)
assert_queries_count(1) do
preloader = ActiveRecord::Associations::Preloader.new(records: [post], associations: :comments)
preloader.call
end
end
def test_preload_makes_correct_number_of_queries_on_relation
post = posts(:welcome)
relation = Post.where(id: post.id)
assert_queries_count(2) do
preloader = ActiveRecord::Associations::Preloader.new(records: relation, associations: :comments)
preloader.call
end
end
def test_preload_does_not_concatenate_duplicate_records
post = posts(:welcome)
post.reload
post.comments.create!(body: "A new comment")
ActiveRecord::Associations::Preloader.new(records: [post], associations: :comments).call
assert_equal post.comments.length, post.comments.count
assert_equal post.comments.all.to_a, post.comments
end
def test_preload_for_hmt_with_conditions
post = posts(:welcome)
_normal_category = post.categories.create!(name: "Normal")
special_category = post.special_categories.create!(name: "Special")
preloader = ActiveRecord::Associations::Preloader.new(records: [post], associations: :hmt_special_categories)
preloader.call
assert_equal 1, post.hmt_special_categories.length
assert_equal [special_category], post.hmt_special_categories
end
def test_preload_groups_queries_with_same_scope
book = books(:awdr)
post = posts(:welcome)
assert_queries_count(1) do
preloader = ActiveRecord::Associations::Preloader.new(records: [book, post], associations: :author)
preloader.call
end
assert_no_queries do
book.author
post.author
end
end
def test_preload_grouped_queries_with_already_loaded_records
book = books(:awdr)
post = posts(:welcome)
book.author
assert_no_queries do
ActiveRecord::Associations::Preloader.new(records: [book, post], associations: :author).call
book.author
post.author
end
end
def test_preload_grouped_queries_of_middle_records
comments = [
comments(:eager_sti_on_associations_s_comment1),
comments(:eager_sti_on_associations_s_comment2),
]
assert_queries_count(2) do
ActiveRecord::Associations::Preloader.new(records: comments, associations: [:author, :ordinary_post]).call
end
end
def test_preload_grouped_queries_of_through_records
author = authors(:david)
assert_queries_count(3) do
ActiveRecord::Associations::Preloader.new(records: [author], associations: [:hello_post_comments, :comments]).call
end
end
def test_preload_through_records_with_already_loaded_middle_record
member = members(:groucho)
expected_member_detail_ids = member.organization_member_details_2.pluck(:id)
member.reload.organization # load through record
assert_queries_count(1) do
ActiveRecord::Associations::Preloader.new(records: [member], associations: :organization_member_details_2).call
end
assert_no_queries do
assert_equal expected_member_detail_ids.sort, member.organization_member_details_2.map(&:id).sort
end
end
def test_preload_with_instance_dependent_scope
david = authors(:david)
david2 = Author.create!(name: "David")
bob = authors(:bob)
post = Post.create!(
author: david,
title: "test post",
body: "this post is about David"
)
post2 = Post.create!(
author: david,
title: "test post 2",
body: "this post is also about David"
)
assert_queries_count(2) do
preloader = ActiveRecord::Associations::Preloader.new(records: [david, david2, bob], associations: :posts_mentioning_author)
preloader.call
end
assert_predicate david.posts_mentioning_author, :loaded?
assert_predicate david2.posts_mentioning_author, :loaded?
assert_predicate bob.posts_mentioning_author, :loaded?
assert_equal [post, post2].sort, david.posts_mentioning_author.sort
assert_equal [], david2.posts_mentioning_author
assert_equal [], bob.posts_mentioning_author
end
def test_preload_with_instance_dependent_through_scope
david = authors(:david)
david2 = Author.create!(name: "David")
bob = authors(:bob)
comment1 = david.posts.first.comments.create!(body: "Hi David!")
comment2 = david.posts.first.comments.create!(body: "This comment mentions david")
assert_queries_count(2) do
preloader = ActiveRecord::Associations::Preloader.new(records: [david, david2, bob], associations: :comments_mentioning_author)
preloader.call
end
assert_predicate david.comments_mentioning_author, :loaded?
assert_predicate david2.comments_mentioning_author, :loaded?
assert_predicate bob.comments_mentioning_author, :loaded?
assert_equal [comment1, comment2].sort, david.comments_mentioning_author.sort
assert_equal [], david2.comments_mentioning_author
assert_equal [], bob.comments_mentioning_author
end
def test_preload_with_through_instance_dependent_scope
david = authors(:david)
david2 = Author.create!(name: "David")
bob = authors(:bob)
post = Post.create!(
author: david,
title: "test post",
body: "this post is about David"
)
Post.create!(
author: david,
title: "test post 2",
body: "this post is also about David"
)
post3 = Post.create!(
author: bob,
title: "test post 3",
body: "this post is about Bob"
)
comment1 = post.comments.create!(body: "hi!")
comment2 = post.comments.create!(body: "hello!")
comment3 = post3.comments.create!(body: "HI BOB!")
assert_queries_count(3) do
preloader = ActiveRecord::Associations::Preloader.new(records: [david, david2, bob], associations: :comments_on_posts_mentioning_author)
preloader.call
end
assert_predicate david.comments_on_posts_mentioning_author, :loaded?
assert_predicate david2.comments_on_posts_mentioning_author, :loaded?