1use crate::prelude::*;
2
3#[driver_test(id(ID), scenario(crate::scenarios::has_one_optional_belongs_to))]
6pub async fn preload_has_one_option_none_then_some(test: &mut Test) -> Result<()> {
7 let mut db = setup(test).await;
8
9 let user_no_profile = User::create().name("No Profile").exec(&mut db).await?;
11
12 let user_no_profile = User::filter_by_id(user_no_profile.id)
14 .include(User::fields().profile())
15 .get(&mut db)
16 .await?;
17
18 assert!(user_no_profile.profile.get().is_none());
20
21 let user_with_profile = User::create()
23 .name("Has Profile")
24 .profile(Profile::create().bio("A bio"))
25 .exec(&mut db)
26 .await?;
27
28 let user_with_profile = User::filter_by_id(user_with_profile.id)
30 .include(User::fields().profile())
31 .get(&mut db)
32 .await?;
33
34 let profile = user_with_profile.profile.get().as_ref().unwrap();
35 assert_eq!("A bio", profile.bio);
36 assert_eq!(user_with_profile.id, *profile.user_id.as_ref().unwrap());
37
38 Ok(())
39}
40
41#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
42pub async fn basic_has_many_and_belongs_to_preload(test: &mut Test) -> Result<()> {
43 let mut db = setup(test).await;
44
45 let user = User::create()
47 .name("Alice")
48 .todo(Todo::create().title("todo 1"))
49 .todo(Todo::create().title("todo 2"))
50 .todo(Todo::create().title("todo 3"))
51 .exec(&mut db)
52 .await?;
53
54 let user = User::filter_by_id(user.id)
56 .include(User::fields().todos())
57 .get(&mut db)
58 .await?;
59
60 assert_eq!(3, user.todos.get().len());
62
63 let id = user.todos.get()[0].id;
64
65 let todo = Todo::filter_by_id(id)
66 .include(Todo::fields().user())
67 .get(&mut db)
68 .await?;
69
70 assert_eq!(user.id, todo.user.get().id);
71 assert_eq!(user.id, todo.user_id);
72 Ok(())
73}
74
75#[driver_test(id(ID))]
76pub async fn multiple_includes_same_model(test: &mut Test) -> Result<()> {
77 #[derive(Debug, toasty::Model)]
78 struct User {
79 #[key]
80 #[auto]
81 id: ID,
82
83 #[allow(dead_code)]
84 name: String,
85
86 #[has_many]
87 posts: toasty::HasMany<Post>,
88
89 #[has_many]
90 comments: toasty::HasMany<Comment>,
91 }
92
93 #[derive(Debug, toasty::Model)]
94 struct Post {
95 #[key]
96 #[auto]
97 id: ID,
98
99 #[allow(dead_code)]
100 title: String,
101
102 #[index]
103 #[allow(dead_code)]
104 user_id: ID,
105
106 #[belongs_to(key = user_id, references = id)]
107 user: toasty::BelongsTo<User>,
108 }
109
110 #[derive(Debug, toasty::Model)]
111 struct Comment {
112 #[key]
113 #[auto]
114 id: ID,
115
116 #[allow(dead_code)]
117 text: String,
118
119 #[index]
120 #[allow(dead_code)]
121 user_id: ID,
122
123 #[belongs_to(key = user_id, references = id)]
124 user: toasty::BelongsTo<User>,
125 }
126
127 let mut db = test.setup_db(models!(User, Post, Comment)).await;
128
129 let user = User::create().name("Test User").exec(&mut db).await?;
131
132 Post::create()
134 .title("Post 1")
135 .user(&user)
136 .exec(&mut db)
137 .await?;
138
139 Post::create()
140 .title("Post 2")
141 .user(&user)
142 .exec(&mut db)
143 .await?;
144
145 Comment::create()
147 .text("Comment 1")
148 .user(&user)
149 .exec(&mut db)
150 .await?;
151
152 Comment::create()
153 .text("Comment 2")
154 .user(&user)
155 .exec(&mut db)
156 .await?;
157
158 Comment::create()
159 .text("Comment 3")
160 .user(&user)
161 .exec(&mut db)
162 .await?;
163
164 let user_with_posts = User::filter_by_id(user.id)
166 .include(User::fields().posts())
167 .get(&mut db)
168 .await?;
169 assert_eq!(2, user_with_posts.posts.get().len());
170
171 let user_with_comments = User::filter_by_id(user.id)
172 .include(User::fields().comments())
173 .get(&mut db)
174 .await?;
175 assert_eq!(3, user_with_comments.comments.get().len());
176
177 let loaded_user = User::filter_by_id(user.id)
179 .include(User::fields().posts()) .include(User::fields().comments()) .get(&mut db)
182 .await?;
183
184 assert_eq!(2, loaded_user.posts.get().len());
185 assert_eq!(3, loaded_user.comments.get().len());
186 Ok(())
187}
188
189#[driver_test(id(ID))]
190pub async fn basic_has_one_and_belongs_to_preload(test: &mut Test) -> Result<()> {
191 #[derive(Debug, toasty::Model)]
192 struct User {
193 #[key]
194 #[auto]
195 id: ID,
196
197 name: String,
198
199 #[has_one]
200 profile: toasty::HasOne<Option<Profile>>,
201 }
202
203 #[derive(Debug, toasty::Model)]
204 struct Profile {
205 #[key]
206 #[auto]
207 id: ID,
208
209 bio: String,
210
211 #[unique]
212 user_id: Option<ID>,
213
214 #[belongs_to(key = user_id, references = id)]
215 user: toasty::BelongsTo<Option<User>>,
216 }
217
218 let mut db = test.setup_db(models!(User, Profile)).await;
219
220 let user = User::create()
222 .name("John Doe")
223 .profile(Profile::create().bio("A person"))
224 .exec(&mut db)
225 .await?;
226
227 let user = User::filter_by_id(user.id)
229 .include(User::fields().profile())
230 .get(&mut db)
231 .await?;
232
233 let profile = user.profile.get().as_ref().unwrap();
235 assert_eq!("A person", profile.bio);
236 assert_eq!(user.id, *profile.user_id.as_ref().unwrap());
237
238 let profile_id = profile.id;
239
240 let profile = Profile::filter_by_id(profile_id)
242 .include(Profile::fields().user())
243 .get(&mut db)
244 .await?;
245
246 assert_eq!(user.id, profile.user.get().as_ref().unwrap().id);
247 assert_eq!("John Doe", profile.user.get().as_ref().unwrap().name);
248 Ok(())
249}
250
251#[driver_test(id(ID))]
252pub async fn multiple_includes_with_has_one(test: &mut Test) -> Result<()> {
253 #[derive(Debug, toasty::Model)]
254 #[allow(dead_code)]
255 struct User {
256 #[key]
257 #[auto]
258 id: ID,
259
260 name: String,
261
262 #[has_one]
263 profile: toasty::HasOne<Option<Profile>>,
264
265 #[has_one]
266 settings: toasty::HasOne<Option<Settings>>,
267 }
268
269 #[derive(Debug, toasty::Model)]
270 #[allow(dead_code)]
271 struct Profile {
272 #[key]
273 #[auto]
274 id: ID,
275
276 bio: String,
277
278 #[unique]
279 user_id: Option<ID>,
280
281 #[belongs_to(key = user_id, references = id)]
282 user: toasty::BelongsTo<Option<User>>,
283 }
284
285 #[derive(Debug, toasty::Model)]
286 #[allow(dead_code)]
287 struct Settings {
288 #[key]
289 #[auto]
290 id: ID,
291
292 theme: String,
293
294 #[unique]
295 user_id: Option<ID>,
296
297 #[belongs_to(key = user_id, references = id)]
298 user: toasty::BelongsTo<Option<User>>,
299 }
300
301 let mut db = test.setup_db(models!(User, Profile, Settings)).await;
302
303 let user = User::create()
305 .name("Jane Doe")
306 .profile(Profile::create().bio("Software engineer"))
307 .settings(Settings::create().theme("dark"))
308 .exec(&mut db)
309 .await?;
310
311 let user_with_profile = User::filter_by_id(user.id)
313 .include(User::fields().profile())
314 .get(&mut db)
315 .await?;
316 assert!(user_with_profile.profile.get().is_some());
317 assert_eq!(
318 "Software engineer",
319 user_with_profile.profile.get().as_ref().unwrap().bio
320 );
321
322 let user_with_settings = User::filter_by_id(user.id)
323 .include(User::fields().settings())
324 .get(&mut db)
325 .await?;
326 assert!(user_with_settings.settings.get().is_some());
327 assert_eq!(
328 "dark",
329 user_with_settings.settings.get().as_ref().unwrap().theme
330 );
331
332 let loaded_user = User::filter_by_id(user.id)
334 .include(User::fields().profile()) .include(User::fields().settings()) .get(&mut db)
337 .await?;
338
339 assert!(loaded_user.profile.get().is_some());
340 assert_eq!(
341 "Software engineer",
342 loaded_user.profile.get().as_ref().unwrap().bio
343 );
344 assert!(loaded_user.settings.get().is_some());
345 assert_eq!("dark", loaded_user.settings.get().as_ref().unwrap().theme);
346 Ok(())
347}
348
349#[driver_test(id(ID))]
350pub async fn combined_has_many_and_has_one_preload(test: &mut Test) -> Result<()> {
351 #[derive(Debug, toasty::Model)]
352 #[allow(dead_code)]
353 struct User {
354 #[key]
355 #[auto]
356 id: ID,
357
358 name: String,
359
360 #[has_one]
361 profile: toasty::HasOne<Option<Profile>>,
362
363 #[has_many]
364 todos: toasty::HasMany<Todo>,
365 }
366
367 #[derive(Debug, toasty::Model)]
368 #[allow(dead_code)]
369 struct Profile {
370 #[key]
371 #[auto]
372 id: ID,
373
374 bio: String,
375
376 #[unique]
377 user_id: Option<ID>,
378
379 #[belongs_to(key = user_id, references = id)]
380 user: toasty::BelongsTo<Option<User>>,
381 }
382
383 #[derive(Debug, toasty::Model)]
384 #[allow(dead_code)]
385 struct Todo {
386 #[key]
387 #[auto]
388 id: ID,
389
390 title: String,
391
392 #[index]
393 user_id: ID,
394
395 #[belongs_to(key = user_id, references = id)]
396 user: toasty::BelongsTo<User>,
397 }
398
399 let mut db = test.setup_db(models!(User, Profile, Todo)).await;
400
401 let user = User::create()
403 .name("Bob Smith")
404 .profile(Profile::create().bio("Developer"))
405 .todo(Todo::create().title("Task 1"))
406 .todo(Todo::create().title("Task 2"))
407 .todo(Todo::create().title("Task 3"))
408 .exec(&mut db)
409 .await?;
410
411 let loaded_user = User::filter_by_id(user.id)
413 .include(User::fields().profile()) .include(User::fields().todos()) .get(&mut db)
416 .await?;
417
418 assert!(loaded_user.profile.get().is_some());
420 assert_eq!("Developer", loaded_user.profile.get().as_ref().unwrap().bio);
421
422 assert_eq!(3, loaded_user.todos.get().len());
424 let todo_titles: Vec<&str> = loaded_user
425 .todos
426 .get()
427 .iter()
428 .map(|t| t.title.as_str())
429 .collect();
430 assert!(todo_titles.contains(&"Task 1"));
431 assert!(todo_titles.contains(&"Task 2"));
432 assert!(todo_titles.contains(&"Task 3"));
433 Ok(())
434}
435
436#[driver_test(id(ID), requires(sql), scenario(crate::scenarios::has_many_belongs_to))]
437pub async fn preload_on_empty_table(test: &mut Test) -> Result<()> {
438 let mut db = setup(test).await;
439
440 let users: Vec<User> = User::all()
442 .include(User::fields().todos())
443 .exec(&mut db)
444 .await?;
445
446 assert_eq!(0, users.len());
447 Ok(())
448}
449
450#[driver_test(id(ID))]
451pub async fn preload_on_empty_query(test: &mut Test) -> Result<()> {
452 #[derive(Debug, toasty::Model)]
453 struct User {
454 #[key]
455 #[auto]
456 id: ID,
457
458 #[index]
459 #[allow(dead_code)]
460 name: String,
461
462 #[has_many]
463 #[allow(dead_code)]
464 todos: toasty::HasMany<Todo>,
465 }
466
467 #[derive(Debug, toasty::Model)]
468 struct Todo {
469 #[key]
470 #[auto]
471 id: ID,
472
473 #[index]
474 #[allow(dead_code)]
475 user_id: ID,
476
477 #[belongs_to(key = user_id, references = id)]
478 #[allow(dead_code)]
479 user: toasty::BelongsTo<User>,
480 }
481
482 let mut db = test.setup_db(models!(User, Todo)).await;
483
484 let users: Vec<User> = User::filter_by_name("foo")
486 .include(User::fields().todos())
487 .exec(&mut db)
488 .await?;
489
490 assert_eq!(0, users.len());
491 Ok(())
492}
493
494#[driver_test(id(ID))]
497pub async fn preload_has_many_with_optional_belongs_to(test: &mut Test) -> Result<()> {
498 #[derive(Debug, toasty::Model)]
499 struct User {
500 #[key]
501 #[auto]
502 id: ID,
503
504 name: String,
505
506 #[has_many]
507 todos: toasty::HasMany<Todo>,
508 }
509
510 #[derive(Debug, toasty::Model)]
511 struct Todo {
512 #[key]
513 #[auto]
514 id: ID,
515
516 #[index]
517 title: String,
518
519 #[index]
520 user_id: Option<ID>,
521
522 #[belongs_to(key = user_id, references = id)]
523 user: toasty::BelongsTo<Option<User>>,
524 }
525
526 let mut db = test.setup_db(models!(User, Todo)).await;
527
528 let user = User::create()
530 .name("Alice")
531 .todo(Todo::create().title("Task 1"))
532 .todo(Todo::create().title("Task 2"))
533 .exec(&mut db)
534 .await?;
535
536 let user = User::filter_by_id(user.id)
538 .include(User::fields().todos())
539 .get(&mut db)
540 .await?;
541
542 assert_eq!(2, user.todos.get().len());
543
544 let todo_id = user.todos.get()[0].id;
545
546 let todo = Todo::filter_by_id(todo_id)
548 .include(Todo::fields().user())
549 .get(&mut db)
550 .await?;
551
552 assert_eq!(user.id, todo.user.get().as_ref().unwrap().id);
553
554 let orphan = Todo::create().title("Orphan").exec(&mut db).await?;
556
557 let orphan = Todo::filter_by_id(orphan.id)
559 .include(Todo::fields().user())
560 .get(&mut db)
561 .await?;
562
563 assert!(orphan.user.get().is_none());
564
565 Ok(())
566}
567
568#[driver_test(id(ID))]
572pub async fn preload_has_one_optional_with_required_belongs_to(test: &mut Test) -> Result<()> {
573 #[derive(Debug, toasty::Model)]
574 struct User {
575 #[key]
576 #[auto]
577 id: ID,
578
579 name: String,
580
581 #[has_one]
582 profile: toasty::HasOne<Option<Profile>>,
583 }
584
585 #[derive(Debug, toasty::Model)]
586 struct Profile {
587 #[key]
588 #[auto]
589 id: ID,
590
591 bio: String,
592
593 #[unique]
594 user_id: ID,
595
596 #[belongs_to(key = user_id, references = id)]
597 user: toasty::BelongsTo<User>,
598 }
599
600 let mut db = test.setup_db(models!(User, Profile)).await;
601
602 let user_with = User::create()
604 .name("Has Profile")
605 .profile(Profile::create().bio("hello"))
606 .exec(&mut db)
607 .await?;
608
609 let user_without = User::create().name("No Profile").exec(&mut db).await?;
611
612 let loaded = User::filter_by_id(user_with.id)
614 .include(User::fields().profile())
615 .get(&mut db)
616 .await?;
617
618 let profile = loaded.profile.get().as_ref().unwrap();
619 assert_eq!("hello", profile.bio);
620 assert_eq!(user_with.id, profile.user_id);
621
622 let loaded = User::filter_by_id(user_without.id)
624 .include(User::fields().profile())
625 .get(&mut db)
626 .await?;
627
628 assert!(loaded.profile.get().is_none());
629
630 let profile = Profile::filter_by_user_id(user_with.id)
632 .include(Profile::fields().user())
633 .get(&mut db)
634 .await?;
635
636 assert_eq!(user_with.id, profile.user.get().id);
637 assert_eq!("Has Profile", profile.user.get().name);
638
639 Ok(())
640}
641
642#[driver_test(id(ID))]
646pub async fn preload_has_one_required_with_optional_belongs_to(test: &mut Test) -> Result<()> {
647 #[derive(Debug, toasty::Model)]
648 struct User {
649 #[key]
650 #[auto]
651 id: ID,
652
653 name: String,
654
655 #[has_one]
656 profile: toasty::HasOne<Profile>,
657 }
658
659 #[derive(Debug, toasty::Model)]
660 struct Profile {
661 #[key]
662 #[auto]
663 id: ID,
664
665 bio: String,
666
667 #[unique]
668 user_id: Option<ID>,
669
670 #[belongs_to(key = user_id, references = id)]
671 user: toasty::BelongsTo<Option<User>>,
672 }
673
674 let mut db = test.setup_db(models!(User, Profile)).await;
675
676 let user = User::create()
678 .name("Alice")
679 .profile(Profile::create().bio("a bio"))
680 .exec(&mut db)
681 .await?;
682
683 let loaded = User::filter_by_id(user.id)
685 .include(User::fields().profile())
686 .get(&mut db)
687 .await?;
688
689 let profile = loaded.profile.get();
690 assert_eq!("a bio", profile.bio);
691 assert_eq!(user.id, *profile.user_id.as_ref().unwrap());
692
693 let profile = Profile::filter_by_id(profile.id)
695 .include(Profile::fields().user())
696 .get(&mut db)
697 .await?;
698
699 assert_eq!(user.id, profile.user.get().as_ref().unwrap().id);
700 assert_eq!("Alice", profile.user.get().as_ref().unwrap().name);
701
702 Ok(())
703}
704
705#[driver_test(id(ID))]
706pub async fn nested_has_many_preload(test: &mut Test) {
707 #[derive(Debug, toasty::Model)]
708 #[allow(dead_code)]
709 struct User {
710 #[key]
711 #[auto]
712 id: ID,
713
714 name: String,
715
716 #[has_many]
717 todos: toasty::HasMany<Todo>,
718 }
719
720 #[derive(Debug, toasty::Model)]
721 #[allow(dead_code)]
722 struct Todo {
723 #[key]
724 #[auto]
725 id: ID,
726
727 title: String,
728
729 #[index]
730 user_id: ID,
731
732 #[belongs_to(key = user_id, references = id)]
733 user: toasty::BelongsTo<User>,
734
735 #[has_many]
736 steps: toasty::HasMany<Step>,
737 }
738
739 #[derive(Debug, toasty::Model)]
740 #[allow(dead_code)]
741 struct Step {
742 #[key]
743 #[auto]
744 id: ID,
745
746 description: String,
747
748 #[index]
749 todo_id: ID,
750
751 #[belongs_to(key = todo_id, references = id)]
752 todo: toasty::BelongsTo<Todo>,
753 }
754
755 let mut db = test.setup_db(models!(User, Todo, Step)).await;
756
757 let user = User::create()
759 .name("Alice")
760 .todo(
761 Todo::create()
762 .title("Todo 1")
763 .step(Step::create().description("Step 1a"))
764 .step(Step::create().description("Step 1b")),
765 )
766 .todo(
767 Todo::create()
768 .title("Todo 2")
769 .step(Step::create().description("Step 2a"))
770 .step(Step::create().description("Step 2b"))
771 .step(Step::create().description("Step 2c")),
772 )
773 .exec(&mut db)
774 .await
775 .unwrap();
776
777 let user = User::filter_by_id(user.id)
779 .include(User::fields().todos().steps())
780 .get(&mut db)
781 .await
782 .unwrap();
783
784 let todos = user.todos.get();
786 assert_eq!(2, todos.len());
787
788 let mut all_step_descriptions: Vec<&str> = Vec::new();
790 for todo in todos {
791 let steps = todo.steps.get();
792 for step in steps {
793 all_step_descriptions.push(&step.description);
794 }
795 }
796 all_step_descriptions.sort();
797 assert_eq!(
798 all_step_descriptions,
799 vec!["Step 1a", "Step 1b", "Step 2a", "Step 2b", "Step 2c"]
800 );
801}
802
803#[driver_test(id(ID))]
806pub async fn nested_has_many_then_has_one_optional(test: &mut Test) -> Result<()> {
807 #[derive(Debug, toasty::Model)]
808 #[allow(dead_code)]
809 struct User {
810 #[key]
811 #[auto]
812 id: ID,
813
814 name: String,
815
816 #[has_many]
817 posts: toasty::HasMany<Post>,
818 }
819
820 #[derive(Debug, toasty::Model)]
821 #[allow(dead_code)]
822 struct Post {
823 #[key]
824 #[auto]
825 id: ID,
826
827 title: String,
828
829 #[index]
830 user_id: ID,
831
832 #[belongs_to(key = user_id, references = id)]
833 user: toasty::BelongsTo<User>,
834
835 #[has_one]
836 detail: toasty::HasOne<Option<Detail>>,
837 }
838
839 #[derive(Debug, toasty::Model)]
840 #[allow(dead_code)]
841 struct Detail {
842 #[key]
843 #[auto]
844 id: ID,
845
846 body: String,
847
848 #[unique]
849 post_id: Option<ID>,
850
851 #[belongs_to(key = post_id, references = id)]
852 post: toasty::BelongsTo<Option<Post>>,
853 }
854
855 let mut db = test.setup_db(models!(User, Post, Detail)).await;
856
857 let user = User::create()
858 .name("Alice")
859 .post(
860 Post::create()
861 .title("P1")
862 .detail(Detail::create().body("D1")),
863 )
864 .post(Post::create().title("P2")) .exec(&mut db)
866 .await?;
867
868 let user = User::filter_by_id(user.id)
869 .include(User::fields().posts().detail())
870 .get(&mut db)
871 .await?;
872
873 let posts = user.posts.get();
874 assert_eq!(2, posts.len());
875
876 let mut with_detail = 0;
877 let mut without_detail = 0;
878 for post in posts {
879 match post.detail.get() {
880 Some(d) => {
881 assert_eq!("D1", d.body);
882 with_detail += 1;
883 }
884 None => without_detail += 1,
885 }
886 }
887 assert_eq!(1, with_detail);
888 assert_eq!(1, without_detail);
889
890 Ok(())
891}
892
893#[driver_test(id(ID))]
896pub async fn nested_has_many_then_has_one_required(test: &mut Test) -> Result<()> {
897 #[derive(Debug, toasty::Model)]
898 #[allow(dead_code)]
899 struct User {
900 #[key]
901 #[auto]
902 id: ID,
903
904 name: String,
905
906 #[has_many]
907 accounts: toasty::HasMany<Account>,
908 }
909
910 #[derive(Debug, toasty::Model)]
911 #[allow(dead_code)]
912 struct Account {
913 #[key]
914 #[auto]
915 id: ID,
916
917 label: String,
918
919 #[index]
920 user_id: ID,
921
922 #[belongs_to(key = user_id, references = id)]
923 user: toasty::BelongsTo<User>,
924
925 #[has_one]
926 settings: toasty::HasOne<Settings>,
927 }
928
929 #[derive(Debug, toasty::Model)]
930 #[allow(dead_code)]
931 struct Settings {
932 #[key]
933 #[auto]
934 id: ID,
935
936 theme: String,
937
938 #[unique]
939 account_id: Option<ID>,
940
941 #[belongs_to(key = account_id, references = id)]
942 account: toasty::BelongsTo<Option<Account>>,
943 }
944
945 let mut db = test.setup_db(models!(User, Account, Settings)).await;
946
947 let user = User::create()
948 .name("Bob")
949 .account(
950 Account::create()
951 .label("A1")
952 .settings(Settings::create().theme("dark")),
953 )
954 .account(
955 Account::create()
956 .label("A2")
957 .settings(Settings::create().theme("light")),
958 )
959 .exec(&mut db)
960 .await?;
961
962 let user = User::filter_by_id(user.id)
963 .include(User::fields().accounts().settings())
964 .get(&mut db)
965 .await?;
966
967 let accounts = user.accounts.get();
968 assert_eq!(2, accounts.len());
969
970 let mut themes: Vec<&str> = accounts
971 .iter()
972 .map(|a| a.settings.get().theme.as_str())
973 .collect();
974 themes.sort();
975 assert_eq!(themes, vec!["dark", "light"]);
976
977 Ok(())
978}
979
980#[driver_test(id(ID))]
983pub async fn nested_has_many_then_belongs_to_required(test: &mut Test) -> Result<()> {
984 #[derive(Debug, toasty::Model)]
985 #[allow(dead_code)]
986 struct Category {
987 #[key]
988 #[auto]
989 id: ID,
990
991 name: String,
992
993 #[has_many]
994 items: toasty::HasMany<Item>,
995 }
996
997 #[derive(Debug, toasty::Model)]
998 #[allow(dead_code)]
999 struct Brand {
1000 #[key]
1001 #[auto]
1002 id: ID,
1003
1004 name: String,
1005 }
1006
1007 #[derive(Debug, toasty::Model)]
1008 #[allow(dead_code)]
1009 struct Item {
1010 #[key]
1011 #[auto]
1012 id: ID,
1013
1014 title: String,
1015
1016 #[index]
1017 category_id: ID,
1018
1019 #[belongs_to(key = category_id, references = id)]
1020 category: toasty::BelongsTo<Category>,
1021
1022 #[index]
1023 brand_id: ID,
1024
1025 #[belongs_to(key = brand_id, references = id)]
1026 brand: toasty::BelongsTo<Brand>,
1027 }
1028
1029 let mut db = test.setup_db(models!(Category, Brand, Item)).await;
1030
1031 let brand_a = Brand::create().name("BrandA").exec(&mut db).await?;
1032 let brand_b = Brand::create().name("BrandB").exec(&mut db).await?;
1033
1034 let cat = Category::create()
1035 .name("Electronics")
1036 .item(Item::create().title("Phone").brand(&brand_a))
1037 .item(Item::create().title("Laptop").brand(&brand_b))
1038 .exec(&mut db)
1039 .await?;
1040
1041 let cat = Category::filter_by_id(cat.id)
1042 .include(Category::fields().items().brand())
1043 .get(&mut db)
1044 .await?;
1045
1046 let items = cat.items.get();
1047 assert_eq!(2, items.len());
1048
1049 let mut brand_names: Vec<&str> = items.iter().map(|i| i.brand.get().name.as_str()).collect();
1050 brand_names.sort();
1051 assert_eq!(brand_names, vec!["BrandA", "BrandB"]);
1052
1053 Ok(())
1054}
1055
1056#[driver_test(id(ID))]
1061pub async fn nested_has_many_then_shared_belongs_to(test: &mut Test) -> Result<()> {
1062 #[derive(Debug, toasty::Model)]
1063 #[allow(dead_code)]
1064 struct Category {
1065 #[key]
1066 #[auto]
1067 id: ID,
1068
1069 name: String,
1070
1071 #[has_many]
1072 items: toasty::HasMany<Item>,
1073 }
1074
1075 #[derive(Debug, toasty::Model)]
1076 #[allow(dead_code)]
1077 struct Brand {
1078 #[key]
1079 #[auto]
1080 id: ID,
1081
1082 name: String,
1083 }
1084
1085 #[derive(Debug, toasty::Model)]
1086 #[allow(dead_code)]
1087 struct Item {
1088 #[key]
1089 #[auto]
1090 id: ID,
1091
1092 title: String,
1093
1094 #[index]
1095 category_id: ID,
1096
1097 #[belongs_to(key = category_id, references = id)]
1098 category: toasty::BelongsTo<Category>,
1099
1100 #[index]
1101 brand_id: ID,
1102
1103 #[belongs_to(key = brand_id, references = id)]
1104 brand: toasty::BelongsTo<Brand>,
1105 }
1106
1107 let mut db = test.setup_db(models!(Category, Brand, Item)).await;
1108
1109 let brand = Brand::create().name("BrandA").exec(&mut db).await?;
1110 let cat = Category::create()
1111 .name("Electronics")
1112 .item(Item::create().title("Phone").brand(&brand))
1113 .item(Item::create().title("Laptop").brand(&brand))
1114 .exec(&mut db)
1115 .await?;
1116
1117 let cat = Category::filter_by_id(cat.id)
1118 .include(Category::fields().items().brand())
1119 .get(&mut db)
1120 .await?;
1121
1122 let items = cat.items.get();
1123 assert_eq!(2, items.len());
1124 for item in items {
1125 assert_eq!("BrandA", item.brand.get().name);
1126 }
1127
1128 Ok(())
1129}
1130
1131#[driver_test(id(ID))]
1134pub async fn nested_has_many_then_belongs_to_optional(test: &mut Test) -> Result<()> {
1135 #[derive(Debug, toasty::Model)]
1136 #[allow(dead_code)]
1137 struct Team {
1138 #[key]
1139 #[auto]
1140 id: ID,
1141
1142 name: String,
1143
1144 #[has_many]
1145 tasks: toasty::HasMany<Task>,
1146 }
1147
1148 #[derive(Debug, toasty::Model)]
1149 #[allow(dead_code)]
1150 struct Assignee {
1151 #[key]
1152 #[auto]
1153 id: ID,
1154
1155 name: String,
1156 }
1157
1158 #[derive(Debug, toasty::Model)]
1159 #[allow(dead_code)]
1160 struct Task {
1161 #[key]
1162 #[auto]
1163 id: ID,
1164
1165 title: String,
1166
1167 #[index]
1168 team_id: ID,
1169
1170 #[belongs_to(key = team_id, references = id)]
1171 team: toasty::BelongsTo<Team>,
1172
1173 #[index]
1174 assignee_id: Option<ID>,
1175
1176 #[belongs_to(key = assignee_id, references = id)]
1177 assignee: toasty::BelongsTo<Option<Assignee>>,
1178 }
1179
1180 let mut db = test.setup_db(models!(Team, Assignee, Task)).await;
1181
1182 let person = Assignee::create().name("Alice").exec(&mut db).await?;
1183
1184 let team = Team::create()
1185 .name("Engineering")
1186 .task(Task::create().title("Assigned").assignee(&person))
1187 .task(Task::create().title("Unassigned"))
1188 .exec(&mut db)
1189 .await?;
1190
1191 let team = Team::filter_by_id(team.id)
1192 .include(Team::fields().tasks().assignee())
1193 .get(&mut db)
1194 .await?;
1195
1196 let tasks = team.tasks.get();
1197 assert_eq!(2, tasks.len());
1198
1199 let mut assigned = 0;
1200 let mut unassigned = 0;
1201 for task in tasks {
1202 match task.assignee.get() {
1203 Some(a) => {
1204 assert_eq!("Alice", a.name);
1205 assigned += 1;
1206 }
1207 None => unassigned += 1,
1208 }
1209 }
1210 assert_eq!(1, assigned);
1211 assert_eq!(1, unassigned);
1212
1213 Ok(())
1214}
1215
1216#[driver_test(id(ID))]
1219pub async fn nested_has_one_optional_then_has_many(test: &mut Test) -> Result<()> {
1220 #[derive(Debug, toasty::Model)]
1221 #[allow(dead_code)]
1222 struct User {
1223 #[key]
1224 #[auto]
1225 id: ID,
1226
1227 name: String,
1228
1229 #[has_one]
1230 profile: toasty::HasOne<Option<Profile>>,
1231 }
1232
1233 #[derive(Debug, toasty::Model)]
1234 #[allow(dead_code)]
1235 struct Profile {
1236 #[key]
1237 #[auto]
1238 id: ID,
1239
1240 bio: String,
1241
1242 #[unique]
1243 user_id: Option<ID>,
1244
1245 #[belongs_to(key = user_id, references = id)]
1246 user: toasty::BelongsTo<Option<User>>,
1247
1248 #[has_many]
1249 badges: toasty::HasMany<Badge>,
1250 }
1251
1252 #[derive(Debug, toasty::Model)]
1253 #[allow(dead_code)]
1254 struct Badge {
1255 #[key]
1256 #[auto]
1257 id: ID,
1258
1259 label: String,
1260
1261 #[index]
1262 profile_id: ID,
1263
1264 #[belongs_to(key = profile_id, references = id)]
1265 profile: toasty::BelongsTo<Profile>,
1266 }
1267
1268 let mut db = test.setup_db(models!(User, Profile, Badge)).await;
1269
1270 let user = User::create()
1272 .name("Alice")
1273 .profile(
1274 Profile::create()
1275 .bio("hi")
1276 .badge(Badge::create().label("Gold"))
1277 .badge(Badge::create().label("Silver")),
1278 )
1279 .exec(&mut db)
1280 .await?;
1281
1282 let user = User::filter_by_id(user.id)
1283 .include(User::fields().profile().badges())
1284 .get(&mut db)
1285 .await?;
1286
1287 let profile = user.profile.get().as_ref().unwrap();
1288 assert_eq!("hi", profile.bio);
1289 let mut labels: Vec<&str> = profile
1290 .badges
1291 .get()
1292 .iter()
1293 .map(|b| b.label.as_str())
1294 .collect();
1295 labels.sort();
1296 assert_eq!(labels, vec!["Gold", "Silver"]);
1297
1298 let user2 = User::create().name("Bob").exec(&mut db).await?;
1300
1301 let user2 = User::filter_by_id(user2.id)
1302 .include(User::fields().profile().badges())
1303 .get(&mut db)
1304 .await?;
1305
1306 assert!(user2.profile.get().is_none());
1307
1308 Ok(())
1309}
1310
1311#[driver_test(id(ID))]
1314pub async fn nested_has_one_required_then_has_many(test: &mut Test) -> Result<()> {
1315 #[derive(Debug, toasty::Model)]
1316 #[allow(dead_code)]
1317 struct Order {
1318 #[key]
1319 #[auto]
1320 id: ID,
1321
1322 label: String,
1323
1324 #[has_one]
1325 invoice: toasty::HasOne<Invoice>,
1326 }
1327
1328 #[derive(Debug, toasty::Model)]
1329 #[allow(dead_code)]
1330 struct Invoice {
1331 #[key]
1332 #[auto]
1333 id: ID,
1334
1335 code: String,
1336
1337 #[unique]
1338 order_id: Option<ID>,
1339
1340 #[belongs_to(key = order_id, references = id)]
1341 order: toasty::BelongsTo<Option<Order>>,
1342
1343 #[has_many]
1344 line_items: toasty::HasMany<LineItem>,
1345 }
1346
1347 #[derive(Debug, toasty::Model)]
1348 #[allow(dead_code)]
1349 struct LineItem {
1350 #[key]
1351 #[auto]
1352 id: ID,
1353
1354 description: String,
1355
1356 #[index]
1357 invoice_id: ID,
1358
1359 #[belongs_to(key = invoice_id, references = id)]
1360 invoice: toasty::BelongsTo<Invoice>,
1361 }
1362
1363 let mut db = test.setup_db(models!(Order, Invoice, LineItem)).await;
1364
1365 let order = Order::create()
1366 .label("Order1")
1367 .invoice(
1368 Invoice::create()
1369 .code("INV-001")
1370 .line_item(LineItem::create().description("Widget"))
1371 .line_item(LineItem::create().description("Gadget")),
1372 )
1373 .exec(&mut db)
1374 .await?;
1375
1376 let order = Order::filter_by_id(order.id)
1377 .include(Order::fields().invoice().line_items())
1378 .get(&mut db)
1379 .await?;
1380
1381 let invoice = order.invoice.get();
1382 assert_eq!("INV-001", invoice.code);
1383 let mut descs: Vec<&str> = invoice
1384 .line_items
1385 .get()
1386 .iter()
1387 .map(|li| li.description.as_str())
1388 .collect();
1389 descs.sort();
1390 assert_eq!(descs, vec!["Gadget", "Widget"]);
1391
1392 Ok(())
1393}
1394
1395#[driver_test(id(ID))]
1398pub async fn nested_has_one_optional_then_has_one_optional(test: &mut Test) -> Result<()> {
1399 #[derive(Debug, toasty::Model)]
1400 #[allow(dead_code)]
1401 struct User {
1402 #[key]
1403 #[auto]
1404 id: ID,
1405
1406 name: String,
1407
1408 #[has_one]
1409 profile: toasty::HasOne<Option<Profile>>,
1410 }
1411
1412 #[derive(Debug, toasty::Model)]
1413 #[allow(dead_code)]
1414 struct Profile {
1415 #[key]
1416 #[auto]
1417 id: ID,
1418
1419 bio: String,
1420
1421 #[unique]
1422 user_id: Option<ID>,
1423
1424 #[belongs_to(key = user_id, references = id)]
1425 user: toasty::BelongsTo<Option<User>>,
1426
1427 #[has_one]
1428 avatar: toasty::HasOne<Option<Avatar>>,
1429 }
1430
1431 #[derive(Debug, toasty::Model)]
1432 #[allow(dead_code)]
1433 struct Avatar {
1434 #[key]
1435 #[auto]
1436 id: ID,
1437
1438 url: String,
1439
1440 #[unique]
1441 profile_id: Option<ID>,
1442
1443 #[belongs_to(key = profile_id, references = id)]
1444 profile: toasty::BelongsTo<Option<Profile>>,
1445 }
1446
1447 let mut db = test.setup_db(models!(User, Profile, Avatar)).await;
1448
1449 let user = User::create()
1451 .name("Alice")
1452 .profile(
1453 Profile::create()
1454 .bio("hi")
1455 .avatar(Avatar::create().url("pic.png")),
1456 )
1457 .exec(&mut db)
1458 .await?;
1459
1460 let user = User::filter_by_id(user.id)
1461 .include(User::fields().profile().avatar())
1462 .get(&mut db)
1463 .await?;
1464
1465 let profile = user.profile.get().as_ref().unwrap();
1466 assert_eq!("hi", profile.bio);
1467 let avatar = profile.avatar.get().as_ref().unwrap();
1468 assert_eq!("pic.png", avatar.url);
1469
1470 let user2 = User::create()
1472 .name("Bob")
1473 .profile(Profile::create().bio("no pic"))
1474 .exec(&mut db)
1475 .await?;
1476
1477 let user2 = User::filter_by_id(user2.id)
1478 .include(User::fields().profile().avatar())
1479 .get(&mut db)
1480 .await?;
1481
1482 let profile2 = user2.profile.get().as_ref().unwrap();
1483 assert_eq!("no pic", profile2.bio);
1484 assert!(profile2.avatar.get().is_none());
1485
1486 let user3 = User::create().name("Carol").exec(&mut db).await?;
1488
1489 let user3 = User::filter_by_id(user3.id)
1490 .include(User::fields().profile().avatar())
1491 .get(&mut db)
1492 .await?;
1493
1494 assert!(user3.profile.get().is_none());
1495
1496 Ok(())
1497}
1498
1499#[driver_test(id(ID))]
1502pub async fn nested_has_one_required_then_has_one_required(test: &mut Test) -> Result<()> {
1503 #[derive(Debug, toasty::Model)]
1504 #[allow(dead_code)]
1505 struct User {
1506 #[key]
1507 #[auto]
1508 id: ID,
1509
1510 name: String,
1511
1512 #[has_one]
1513 profile: toasty::HasOne<Profile>,
1514 }
1515
1516 #[derive(Debug, toasty::Model)]
1517 #[allow(dead_code)]
1518 struct Profile {
1519 #[key]
1520 #[auto]
1521 id: ID,
1522
1523 bio: String,
1524
1525 #[unique]
1526 user_id: Option<ID>,
1527
1528 #[belongs_to(key = user_id, references = id)]
1529 user: toasty::BelongsTo<Option<User>>,
1530
1531 #[has_one]
1532 avatar: toasty::HasOne<Avatar>,
1533 }
1534
1535 #[derive(Debug, toasty::Model)]
1536 #[allow(dead_code)]
1537 struct Avatar {
1538 #[key]
1539 #[auto]
1540 id: ID,
1541
1542 url: String,
1543
1544 #[unique]
1545 profile_id: Option<ID>,
1546
1547 #[belongs_to(key = profile_id, references = id)]
1548 profile: toasty::BelongsTo<Option<Profile>>,
1549 }
1550
1551 let mut db = test.setup_db(models!(User, Profile, Avatar)).await;
1552
1553 let user = User::create()
1554 .name("Alice")
1555 .profile(
1556 Profile::create()
1557 .bio("engineer")
1558 .avatar(Avatar::create().url("alice.jpg")),
1559 )
1560 .exec(&mut db)
1561 .await?;
1562
1563 let user = User::filter_by_id(user.id)
1564 .include(User::fields().profile().avatar())
1565 .get(&mut db)
1566 .await?;
1567
1568 let profile = user.profile.get();
1569 assert_eq!("engineer", profile.bio);
1570 let avatar = profile.avatar.get();
1571 assert_eq!("alice.jpg", avatar.url);
1572
1573 Ok(())
1574}
1575
1576#[driver_test(id(ID))]
1579pub async fn nested_has_one_optional_then_belongs_to_required(test: &mut Test) -> Result<()> {
1580 #[derive(Debug, toasty::Model)]
1581 #[allow(dead_code)]
1582 struct User {
1583 #[key]
1584 #[auto]
1585 id: ID,
1586
1587 name: String,
1588
1589 #[has_one]
1590 review: toasty::HasOne<Option<Review>>,
1591 }
1592
1593 #[derive(Debug, toasty::Model)]
1594 #[allow(dead_code)]
1595 struct Product {
1596 #[key]
1597 #[auto]
1598 id: ID,
1599
1600 name: String,
1601 }
1602
1603 #[derive(Debug, toasty::Model)]
1604 #[allow(dead_code)]
1605 struct Review {
1606 #[key]
1607 #[auto]
1608 id: ID,
1609
1610 body: String,
1611
1612 #[unique]
1613 user_id: Option<ID>,
1614
1615 #[belongs_to(key = user_id, references = id)]
1616 user: toasty::BelongsTo<Option<User>>,
1617
1618 #[index]
1619 product_id: ID,
1620
1621 #[belongs_to(key = product_id, references = id)]
1622 product: toasty::BelongsTo<Product>,
1623 }
1624
1625 let mut db = test.setup_db(models!(User, Product, Review)).await;
1626
1627 let product = Product::create().name("Widget").exec(&mut db).await?;
1628
1629 let user = User::create()
1630 .name("Alice")
1631 .review(Review::create().body("Great!").product(&product))
1632 .exec(&mut db)
1633 .await?;
1634
1635 let user = User::filter_by_id(user.id)
1637 .include(User::fields().review().product())
1638 .get(&mut db)
1639 .await?;
1640
1641 let review = user.review.get().as_ref().unwrap();
1642 assert_eq!("Great!", review.body);
1643 assert_eq!("Widget", review.product.get().name);
1644
1645 let user2 = User::create().name("Bob").exec(&mut db).await?;
1647
1648 let user2 = User::filter_by_id(user2.id)
1649 .include(User::fields().review().product())
1650 .get(&mut db)
1651 .await?;
1652
1653 assert!(user2.review.get().is_none());
1654
1655 Ok(())
1656}
1657
1658#[driver_test(id(ID))]
1661pub async fn nested_belongs_to_required_then_has_many(test: &mut Test) -> Result<()> {
1662 #[derive(Debug, toasty::Model)]
1663 #[allow(dead_code)]
1664 struct Post {
1665 #[key]
1666 #[auto]
1667 id: ID,
1668
1669 title: String,
1670
1671 #[has_many]
1672 tags: toasty::HasMany<Tag>,
1673 }
1674
1675 #[derive(Debug, toasty::Model)]
1676 #[allow(dead_code)]
1677 struct Tag {
1678 #[key]
1679 #[auto]
1680 id: ID,
1681
1682 label: String,
1683
1684 #[index]
1685 post_id: ID,
1686
1687 #[belongs_to(key = post_id, references = id)]
1688 post: toasty::BelongsTo<Post>,
1689 }
1690
1691 #[derive(Debug, toasty::Model)]
1692 #[allow(dead_code)]
1693 struct Comment {
1694 #[key]
1695 #[auto]
1696 id: ID,
1697
1698 body: String,
1699
1700 #[index]
1701 post_id: ID,
1702
1703 #[belongs_to(key = post_id, references = id)]
1704 post: toasty::BelongsTo<Post>,
1705 }
1706
1707 let mut db = test.setup_db(models!(Post, Tag, Comment)).await;
1708
1709 let post = Post::create()
1710 .title("Hello")
1711 .tag(Tag::create().label("rust"))
1712 .tag(Tag::create().label("orm"))
1713 .exec(&mut db)
1714 .await?;
1715
1716 let comment = Comment::create()
1717 .body("Nice post")
1718 .post(&post)
1719 .exec(&mut db)
1720 .await?;
1721
1722 let comment = Comment::filter_by_id(comment.id)
1724 .include(Comment::fields().post().tags())
1725 .get(&mut db)
1726 .await?;
1727
1728 assert_eq!("Hello", comment.post.get().title);
1729 let mut labels: Vec<&str> = comment
1730 .post
1731 .get()
1732 .tags
1733 .get()
1734 .iter()
1735 .map(|t| t.label.as_str())
1736 .collect();
1737 labels.sort();
1738 assert_eq!(labels, vec!["orm", "rust"]);
1739
1740 Ok(())
1741}
1742
1743#[driver_test(id(ID))]
1746pub async fn nested_belongs_to_required_then_has_one_optional(test: &mut Test) -> Result<()> {
1747 #[derive(Debug, toasty::Model)]
1748 #[allow(dead_code)]
1749 struct User {
1750 #[key]
1751 #[auto]
1752 id: ID,
1753
1754 name: String,
1755
1756 #[has_one]
1757 profile: toasty::HasOne<Option<Profile>>,
1758
1759 #[has_many]
1760 todos: toasty::HasMany<Todo>,
1761 }
1762
1763 #[derive(Debug, toasty::Model)]
1764 #[allow(dead_code)]
1765 struct Profile {
1766 #[key]
1767 #[auto]
1768 id: ID,
1769
1770 bio: String,
1771
1772 #[unique]
1773 user_id: Option<ID>,
1774
1775 #[belongs_to(key = user_id, references = id)]
1776 user: toasty::BelongsTo<Option<User>>,
1777 }
1778
1779 #[derive(Debug, toasty::Model)]
1780 #[allow(dead_code)]
1781 struct Todo {
1782 #[key]
1783 #[auto]
1784 id: ID,
1785
1786 title: String,
1787
1788 #[index]
1789 user_id: ID,
1790
1791 #[belongs_to(key = user_id, references = id)]
1792 user: toasty::BelongsTo<User>,
1793 }
1794
1795 let mut db = test.setup_db(models!(User, Profile, Todo)).await;
1796
1797 let user = User::create()
1799 .name("Alice")
1800 .profile(Profile::create().bio("developer"))
1801 .todo(Todo::create().title("Task 1"))
1802 .exec(&mut db)
1803 .await?;
1804
1805 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1806
1807 let todo = Todo::filter_by_id(todo_id)
1808 .include(Todo::fields().user().profile())
1809 .get(&mut db)
1810 .await?;
1811
1812 assert_eq!("Alice", todo.user.get().name);
1813 let profile = todo.user.get().profile.get().as_ref().unwrap();
1814 assert_eq!("developer", profile.bio);
1815
1816 let user2 = User::create()
1818 .name("Bob")
1819 .todo(Todo::create().title("Task 2"))
1820 .exec(&mut db)
1821 .await?;
1822
1823 let todo2_id = Todo::get_by_user_id(&mut db, user2.id).await?.id;
1824
1825 let todo2 = Todo::filter_by_id(todo2_id)
1826 .include(Todo::fields().user().profile())
1827 .get(&mut db)
1828 .await?;
1829
1830 assert_eq!("Bob", todo2.user.get().name);
1831 assert!(todo2.user.get().profile.get().is_none());
1832
1833 Ok(())
1834}
1835
1836#[driver_test(id(ID))]
1839pub async fn nested_belongs_to_required_then_belongs_to_required(test: &mut Test) -> Result<()> {
1840 #[derive(Debug, toasty::Model)]
1841 #[allow(dead_code)]
1842 struct User {
1843 #[key]
1844 #[auto]
1845 id: ID,
1846
1847 name: String,
1848
1849 #[has_many]
1850 todos: toasty::HasMany<Todo>,
1851 }
1852
1853 #[derive(Debug, toasty::Model)]
1854 #[allow(dead_code)]
1855 struct Todo {
1856 #[key]
1857 #[auto]
1858 id: ID,
1859
1860 title: String,
1861
1862 #[index]
1863 user_id: ID,
1864
1865 #[belongs_to(key = user_id, references = id)]
1866 user: toasty::BelongsTo<User>,
1867
1868 #[has_many]
1869 steps: toasty::HasMany<Step>,
1870 }
1871
1872 #[derive(Debug, toasty::Model)]
1873 #[allow(dead_code)]
1874 struct Step {
1875 #[key]
1876 #[auto]
1877 id: ID,
1878
1879 description: String,
1880
1881 #[index]
1882 todo_id: ID,
1883
1884 #[belongs_to(key = todo_id, references = id)]
1885 todo: toasty::BelongsTo<Todo>,
1886 }
1887
1888 let mut db = test.setup_db(models!(User, Todo, Step)).await;
1889
1890 let user = User::create()
1891 .name("Alice")
1892 .todo(
1893 Todo::create()
1894 .title("T1")
1895 .step(Step::create().description("S1")),
1896 )
1897 .exec(&mut db)
1898 .await?;
1899
1900 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1901 let step_id = Step::get_by_todo_id(&mut db, todo_id).await?.id;
1902
1903 let step = Step::filter_by_id(step_id)
1905 .include(Step::fields().todo().user())
1906 .get(&mut db)
1907 .await?;
1908
1909 assert_eq!("T1", step.todo.get().title);
1910 assert_eq!("Alice", step.todo.get().user.get().name);
1911
1912 Ok(())
1913}
1914
1915#[driver_test(id(ID))]
1918pub async fn nested_belongs_to_optional_then_has_many(test: &mut Test) -> Result<()> {
1919 #[derive(Debug, toasty::Model)]
1920 #[allow(dead_code)]
1921 struct Project {
1922 #[key]
1923 #[auto]
1924 id: ID,
1925
1926 name: String,
1927
1928 #[has_many]
1929 members: toasty::HasMany<Member>,
1930 }
1931
1932 #[derive(Debug, toasty::Model)]
1933 #[allow(dead_code)]
1934 struct Member {
1935 #[key]
1936 #[auto]
1937 id: ID,
1938
1939 name: String,
1940
1941 #[index]
1942 project_id: ID,
1943
1944 #[belongs_to(key = project_id, references = id)]
1945 project: toasty::BelongsTo<Project>,
1946 }
1947
1948 #[derive(Debug, toasty::Model)]
1949 #[allow(dead_code)]
1950 struct Task {
1951 #[key]
1952 #[auto]
1953 id: ID,
1954
1955 title: String,
1956
1957 #[index]
1958 project_id: Option<ID>,
1959
1960 #[belongs_to(key = project_id, references = id)]
1961 project: toasty::BelongsTo<Option<Project>>,
1962 }
1963
1964 let mut db = test.setup_db(models!(Project, Member, Task)).await;
1965
1966 let project = Project::create()
1967 .name("Proj1")
1968 .member(Member::create().name("Alice"))
1969 .member(Member::create().name("Bob"))
1970 .exec(&mut db)
1971 .await?;
1972
1973 let task = Task::create()
1975 .title("Linked")
1976 .project(&project)
1977 .exec(&mut db)
1978 .await?;
1979
1980 let task = Task::filter_by_id(task.id)
1981 .include(Task::fields().project().members())
1982 .get(&mut db)
1983 .await?;
1984
1985 let proj = task.project.get().as_ref().unwrap();
1986 assert_eq!("Proj1", proj.name);
1987 let mut names: Vec<&str> = proj.members.get().iter().map(|m| m.name.as_str()).collect();
1988 names.sort();
1989 assert_eq!(names, vec!["Alice", "Bob"]);
1990
1991 let orphan = Task::create().title("Orphan").exec(&mut db).await?;
1993
1994 let orphan = Task::filter_by_id(orphan.id)
1995 .include(Task::fields().project().members())
1996 .get(&mut db)
1997 .await?;
1998
1999 assert!(orphan.project.get().is_none());
2000
2001 Ok(())
2002}
2003
2004#[driver_test(id(ID))]
2007pub async fn nested_belongs_to_optional_then_belongs_to_optional(test: &mut Test) -> Result<()> {
2008 #[derive(Debug, toasty::Model)]
2009 #[allow(dead_code)]
2010 struct Category {
2011 #[key]
2012 #[auto]
2013 id: ID,
2014
2015 name: String,
2016 }
2017
2018 #[derive(Debug, toasty::Model)]
2019 #[allow(dead_code)]
2020 struct Post {
2021 #[key]
2022 #[auto]
2023 id: ID,
2024
2025 title: String,
2026
2027 #[index]
2028 category_id: Option<ID>,
2029
2030 #[belongs_to(key = category_id, references = id)]
2031 category: toasty::BelongsTo<Option<Category>>,
2032 }
2033
2034 #[derive(Debug, toasty::Model)]
2035 #[allow(dead_code)]
2036 struct Comment {
2037 #[key]
2038 #[auto]
2039 id: ID,
2040
2041 body: String,
2042
2043 #[index]
2044 post_id: Option<ID>,
2045
2046 #[belongs_to(key = post_id, references = id)]
2047 post: toasty::BelongsTo<Option<Post>>,
2048 }
2049
2050 let mut db = test.setup_db(models!(Category, Post, Comment)).await;
2051
2052 let cat = Category::create().name("Tech").exec(&mut db).await?;
2053 let post = Post::create()
2054 .title("Hello")
2055 .category(&cat)
2056 .exec(&mut db)
2057 .await?;
2058
2059 let c1 = Comment::create()
2061 .body("Nice")
2062 .post(&post)
2063 .exec(&mut db)
2064 .await?;
2065
2066 let c1 = Comment::filter_by_id(c1.id)
2067 .include(Comment::fields().post().category())
2068 .get(&mut db)
2069 .await?;
2070
2071 let loaded_post = c1.post.get().as_ref().unwrap();
2072 assert_eq!("Hello", loaded_post.title);
2073 let loaded_cat = loaded_post.category.get().as_ref().unwrap();
2074 assert_eq!("Tech", loaded_cat.name);
2075
2076 let post2 = Post::create().title("Uncategorized").exec(&mut db).await?;
2078 let c2 = Comment::create()
2079 .body("Hmm")
2080 .post(&post2)
2081 .exec(&mut db)
2082 .await?;
2083
2084 let c2 = Comment::filter_by_id(c2.id)
2085 .include(Comment::fields().post().category())
2086 .get(&mut db)
2087 .await?;
2088
2089 let loaded_post2 = c2.post.get().as_ref().unwrap();
2090 assert_eq!("Uncategorized", loaded_post2.title);
2091 assert!(loaded_post2.category.get().is_none());
2092
2093 let c3 = Comment::create().body("Orphan").exec(&mut db).await?;
2095
2096 let c3 = Comment::filter_by_id(c3.id)
2097 .include(Comment::fields().post().category())
2098 .get(&mut db)
2099 .await?;
2100
2101 assert!(c3.post.get().is_none());
2102
2103 Ok(())
2104}
2105
2106#[driver_test(id(ID))]
2109pub async fn nested_belongs_to_required_then_has_one_required(test: &mut Test) -> Result<()> {
2110 #[derive(Debug, toasty::Model)]
2111 #[allow(dead_code)]
2112 struct User {
2113 #[key]
2114 #[auto]
2115 id: ID,
2116
2117 name: String,
2118
2119 #[has_one]
2120 config: toasty::HasOne<Config>,
2121
2122 #[has_many]
2123 todos: toasty::HasMany<Todo>,
2124 }
2125
2126 #[derive(Debug, toasty::Model)]
2127 #[allow(dead_code)]
2128 struct Config {
2129 #[key]
2130 #[auto]
2131 id: ID,
2132
2133 theme: String,
2134
2135 #[unique]
2136 user_id: Option<ID>,
2137
2138 #[belongs_to(key = user_id, references = id)]
2139 user: toasty::BelongsTo<Option<User>>,
2140 }
2141
2142 #[derive(Debug, toasty::Model)]
2143 #[allow(dead_code)]
2144 struct Todo {
2145 #[key]
2146 #[auto]
2147 id: ID,
2148
2149 title: String,
2150
2151 #[index]
2152 user_id: ID,
2153
2154 #[belongs_to(key = user_id, references = id)]
2155 user: toasty::BelongsTo<User>,
2156 }
2157
2158 let mut db = test.setup_db(models!(User, Config, Todo)).await;
2159
2160 let user = User::create()
2161 .name("Alice")
2162 .config(Config::create().theme("dark"))
2163 .todo(Todo::create().title("Task"))
2164 .exec(&mut db)
2165 .await?;
2166
2167 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
2168
2169 let todo = Todo::filter_by_id(todo_id)
2170 .include(Todo::fields().user().config())
2171 .get(&mut db)
2172 .await?;
2173
2174 assert_eq!("Alice", todo.user.get().name);
2175 assert_eq!("dark", todo.user.get().config.get().theme);
2176
2177 Ok(())
2178}
2179
2180#[driver_test(id(ID))]
2184pub async fn nested_has_many_then_has_many_with_empty_leaves(test: &mut Test) {
2185 #[derive(Debug, toasty::Model)]
2186 #[allow(dead_code)]
2187 struct User {
2188 #[key]
2189 #[auto]
2190 id: ID,
2191
2192 name: String,
2193
2194 #[has_many]
2195 todos: toasty::HasMany<Todo>,
2196 }
2197
2198 #[derive(Debug, toasty::Model)]
2199 #[allow(dead_code)]
2200 struct Todo {
2201 #[key]
2202 #[auto]
2203 id: ID,
2204
2205 title: String,
2206
2207 #[index]
2208 user_id: ID,
2209
2210 #[belongs_to(key = user_id, references = id)]
2211 user: toasty::BelongsTo<User>,
2212
2213 #[has_many]
2214 steps: toasty::HasMany<Step>,
2215 }
2216
2217 #[derive(Debug, toasty::Model)]
2218 #[allow(dead_code)]
2219 struct Step {
2220 #[key]
2221 #[auto]
2222 id: ID,
2223
2224 description: String,
2225
2226 #[index]
2227 todo_id: ID,
2228
2229 #[belongs_to(key = todo_id, references = id)]
2230 todo: toasty::BelongsTo<Todo>,
2231 }
2232
2233 let mut db = test.setup_db(models!(User, Todo, Step)).await;
2234
2235 let user = User::create()
2236 .name("Alice")
2237 .todo(
2238 Todo::create()
2239 .title("With Steps")
2240 .step(Step::create().description("S1")),
2241 )
2242 .todo(Todo::create().title("No Steps")) .exec(&mut db)
2244 .await
2245 .unwrap();
2246
2247 let user = User::filter_by_id(user.id)
2248 .include(User::fields().todos().steps())
2249 .get(&mut db)
2250 .await
2251 .unwrap();
2252
2253 let todos = user.todos.get();
2254 assert_eq!(2, todos.len());
2255
2256 let mut total_steps = 0;
2257 for todo in todos {
2258 let steps = todo.steps.get();
2259 if todo.title == "With Steps" {
2260 assert_eq!(1, steps.len());
2261 assert_eq!("S1", steps[0].description);
2262 } else {
2263 assert_eq!(0, steps.len());
2264 }
2265 total_steps += steps.len();
2266 }
2267 assert_eq!(1, total_steps);
2268}
2269
2270#[driver_test(
2275 id(ID),
2276 requires(sql),
2277 scenario(crate::scenarios::has_many_multi_relation)
2278)]
2279pub async fn sibling_nested_includes_on_shared_prefix(test: &mut Test) -> Result<()> {
2280 let mut db = setup(test).await;
2281
2282 let category = toasty::create!(Category { name: "Food" })
2283 .exec(&mut db)
2284 .await?;
2285 let user = toasty::create!(User {
2286 name: "Alice",
2287 todos: [
2288 { title: "T1", category: &category },
2289 { title: "T2", category: &category },
2290 ],
2291 })
2292 .exec(&mut db)
2293 .await?;
2294
2295 let loaded = User::filter_by_id(user.id)
2298 .include(User::fields().todos().user())
2299 .include(User::fields().todos().category())
2300 .get(&mut db)
2301 .await?;
2302
2303 let todos = loaded.todos.get();
2304 assert_eq!(2, todos.len());
2305 for todo in todos {
2306 assert_eq!("Alice", todo.user.get().name);
2307 assert_eq!("Food", todo.category.get().name);
2308 }
2309
2310 Ok(())
2311}
2312
2313#[driver_test(
2317 id(ID),
2318 requires(sql),
2319 scenario(crate::scenarios::has_many_multi_relation)
2320)]
2321pub async fn bare_and_nested_includes_on_shared_prefix(test: &mut Test) -> Result<()> {
2322 let mut db = setup(test).await;
2323
2324 let category = toasty::create!(Category { name: "Food" })
2325 .exec(&mut db)
2326 .await?;
2327 let user = toasty::create!(User {
2328 name: "Alice",
2329 todos: [{ title: "T1", category: &category }],
2330 })
2331 .exec(&mut db)
2332 .await?;
2333
2334 let loaded = User::filter_by_id(user.id)
2335 .include(User::fields().todos()) .include(User::fields().todos().user()) .include(User::fields().todos().category()) .get(&mut db)
2339 .await?;
2340
2341 let todos = loaded.todos.get();
2342 assert_eq!(1, todos.len());
2343 assert_eq!("Alice", todos[0].user.get().name);
2344 assert_eq!("Food", todos[0].category.get().name);
2345
2346 Ok(())
2347}
2348
2349#[driver_test(id(ID))]
2354pub async fn sibling_nested_includes_on_shared_prefix_non_sql(test: &mut Test) -> Result<()> {
2355 #[derive(Debug, toasty::Model)]
2356 #[allow(dead_code)]
2357 struct Category {
2358 #[key]
2359 #[auto]
2360 id: ID,
2361
2362 name: String,
2363
2364 #[has_many]
2365 items: toasty::HasMany<Item>,
2366 }
2367
2368 #[derive(Debug, toasty::Model)]
2369 #[allow(dead_code)]
2370 struct Brand {
2371 #[key]
2372 #[auto]
2373 id: ID,
2374
2375 name: String,
2376 }
2377
2378 #[derive(Debug, toasty::Model)]
2379 #[allow(dead_code)]
2380 struct Supplier {
2381 #[key]
2382 #[auto]
2383 id: ID,
2384
2385 name: String,
2386 }
2387
2388 #[derive(Debug, toasty::Model)]
2389 #[allow(dead_code)]
2390 struct Item {
2391 #[key]
2392 #[auto]
2393 id: ID,
2394
2395 title: String,
2396
2397 #[index]
2398 category_id: ID,
2399
2400 #[belongs_to(key = category_id, references = id)]
2401 category: toasty::BelongsTo<Category>,
2402
2403 #[index]
2404 brand_id: ID,
2405
2406 #[belongs_to(key = brand_id, references = id)]
2407 brand: toasty::BelongsTo<Brand>,
2408
2409 #[index]
2410 supplier_id: ID,
2411
2412 #[belongs_to(key = supplier_id, references = id)]
2413 supplier: toasty::BelongsTo<Supplier>,
2414 }
2415
2416 let mut db = test
2417 .setup_db(models!(Category, Brand, Supplier, Item))
2418 .await;
2419
2420 let brand_a = toasty::create!(Brand { name: "BrandA" })
2421 .exec(&mut db)
2422 .await?;
2423 let brand_b = toasty::create!(Brand { name: "BrandB" })
2424 .exec(&mut db)
2425 .await?;
2426 let sup_a = toasty::create!(Supplier { name: "SupA" })
2427 .exec(&mut db)
2428 .await?;
2429 let sup_b = toasty::create!(Supplier { name: "SupB" })
2430 .exec(&mut db)
2431 .await?;
2432
2433 let cat = toasty::create!(Category {
2434 name: "Electronics",
2435 items: [
2436 { title: "Phone", brand: &brand_a, supplier: &sup_a },
2437 { title: "Laptop", brand: &brand_b, supplier: &sup_b },
2438 ],
2439 })
2440 .exec(&mut db)
2441 .await?;
2442
2443 let loaded = Category::filter_by_id(cat.id)
2446 .include(Category::fields().items().brand())
2447 .include(Category::fields().items().supplier())
2448 .get(&mut db)
2449 .await?;
2450
2451 let items = loaded.items.get();
2452 assert_eq!(2, items.len());
2453 let mut pairs: Vec<(&str, &str)> = items
2454 .iter()
2455 .map(|i| (i.brand.get().name.as_str(), i.supplier.get().name.as_str()))
2456 .collect();
2457 pairs.sort();
2458 assert_eq!(pairs, vec![("BrandA", "SupA"), ("BrandB", "SupB")]);
2459
2460 Ok(())
2461}