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))]
1059pub async fn nested_has_many_then_belongs_to_optional(test: &mut Test) -> Result<()> {
1060 #[derive(Debug, toasty::Model)]
1061 #[allow(dead_code)]
1062 struct Team {
1063 #[key]
1064 #[auto]
1065 id: ID,
1066
1067 name: String,
1068
1069 #[has_many]
1070 tasks: toasty::HasMany<Task>,
1071 }
1072
1073 #[derive(Debug, toasty::Model)]
1074 #[allow(dead_code)]
1075 struct Assignee {
1076 #[key]
1077 #[auto]
1078 id: ID,
1079
1080 name: String,
1081 }
1082
1083 #[derive(Debug, toasty::Model)]
1084 #[allow(dead_code)]
1085 struct Task {
1086 #[key]
1087 #[auto]
1088 id: ID,
1089
1090 title: String,
1091
1092 #[index]
1093 team_id: ID,
1094
1095 #[belongs_to(key = team_id, references = id)]
1096 team: toasty::BelongsTo<Team>,
1097
1098 #[index]
1099 assignee_id: Option<ID>,
1100
1101 #[belongs_to(key = assignee_id, references = id)]
1102 assignee: toasty::BelongsTo<Option<Assignee>>,
1103 }
1104
1105 let mut db = test.setup_db(models!(Team, Assignee, Task)).await;
1106
1107 let person = Assignee::create().name("Alice").exec(&mut db).await?;
1108
1109 let team = Team::create()
1110 .name("Engineering")
1111 .task(Task::create().title("Assigned").assignee(&person))
1112 .task(Task::create().title("Unassigned"))
1113 .exec(&mut db)
1114 .await?;
1115
1116 let team = Team::filter_by_id(team.id)
1117 .include(Team::fields().tasks().assignee())
1118 .get(&mut db)
1119 .await?;
1120
1121 let tasks = team.tasks.get();
1122 assert_eq!(2, tasks.len());
1123
1124 let mut assigned = 0;
1125 let mut unassigned = 0;
1126 for task in tasks {
1127 match task.assignee.get() {
1128 Some(a) => {
1129 assert_eq!("Alice", a.name);
1130 assigned += 1;
1131 }
1132 None => unassigned += 1,
1133 }
1134 }
1135 assert_eq!(1, assigned);
1136 assert_eq!(1, unassigned);
1137
1138 Ok(())
1139}
1140
1141#[driver_test(id(ID))]
1144pub async fn nested_has_one_optional_then_has_many(test: &mut Test) -> Result<()> {
1145 #[derive(Debug, toasty::Model)]
1146 #[allow(dead_code)]
1147 struct User {
1148 #[key]
1149 #[auto]
1150 id: ID,
1151
1152 name: String,
1153
1154 #[has_one]
1155 profile: toasty::HasOne<Option<Profile>>,
1156 }
1157
1158 #[derive(Debug, toasty::Model)]
1159 #[allow(dead_code)]
1160 struct Profile {
1161 #[key]
1162 #[auto]
1163 id: ID,
1164
1165 bio: String,
1166
1167 #[unique]
1168 user_id: Option<ID>,
1169
1170 #[belongs_to(key = user_id, references = id)]
1171 user: toasty::BelongsTo<Option<User>>,
1172
1173 #[has_many]
1174 badges: toasty::HasMany<Badge>,
1175 }
1176
1177 #[derive(Debug, toasty::Model)]
1178 #[allow(dead_code)]
1179 struct Badge {
1180 #[key]
1181 #[auto]
1182 id: ID,
1183
1184 label: String,
1185
1186 #[index]
1187 profile_id: ID,
1188
1189 #[belongs_to(key = profile_id, references = id)]
1190 profile: toasty::BelongsTo<Profile>,
1191 }
1192
1193 let mut db = test.setup_db(models!(User, Profile, Badge)).await;
1194
1195 let user = User::create()
1197 .name("Alice")
1198 .profile(
1199 Profile::create()
1200 .bio("hi")
1201 .badge(Badge::create().label("Gold"))
1202 .badge(Badge::create().label("Silver")),
1203 )
1204 .exec(&mut db)
1205 .await?;
1206
1207 let user = User::filter_by_id(user.id)
1208 .include(User::fields().profile().badges())
1209 .get(&mut db)
1210 .await?;
1211
1212 let profile = user.profile.get().as_ref().unwrap();
1213 assert_eq!("hi", profile.bio);
1214 let mut labels: Vec<&str> = profile
1215 .badges
1216 .get()
1217 .iter()
1218 .map(|b| b.label.as_str())
1219 .collect();
1220 labels.sort();
1221 assert_eq!(labels, vec!["Gold", "Silver"]);
1222
1223 let user2 = User::create().name("Bob").exec(&mut db).await?;
1225
1226 let user2 = User::filter_by_id(user2.id)
1227 .include(User::fields().profile().badges())
1228 .get(&mut db)
1229 .await?;
1230
1231 assert!(user2.profile.get().is_none());
1232
1233 Ok(())
1234}
1235
1236#[driver_test(id(ID))]
1239pub async fn nested_has_one_required_then_has_many(test: &mut Test) -> Result<()> {
1240 #[derive(Debug, toasty::Model)]
1241 #[allow(dead_code)]
1242 struct Order {
1243 #[key]
1244 #[auto]
1245 id: ID,
1246
1247 label: String,
1248
1249 #[has_one]
1250 invoice: toasty::HasOne<Invoice>,
1251 }
1252
1253 #[derive(Debug, toasty::Model)]
1254 #[allow(dead_code)]
1255 struct Invoice {
1256 #[key]
1257 #[auto]
1258 id: ID,
1259
1260 code: String,
1261
1262 #[unique]
1263 order_id: Option<ID>,
1264
1265 #[belongs_to(key = order_id, references = id)]
1266 order: toasty::BelongsTo<Option<Order>>,
1267
1268 #[has_many]
1269 line_items: toasty::HasMany<LineItem>,
1270 }
1271
1272 #[derive(Debug, toasty::Model)]
1273 #[allow(dead_code)]
1274 struct LineItem {
1275 #[key]
1276 #[auto]
1277 id: ID,
1278
1279 description: String,
1280
1281 #[index]
1282 invoice_id: ID,
1283
1284 #[belongs_to(key = invoice_id, references = id)]
1285 invoice: toasty::BelongsTo<Invoice>,
1286 }
1287
1288 let mut db = test.setup_db(models!(Order, Invoice, LineItem)).await;
1289
1290 let order = Order::create()
1291 .label("Order1")
1292 .invoice(
1293 Invoice::create()
1294 .code("INV-001")
1295 .line_item(LineItem::create().description("Widget"))
1296 .line_item(LineItem::create().description("Gadget")),
1297 )
1298 .exec(&mut db)
1299 .await?;
1300
1301 let order = Order::filter_by_id(order.id)
1302 .include(Order::fields().invoice().line_items())
1303 .get(&mut db)
1304 .await?;
1305
1306 let invoice = order.invoice.get();
1307 assert_eq!("INV-001", invoice.code);
1308 let mut descs: Vec<&str> = invoice
1309 .line_items
1310 .get()
1311 .iter()
1312 .map(|li| li.description.as_str())
1313 .collect();
1314 descs.sort();
1315 assert_eq!(descs, vec!["Gadget", "Widget"]);
1316
1317 Ok(())
1318}
1319
1320#[driver_test(id(ID))]
1323pub async fn nested_has_one_optional_then_has_one_optional(test: &mut Test) -> Result<()> {
1324 #[derive(Debug, toasty::Model)]
1325 #[allow(dead_code)]
1326 struct User {
1327 #[key]
1328 #[auto]
1329 id: ID,
1330
1331 name: String,
1332
1333 #[has_one]
1334 profile: toasty::HasOne<Option<Profile>>,
1335 }
1336
1337 #[derive(Debug, toasty::Model)]
1338 #[allow(dead_code)]
1339 struct Profile {
1340 #[key]
1341 #[auto]
1342 id: ID,
1343
1344 bio: String,
1345
1346 #[unique]
1347 user_id: Option<ID>,
1348
1349 #[belongs_to(key = user_id, references = id)]
1350 user: toasty::BelongsTo<Option<User>>,
1351
1352 #[has_one]
1353 avatar: toasty::HasOne<Option<Avatar>>,
1354 }
1355
1356 #[derive(Debug, toasty::Model)]
1357 #[allow(dead_code)]
1358 struct Avatar {
1359 #[key]
1360 #[auto]
1361 id: ID,
1362
1363 url: String,
1364
1365 #[unique]
1366 profile_id: Option<ID>,
1367
1368 #[belongs_to(key = profile_id, references = id)]
1369 profile: toasty::BelongsTo<Option<Profile>>,
1370 }
1371
1372 let mut db = test.setup_db(models!(User, Profile, Avatar)).await;
1373
1374 let user = User::create()
1376 .name("Alice")
1377 .profile(
1378 Profile::create()
1379 .bio("hi")
1380 .avatar(Avatar::create().url("pic.png")),
1381 )
1382 .exec(&mut db)
1383 .await?;
1384
1385 let user = User::filter_by_id(user.id)
1386 .include(User::fields().profile().avatar())
1387 .get(&mut db)
1388 .await?;
1389
1390 let profile = user.profile.get().as_ref().unwrap();
1391 assert_eq!("hi", profile.bio);
1392 let avatar = profile.avatar.get().as_ref().unwrap();
1393 assert_eq!("pic.png", avatar.url);
1394
1395 let user2 = User::create()
1397 .name("Bob")
1398 .profile(Profile::create().bio("no pic"))
1399 .exec(&mut db)
1400 .await?;
1401
1402 let user2 = User::filter_by_id(user2.id)
1403 .include(User::fields().profile().avatar())
1404 .get(&mut db)
1405 .await?;
1406
1407 let profile2 = user2.profile.get().as_ref().unwrap();
1408 assert_eq!("no pic", profile2.bio);
1409 assert!(profile2.avatar.get().is_none());
1410
1411 let user3 = User::create().name("Carol").exec(&mut db).await?;
1413
1414 let user3 = User::filter_by_id(user3.id)
1415 .include(User::fields().profile().avatar())
1416 .get(&mut db)
1417 .await?;
1418
1419 assert!(user3.profile.get().is_none());
1420
1421 Ok(())
1422}
1423
1424#[driver_test(id(ID))]
1427pub async fn nested_has_one_required_then_has_one_required(test: &mut Test) -> Result<()> {
1428 #[derive(Debug, toasty::Model)]
1429 #[allow(dead_code)]
1430 struct User {
1431 #[key]
1432 #[auto]
1433 id: ID,
1434
1435 name: String,
1436
1437 #[has_one]
1438 profile: toasty::HasOne<Profile>,
1439 }
1440
1441 #[derive(Debug, toasty::Model)]
1442 #[allow(dead_code)]
1443 struct Profile {
1444 #[key]
1445 #[auto]
1446 id: ID,
1447
1448 bio: String,
1449
1450 #[unique]
1451 user_id: Option<ID>,
1452
1453 #[belongs_to(key = user_id, references = id)]
1454 user: toasty::BelongsTo<Option<User>>,
1455
1456 #[has_one]
1457 avatar: toasty::HasOne<Avatar>,
1458 }
1459
1460 #[derive(Debug, toasty::Model)]
1461 #[allow(dead_code)]
1462 struct Avatar {
1463 #[key]
1464 #[auto]
1465 id: ID,
1466
1467 url: String,
1468
1469 #[unique]
1470 profile_id: Option<ID>,
1471
1472 #[belongs_to(key = profile_id, references = id)]
1473 profile: toasty::BelongsTo<Option<Profile>>,
1474 }
1475
1476 let mut db = test.setup_db(models!(User, Profile, Avatar)).await;
1477
1478 let user = User::create()
1479 .name("Alice")
1480 .profile(
1481 Profile::create()
1482 .bio("engineer")
1483 .avatar(Avatar::create().url("alice.jpg")),
1484 )
1485 .exec(&mut db)
1486 .await?;
1487
1488 let user = User::filter_by_id(user.id)
1489 .include(User::fields().profile().avatar())
1490 .get(&mut db)
1491 .await?;
1492
1493 let profile = user.profile.get();
1494 assert_eq!("engineer", profile.bio);
1495 let avatar = profile.avatar.get();
1496 assert_eq!("alice.jpg", avatar.url);
1497
1498 Ok(())
1499}
1500
1501#[driver_test(id(ID))]
1504pub async fn nested_has_one_optional_then_belongs_to_required(test: &mut Test) -> Result<()> {
1505 #[derive(Debug, toasty::Model)]
1506 #[allow(dead_code)]
1507 struct User {
1508 #[key]
1509 #[auto]
1510 id: ID,
1511
1512 name: String,
1513
1514 #[has_one]
1515 review: toasty::HasOne<Option<Review>>,
1516 }
1517
1518 #[derive(Debug, toasty::Model)]
1519 #[allow(dead_code)]
1520 struct Product {
1521 #[key]
1522 #[auto]
1523 id: ID,
1524
1525 name: String,
1526 }
1527
1528 #[derive(Debug, toasty::Model)]
1529 #[allow(dead_code)]
1530 struct Review {
1531 #[key]
1532 #[auto]
1533 id: ID,
1534
1535 body: String,
1536
1537 #[unique]
1538 user_id: Option<ID>,
1539
1540 #[belongs_to(key = user_id, references = id)]
1541 user: toasty::BelongsTo<Option<User>>,
1542
1543 #[index]
1544 product_id: ID,
1545
1546 #[belongs_to(key = product_id, references = id)]
1547 product: toasty::BelongsTo<Product>,
1548 }
1549
1550 let mut db = test.setup_db(models!(User, Product, Review)).await;
1551
1552 let product = Product::create().name("Widget").exec(&mut db).await?;
1553
1554 let user = User::create()
1555 .name("Alice")
1556 .review(Review::create().body("Great!").product(&product))
1557 .exec(&mut db)
1558 .await?;
1559
1560 let user = User::filter_by_id(user.id)
1562 .include(User::fields().review().product())
1563 .get(&mut db)
1564 .await?;
1565
1566 let review = user.review.get().as_ref().unwrap();
1567 assert_eq!("Great!", review.body);
1568 assert_eq!("Widget", review.product.get().name);
1569
1570 let user2 = User::create().name("Bob").exec(&mut db).await?;
1572
1573 let user2 = User::filter_by_id(user2.id)
1574 .include(User::fields().review().product())
1575 .get(&mut db)
1576 .await?;
1577
1578 assert!(user2.review.get().is_none());
1579
1580 Ok(())
1581}
1582
1583#[driver_test(id(ID))]
1586pub async fn nested_belongs_to_required_then_has_many(test: &mut Test) -> Result<()> {
1587 #[derive(Debug, toasty::Model)]
1588 #[allow(dead_code)]
1589 struct Post {
1590 #[key]
1591 #[auto]
1592 id: ID,
1593
1594 title: String,
1595
1596 #[has_many]
1597 tags: toasty::HasMany<Tag>,
1598 }
1599
1600 #[derive(Debug, toasty::Model)]
1601 #[allow(dead_code)]
1602 struct Tag {
1603 #[key]
1604 #[auto]
1605 id: ID,
1606
1607 label: String,
1608
1609 #[index]
1610 post_id: ID,
1611
1612 #[belongs_to(key = post_id, references = id)]
1613 post: toasty::BelongsTo<Post>,
1614 }
1615
1616 #[derive(Debug, toasty::Model)]
1617 #[allow(dead_code)]
1618 struct Comment {
1619 #[key]
1620 #[auto]
1621 id: ID,
1622
1623 body: String,
1624
1625 #[index]
1626 post_id: ID,
1627
1628 #[belongs_to(key = post_id, references = id)]
1629 post: toasty::BelongsTo<Post>,
1630 }
1631
1632 let mut db = test.setup_db(models!(Post, Tag, Comment)).await;
1633
1634 let post = Post::create()
1635 .title("Hello")
1636 .tag(Tag::create().label("rust"))
1637 .tag(Tag::create().label("orm"))
1638 .exec(&mut db)
1639 .await?;
1640
1641 let comment = Comment::create()
1642 .body("Nice post")
1643 .post(&post)
1644 .exec(&mut db)
1645 .await?;
1646
1647 let comment = Comment::filter_by_id(comment.id)
1649 .include(Comment::fields().post().tags())
1650 .get(&mut db)
1651 .await?;
1652
1653 assert_eq!("Hello", comment.post.get().title);
1654 let mut labels: Vec<&str> = comment
1655 .post
1656 .get()
1657 .tags
1658 .get()
1659 .iter()
1660 .map(|t| t.label.as_str())
1661 .collect();
1662 labels.sort();
1663 assert_eq!(labels, vec!["orm", "rust"]);
1664
1665 Ok(())
1666}
1667
1668#[driver_test(id(ID))]
1671pub async fn nested_belongs_to_required_then_has_one_optional(test: &mut Test) -> Result<()> {
1672 #[derive(Debug, toasty::Model)]
1673 #[allow(dead_code)]
1674 struct User {
1675 #[key]
1676 #[auto]
1677 id: ID,
1678
1679 name: String,
1680
1681 #[has_one]
1682 profile: toasty::HasOne<Option<Profile>>,
1683
1684 #[has_many]
1685 todos: toasty::HasMany<Todo>,
1686 }
1687
1688 #[derive(Debug, toasty::Model)]
1689 #[allow(dead_code)]
1690 struct Profile {
1691 #[key]
1692 #[auto]
1693 id: ID,
1694
1695 bio: String,
1696
1697 #[unique]
1698 user_id: Option<ID>,
1699
1700 #[belongs_to(key = user_id, references = id)]
1701 user: toasty::BelongsTo<Option<User>>,
1702 }
1703
1704 #[derive(Debug, toasty::Model)]
1705 #[allow(dead_code)]
1706 struct Todo {
1707 #[key]
1708 #[auto]
1709 id: ID,
1710
1711 title: String,
1712
1713 #[index]
1714 user_id: ID,
1715
1716 #[belongs_to(key = user_id, references = id)]
1717 user: toasty::BelongsTo<User>,
1718 }
1719
1720 let mut db = test.setup_db(models!(User, Profile, Todo)).await;
1721
1722 let user = User::create()
1724 .name("Alice")
1725 .profile(Profile::create().bio("developer"))
1726 .todo(Todo::create().title("Task 1"))
1727 .exec(&mut db)
1728 .await?;
1729
1730 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1731
1732 let todo = Todo::filter_by_id(todo_id)
1733 .include(Todo::fields().user().profile())
1734 .get(&mut db)
1735 .await?;
1736
1737 assert_eq!("Alice", todo.user.get().name);
1738 let profile = todo.user.get().profile.get().as_ref().unwrap();
1739 assert_eq!("developer", profile.bio);
1740
1741 let user2 = User::create()
1743 .name("Bob")
1744 .todo(Todo::create().title("Task 2"))
1745 .exec(&mut db)
1746 .await?;
1747
1748 let todo2_id = Todo::get_by_user_id(&mut db, user2.id).await?.id;
1749
1750 let todo2 = Todo::filter_by_id(todo2_id)
1751 .include(Todo::fields().user().profile())
1752 .get(&mut db)
1753 .await?;
1754
1755 assert_eq!("Bob", todo2.user.get().name);
1756 assert!(todo2.user.get().profile.get().is_none());
1757
1758 Ok(())
1759}
1760
1761#[driver_test(id(ID))]
1764pub async fn nested_belongs_to_required_then_belongs_to_required(test: &mut Test) -> Result<()> {
1765 #[derive(Debug, toasty::Model)]
1766 #[allow(dead_code)]
1767 struct User {
1768 #[key]
1769 #[auto]
1770 id: ID,
1771
1772 name: String,
1773
1774 #[has_many]
1775 todos: toasty::HasMany<Todo>,
1776 }
1777
1778 #[derive(Debug, toasty::Model)]
1779 #[allow(dead_code)]
1780 struct Todo {
1781 #[key]
1782 #[auto]
1783 id: ID,
1784
1785 title: String,
1786
1787 #[index]
1788 user_id: ID,
1789
1790 #[belongs_to(key = user_id, references = id)]
1791 user: toasty::BelongsTo<User>,
1792
1793 #[has_many]
1794 steps: toasty::HasMany<Step>,
1795 }
1796
1797 #[derive(Debug, toasty::Model)]
1798 #[allow(dead_code)]
1799 struct Step {
1800 #[key]
1801 #[auto]
1802 id: ID,
1803
1804 description: String,
1805
1806 #[index]
1807 todo_id: ID,
1808
1809 #[belongs_to(key = todo_id, references = id)]
1810 todo: toasty::BelongsTo<Todo>,
1811 }
1812
1813 let mut db = test.setup_db(models!(User, Todo, Step)).await;
1814
1815 let user = User::create()
1816 .name("Alice")
1817 .todo(
1818 Todo::create()
1819 .title("T1")
1820 .step(Step::create().description("S1")),
1821 )
1822 .exec(&mut db)
1823 .await?;
1824
1825 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1826 let step_id = Step::get_by_todo_id(&mut db, todo_id).await?.id;
1827
1828 let step = Step::filter_by_id(step_id)
1830 .include(Step::fields().todo().user())
1831 .get(&mut db)
1832 .await?;
1833
1834 assert_eq!("T1", step.todo.get().title);
1835 assert_eq!("Alice", step.todo.get().user.get().name);
1836
1837 Ok(())
1838}
1839
1840#[driver_test(id(ID))]
1843pub async fn nested_belongs_to_optional_then_has_many(test: &mut Test) -> Result<()> {
1844 #[derive(Debug, toasty::Model)]
1845 #[allow(dead_code)]
1846 struct Project {
1847 #[key]
1848 #[auto]
1849 id: ID,
1850
1851 name: String,
1852
1853 #[has_many]
1854 members: toasty::HasMany<Member>,
1855 }
1856
1857 #[derive(Debug, toasty::Model)]
1858 #[allow(dead_code)]
1859 struct Member {
1860 #[key]
1861 #[auto]
1862 id: ID,
1863
1864 name: String,
1865
1866 #[index]
1867 project_id: ID,
1868
1869 #[belongs_to(key = project_id, references = id)]
1870 project: toasty::BelongsTo<Project>,
1871 }
1872
1873 #[derive(Debug, toasty::Model)]
1874 #[allow(dead_code)]
1875 struct Task {
1876 #[key]
1877 #[auto]
1878 id: ID,
1879
1880 title: String,
1881
1882 #[index]
1883 project_id: Option<ID>,
1884
1885 #[belongs_to(key = project_id, references = id)]
1886 project: toasty::BelongsTo<Option<Project>>,
1887 }
1888
1889 let mut db = test.setup_db(models!(Project, Member, Task)).await;
1890
1891 let project = Project::create()
1892 .name("Proj1")
1893 .member(Member::create().name("Alice"))
1894 .member(Member::create().name("Bob"))
1895 .exec(&mut db)
1896 .await?;
1897
1898 let task = Task::create()
1900 .title("Linked")
1901 .project(&project)
1902 .exec(&mut db)
1903 .await?;
1904
1905 let task = Task::filter_by_id(task.id)
1906 .include(Task::fields().project().members())
1907 .get(&mut db)
1908 .await?;
1909
1910 let proj = task.project.get().as_ref().unwrap();
1911 assert_eq!("Proj1", proj.name);
1912 let mut names: Vec<&str> = proj.members.get().iter().map(|m| m.name.as_str()).collect();
1913 names.sort();
1914 assert_eq!(names, vec!["Alice", "Bob"]);
1915
1916 let orphan = Task::create().title("Orphan").exec(&mut db).await?;
1918
1919 let orphan = Task::filter_by_id(orphan.id)
1920 .include(Task::fields().project().members())
1921 .get(&mut db)
1922 .await?;
1923
1924 assert!(orphan.project.get().is_none());
1925
1926 Ok(())
1927}
1928
1929#[driver_test(id(ID))]
1932pub async fn nested_belongs_to_optional_then_belongs_to_optional(test: &mut Test) -> Result<()> {
1933 #[derive(Debug, toasty::Model)]
1934 #[allow(dead_code)]
1935 struct Category {
1936 #[key]
1937 #[auto]
1938 id: ID,
1939
1940 name: String,
1941 }
1942
1943 #[derive(Debug, toasty::Model)]
1944 #[allow(dead_code)]
1945 struct Post {
1946 #[key]
1947 #[auto]
1948 id: ID,
1949
1950 title: String,
1951
1952 #[index]
1953 category_id: Option<ID>,
1954
1955 #[belongs_to(key = category_id, references = id)]
1956 category: toasty::BelongsTo<Option<Category>>,
1957 }
1958
1959 #[derive(Debug, toasty::Model)]
1960 #[allow(dead_code)]
1961 struct Comment {
1962 #[key]
1963 #[auto]
1964 id: ID,
1965
1966 body: String,
1967
1968 #[index]
1969 post_id: Option<ID>,
1970
1971 #[belongs_to(key = post_id, references = id)]
1972 post: toasty::BelongsTo<Option<Post>>,
1973 }
1974
1975 let mut db = test.setup_db(models!(Category, Post, Comment)).await;
1976
1977 let cat = Category::create().name("Tech").exec(&mut db).await?;
1978 let post = Post::create()
1979 .title("Hello")
1980 .category(&cat)
1981 .exec(&mut db)
1982 .await?;
1983
1984 let c1 = Comment::create()
1986 .body("Nice")
1987 .post(&post)
1988 .exec(&mut db)
1989 .await?;
1990
1991 let c1 = Comment::filter_by_id(c1.id)
1992 .include(Comment::fields().post().category())
1993 .get(&mut db)
1994 .await?;
1995
1996 let loaded_post = c1.post.get().as_ref().unwrap();
1997 assert_eq!("Hello", loaded_post.title);
1998 let loaded_cat = loaded_post.category.get().as_ref().unwrap();
1999 assert_eq!("Tech", loaded_cat.name);
2000
2001 let post2 = Post::create().title("Uncategorized").exec(&mut db).await?;
2003 let c2 = Comment::create()
2004 .body("Hmm")
2005 .post(&post2)
2006 .exec(&mut db)
2007 .await?;
2008
2009 let c2 = Comment::filter_by_id(c2.id)
2010 .include(Comment::fields().post().category())
2011 .get(&mut db)
2012 .await?;
2013
2014 let loaded_post2 = c2.post.get().as_ref().unwrap();
2015 assert_eq!("Uncategorized", loaded_post2.title);
2016 assert!(loaded_post2.category.get().is_none());
2017
2018 let c3 = Comment::create().body("Orphan").exec(&mut db).await?;
2020
2021 let c3 = Comment::filter_by_id(c3.id)
2022 .include(Comment::fields().post().category())
2023 .get(&mut db)
2024 .await?;
2025
2026 assert!(c3.post.get().is_none());
2027
2028 Ok(())
2029}
2030
2031#[driver_test(id(ID))]
2034pub async fn nested_belongs_to_required_then_has_one_required(test: &mut Test) -> Result<()> {
2035 #[derive(Debug, toasty::Model)]
2036 #[allow(dead_code)]
2037 struct User {
2038 #[key]
2039 #[auto]
2040 id: ID,
2041
2042 name: String,
2043
2044 #[has_one]
2045 config: toasty::HasOne<Config>,
2046
2047 #[has_many]
2048 todos: toasty::HasMany<Todo>,
2049 }
2050
2051 #[derive(Debug, toasty::Model)]
2052 #[allow(dead_code)]
2053 struct Config {
2054 #[key]
2055 #[auto]
2056 id: ID,
2057
2058 theme: String,
2059
2060 #[unique]
2061 user_id: Option<ID>,
2062
2063 #[belongs_to(key = user_id, references = id)]
2064 user: toasty::BelongsTo<Option<User>>,
2065 }
2066
2067 #[derive(Debug, toasty::Model)]
2068 #[allow(dead_code)]
2069 struct Todo {
2070 #[key]
2071 #[auto]
2072 id: ID,
2073
2074 title: String,
2075
2076 #[index]
2077 user_id: ID,
2078
2079 #[belongs_to(key = user_id, references = id)]
2080 user: toasty::BelongsTo<User>,
2081 }
2082
2083 let mut db = test.setup_db(models!(User, Config, Todo)).await;
2084
2085 let user = User::create()
2086 .name("Alice")
2087 .config(Config::create().theme("dark"))
2088 .todo(Todo::create().title("Task"))
2089 .exec(&mut db)
2090 .await?;
2091
2092 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
2093
2094 let todo = Todo::filter_by_id(todo_id)
2095 .include(Todo::fields().user().config())
2096 .get(&mut db)
2097 .await?;
2098
2099 assert_eq!("Alice", todo.user.get().name);
2100 assert_eq!("dark", todo.user.get().config.get().theme);
2101
2102 Ok(())
2103}
2104
2105#[driver_test(id(ID))]
2109pub async fn nested_has_many_then_has_many_with_empty_leaves(test: &mut Test) {
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_many]
2120 todos: toasty::HasMany<Todo>,
2121 }
2122
2123 #[derive(Debug, toasty::Model)]
2124 #[allow(dead_code)]
2125 struct Todo {
2126 #[key]
2127 #[auto]
2128 id: ID,
2129
2130 title: String,
2131
2132 #[index]
2133 user_id: ID,
2134
2135 #[belongs_to(key = user_id, references = id)]
2136 user: toasty::BelongsTo<User>,
2137
2138 #[has_many]
2139 steps: toasty::HasMany<Step>,
2140 }
2141
2142 #[derive(Debug, toasty::Model)]
2143 #[allow(dead_code)]
2144 struct Step {
2145 #[key]
2146 #[auto]
2147 id: ID,
2148
2149 description: String,
2150
2151 #[index]
2152 todo_id: ID,
2153
2154 #[belongs_to(key = todo_id, references = id)]
2155 todo: toasty::BelongsTo<Todo>,
2156 }
2157
2158 let mut db = test.setup_db(models!(User, Todo, Step)).await;
2159
2160 let user = User::create()
2161 .name("Alice")
2162 .todo(
2163 Todo::create()
2164 .title("With Steps")
2165 .step(Step::create().description("S1")),
2166 )
2167 .todo(Todo::create().title("No Steps")) .exec(&mut db)
2169 .await
2170 .unwrap();
2171
2172 let user = User::filter_by_id(user.id)
2173 .include(User::fields().todos().steps())
2174 .get(&mut db)
2175 .await
2176 .unwrap();
2177
2178 let todos = user.todos.get();
2179 assert_eq!(2, todos.len());
2180
2181 let mut total_steps = 0;
2182 for todo in todos {
2183 let steps = todo.steps.get();
2184 if todo.title == "With Steps" {
2185 assert_eq!(1, steps.len());
2186 assert_eq!("S1", steps[0].description);
2187 } else {
2188 assert_eq!(0, steps.len());
2189 }
2190 total_steps += steps.len();
2191 }
2192 assert_eq!(1, total_steps);
2193}