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(
437 id(ID),
438 requires(scan),
439 scenario(crate::scenarios::has_many_belongs_to)
440)]
441pub async fn preload_on_empty_table(test: &mut Test) -> Result<()> {
442 let mut db = setup(test).await;
443
444 let users: Vec<User> = User::all()
446 .include(User::fields().todos())
447 .exec(&mut db)
448 .await?;
449
450 assert_eq!(0, users.len());
451 Ok(())
452}
453
454#[driver_test(id(ID))]
455pub async fn preload_on_empty_query(test: &mut Test) -> Result<()> {
456 #[derive(Debug, toasty::Model)]
457 struct User {
458 #[key]
459 #[auto]
460 id: ID,
461
462 #[index]
463 #[allow(dead_code)]
464 name: String,
465
466 #[has_many]
467 #[allow(dead_code)]
468 todos: toasty::HasMany<Todo>,
469 }
470
471 #[derive(Debug, toasty::Model)]
472 struct Todo {
473 #[key]
474 #[auto]
475 id: ID,
476
477 #[index]
478 #[allow(dead_code)]
479 user_id: ID,
480
481 #[belongs_to(key = user_id, references = id)]
482 #[allow(dead_code)]
483 user: toasty::BelongsTo<User>,
484 }
485
486 let mut db = test.setup_db(models!(User, Todo)).await;
487
488 let users: Vec<User> = User::filter_by_name("foo")
490 .include(User::fields().todos())
491 .exec(&mut db)
492 .await?;
493
494 assert_eq!(0, users.len());
495 Ok(())
496}
497
498#[driver_test(id(ID))]
501pub async fn preload_has_many_with_optional_belongs_to(test: &mut Test) -> Result<()> {
502 #[derive(Debug, toasty::Model)]
503 struct User {
504 #[key]
505 #[auto]
506 id: ID,
507
508 name: String,
509
510 #[has_many]
511 todos: toasty::HasMany<Todo>,
512 }
513
514 #[derive(Debug, toasty::Model)]
515 struct Todo {
516 #[key]
517 #[auto]
518 id: ID,
519
520 #[index]
521 title: String,
522
523 #[index]
524 user_id: Option<ID>,
525
526 #[belongs_to(key = user_id, references = id)]
527 user: toasty::BelongsTo<Option<User>>,
528 }
529
530 let mut db = test.setup_db(models!(User, Todo)).await;
531
532 let user = User::create()
534 .name("Alice")
535 .todo(Todo::create().title("Task 1"))
536 .todo(Todo::create().title("Task 2"))
537 .exec(&mut db)
538 .await?;
539
540 let user = User::filter_by_id(user.id)
542 .include(User::fields().todos())
543 .get(&mut db)
544 .await?;
545
546 assert_eq!(2, user.todos.get().len());
547
548 let todo_id = user.todos.get()[0].id;
549
550 let todo = Todo::filter_by_id(todo_id)
552 .include(Todo::fields().user())
553 .get(&mut db)
554 .await?;
555
556 assert_eq!(user.id, todo.user.get().as_ref().unwrap().id);
557
558 let orphan = Todo::create().title("Orphan").exec(&mut db).await?;
560
561 let orphan = Todo::filter_by_id(orphan.id)
563 .include(Todo::fields().user())
564 .get(&mut db)
565 .await?;
566
567 assert!(orphan.user.get().is_none());
568
569 Ok(())
570}
571
572#[driver_test(id(ID))]
576pub async fn preload_has_one_optional_with_required_belongs_to(test: &mut Test) -> Result<()> {
577 #[derive(Debug, toasty::Model)]
578 struct User {
579 #[key]
580 #[auto]
581 id: ID,
582
583 name: String,
584
585 #[has_one]
586 profile: toasty::HasOne<Option<Profile>>,
587 }
588
589 #[derive(Debug, toasty::Model)]
590 struct Profile {
591 #[key]
592 #[auto]
593 id: ID,
594
595 bio: String,
596
597 #[unique]
598 user_id: ID,
599
600 #[belongs_to(key = user_id, references = id)]
601 user: toasty::BelongsTo<User>,
602 }
603
604 let mut db = test.setup_db(models!(User, Profile)).await;
605
606 let user_with = User::create()
608 .name("Has Profile")
609 .profile(Profile::create().bio("hello"))
610 .exec(&mut db)
611 .await?;
612
613 let user_without = User::create().name("No Profile").exec(&mut db).await?;
615
616 let loaded = User::filter_by_id(user_with.id)
618 .include(User::fields().profile())
619 .get(&mut db)
620 .await?;
621
622 let profile = loaded.profile.get().as_ref().unwrap();
623 assert_eq!("hello", profile.bio);
624 assert_eq!(user_with.id, profile.user_id);
625
626 let loaded = User::filter_by_id(user_without.id)
628 .include(User::fields().profile())
629 .get(&mut db)
630 .await?;
631
632 assert!(loaded.profile.get().is_none());
633
634 let profile = Profile::filter_by_user_id(user_with.id)
636 .include(Profile::fields().user())
637 .get(&mut db)
638 .await?;
639
640 assert_eq!(user_with.id, profile.user.get().id);
641 assert_eq!("Has Profile", profile.user.get().name);
642
643 Ok(())
644}
645
646#[driver_test(id(ID))]
650pub async fn preload_has_one_required_with_optional_belongs_to(test: &mut Test) -> Result<()> {
651 #[derive(Debug, toasty::Model)]
652 struct User {
653 #[key]
654 #[auto]
655 id: ID,
656
657 name: String,
658
659 #[has_one]
660 profile: toasty::HasOne<Profile>,
661 }
662
663 #[derive(Debug, toasty::Model)]
664 struct Profile {
665 #[key]
666 #[auto]
667 id: ID,
668
669 bio: String,
670
671 #[unique]
672 user_id: Option<ID>,
673
674 #[belongs_to(key = user_id, references = id)]
675 user: toasty::BelongsTo<Option<User>>,
676 }
677
678 let mut db = test.setup_db(models!(User, Profile)).await;
679
680 let user = User::create()
682 .name("Alice")
683 .profile(Profile::create().bio("a bio"))
684 .exec(&mut db)
685 .await?;
686
687 let loaded = User::filter_by_id(user.id)
689 .include(User::fields().profile())
690 .get(&mut db)
691 .await?;
692
693 let profile = loaded.profile.get();
694 assert_eq!("a bio", profile.bio);
695 assert_eq!(user.id, *profile.user_id.as_ref().unwrap());
696
697 let profile = Profile::filter_by_id(profile.id)
699 .include(Profile::fields().user())
700 .get(&mut db)
701 .await?;
702
703 assert_eq!(user.id, profile.user.get().as_ref().unwrap().id);
704 assert_eq!("Alice", profile.user.get().as_ref().unwrap().name);
705
706 Ok(())
707}
708
709#[driver_test(id(ID))]
710pub async fn nested_has_many_preload(test: &mut Test) {
711 #[derive(Debug, toasty::Model)]
712 #[allow(dead_code)]
713 struct User {
714 #[key]
715 #[auto]
716 id: ID,
717
718 name: String,
719
720 #[has_many]
721 todos: toasty::HasMany<Todo>,
722 }
723
724 #[derive(Debug, toasty::Model)]
725 #[allow(dead_code)]
726 struct Todo {
727 #[key]
728 #[auto]
729 id: ID,
730
731 title: String,
732
733 #[index]
734 user_id: ID,
735
736 #[belongs_to(key = user_id, references = id)]
737 user: toasty::BelongsTo<User>,
738
739 #[has_many]
740 steps: toasty::HasMany<Step>,
741 }
742
743 #[derive(Debug, toasty::Model)]
744 #[allow(dead_code)]
745 struct Step {
746 #[key]
747 #[auto]
748 id: ID,
749
750 description: String,
751
752 #[index]
753 todo_id: ID,
754
755 #[belongs_to(key = todo_id, references = id)]
756 todo: toasty::BelongsTo<Todo>,
757 }
758
759 let mut db = test.setup_db(models!(User, Todo, Step)).await;
760
761 let user = User::create()
763 .name("Alice")
764 .todo(
765 Todo::create()
766 .title("Todo 1")
767 .step(Step::create().description("Step 1a"))
768 .step(Step::create().description("Step 1b")),
769 )
770 .todo(
771 Todo::create()
772 .title("Todo 2")
773 .step(Step::create().description("Step 2a"))
774 .step(Step::create().description("Step 2b"))
775 .step(Step::create().description("Step 2c")),
776 )
777 .exec(&mut db)
778 .await
779 .unwrap();
780
781 let user = User::filter_by_id(user.id)
783 .include(User::fields().todos().steps())
784 .get(&mut db)
785 .await
786 .unwrap();
787
788 let todos = user.todos.get();
790 assert_eq!(2, todos.len());
791
792 let mut all_step_descriptions: Vec<&str> = Vec::new();
794 for todo in todos {
795 let steps = todo.steps.get();
796 for step in steps {
797 all_step_descriptions.push(&step.description);
798 }
799 }
800 all_step_descriptions.sort();
801 assert_eq!(
802 all_step_descriptions,
803 vec!["Step 1a", "Step 1b", "Step 2a", "Step 2b", "Step 2c"]
804 );
805}
806
807#[driver_test(id(ID))]
810pub async fn nested_has_many_then_has_one_optional(test: &mut Test) -> Result<()> {
811 #[derive(Debug, toasty::Model)]
812 #[allow(dead_code)]
813 struct User {
814 #[key]
815 #[auto]
816 id: ID,
817
818 name: String,
819
820 #[has_many]
821 posts: toasty::HasMany<Post>,
822 }
823
824 #[derive(Debug, toasty::Model)]
825 #[allow(dead_code)]
826 struct Post {
827 #[key]
828 #[auto]
829 id: ID,
830
831 title: String,
832
833 #[index]
834 user_id: ID,
835
836 #[belongs_to(key = user_id, references = id)]
837 user: toasty::BelongsTo<User>,
838
839 #[has_one]
840 detail: toasty::HasOne<Option<Detail>>,
841 }
842
843 #[derive(Debug, toasty::Model)]
844 #[allow(dead_code)]
845 struct Detail {
846 #[key]
847 #[auto]
848 id: ID,
849
850 body: String,
851
852 #[unique]
853 post_id: Option<ID>,
854
855 #[belongs_to(key = post_id, references = id)]
856 post: toasty::BelongsTo<Option<Post>>,
857 }
858
859 let mut db = test.setup_db(models!(User, Post, Detail)).await;
860
861 let user = User::create()
862 .name("Alice")
863 .post(
864 Post::create()
865 .title("P1")
866 .detail(Detail::create().body("D1")),
867 )
868 .post(Post::create().title("P2")) .exec(&mut db)
870 .await?;
871
872 let user = User::filter_by_id(user.id)
873 .include(User::fields().posts().detail())
874 .get(&mut db)
875 .await?;
876
877 let posts = user.posts.get();
878 assert_eq!(2, posts.len());
879
880 let mut with_detail = 0;
881 let mut without_detail = 0;
882 for post in posts {
883 match post.detail.get() {
884 Some(d) => {
885 assert_eq!("D1", d.body);
886 with_detail += 1;
887 }
888 None => without_detail += 1,
889 }
890 }
891 assert_eq!(1, with_detail);
892 assert_eq!(1, without_detail);
893
894 Ok(())
895}
896
897#[driver_test(id(ID))]
900pub async fn nested_has_many_then_has_one_required(test: &mut Test) -> Result<()> {
901 #[derive(Debug, toasty::Model)]
902 #[allow(dead_code)]
903 struct User {
904 #[key]
905 #[auto]
906 id: ID,
907
908 name: String,
909
910 #[has_many]
911 accounts: toasty::HasMany<Account>,
912 }
913
914 #[derive(Debug, toasty::Model)]
915 #[allow(dead_code)]
916 struct Account {
917 #[key]
918 #[auto]
919 id: ID,
920
921 label: String,
922
923 #[index]
924 user_id: ID,
925
926 #[belongs_to(key = user_id, references = id)]
927 user: toasty::BelongsTo<User>,
928
929 #[has_one]
930 settings: toasty::HasOne<Settings>,
931 }
932
933 #[derive(Debug, toasty::Model)]
934 #[allow(dead_code)]
935 struct Settings {
936 #[key]
937 #[auto]
938 id: ID,
939
940 theme: String,
941
942 #[unique]
943 account_id: Option<ID>,
944
945 #[belongs_to(key = account_id, references = id)]
946 account: toasty::BelongsTo<Option<Account>>,
947 }
948
949 let mut db = test.setup_db(models!(User, Account, Settings)).await;
950
951 let user = User::create()
952 .name("Bob")
953 .account(
954 Account::create()
955 .label("A1")
956 .settings(Settings::create().theme("dark")),
957 )
958 .account(
959 Account::create()
960 .label("A2")
961 .settings(Settings::create().theme("light")),
962 )
963 .exec(&mut db)
964 .await?;
965
966 let user = User::filter_by_id(user.id)
967 .include(User::fields().accounts().settings())
968 .get(&mut db)
969 .await?;
970
971 let accounts = user.accounts.get();
972 assert_eq!(2, accounts.len());
973
974 let mut themes: Vec<&str> = accounts
975 .iter()
976 .map(|a| a.settings.get().theme.as_str())
977 .collect();
978 themes.sort();
979 assert_eq!(themes, vec!["dark", "light"]);
980
981 Ok(())
982}
983
984#[driver_test(id(ID))]
987pub async fn nested_has_many_then_belongs_to_required(test: &mut Test) -> Result<()> {
988 #[derive(Debug, toasty::Model)]
989 #[allow(dead_code)]
990 struct Category {
991 #[key]
992 #[auto]
993 id: ID,
994
995 name: String,
996
997 #[has_many]
998 items: toasty::HasMany<Item>,
999 }
1000
1001 #[derive(Debug, toasty::Model)]
1002 #[allow(dead_code)]
1003 struct Brand {
1004 #[key]
1005 #[auto]
1006 id: ID,
1007
1008 name: String,
1009 }
1010
1011 #[derive(Debug, toasty::Model)]
1012 #[allow(dead_code)]
1013 struct Item {
1014 #[key]
1015 #[auto]
1016 id: ID,
1017
1018 title: String,
1019
1020 #[index]
1021 category_id: ID,
1022
1023 #[belongs_to(key = category_id, references = id)]
1024 category: toasty::BelongsTo<Category>,
1025
1026 #[index]
1027 brand_id: ID,
1028
1029 #[belongs_to(key = brand_id, references = id)]
1030 brand: toasty::BelongsTo<Brand>,
1031 }
1032
1033 let mut db = test.setup_db(models!(Category, Brand, Item)).await;
1034
1035 let brand_a = Brand::create().name("BrandA").exec(&mut db).await?;
1036 let brand_b = Brand::create().name("BrandB").exec(&mut db).await?;
1037
1038 let cat = Category::create()
1039 .name("Electronics")
1040 .item(Item::create().title("Phone").brand(&brand_a))
1041 .item(Item::create().title("Laptop").brand(&brand_b))
1042 .exec(&mut db)
1043 .await?;
1044
1045 let cat = Category::filter_by_id(cat.id)
1046 .include(Category::fields().items().brand())
1047 .get(&mut db)
1048 .await?;
1049
1050 let items = cat.items.get();
1051 assert_eq!(2, items.len());
1052
1053 let mut brand_names: Vec<&str> = items.iter().map(|i| i.brand.get().name.as_str()).collect();
1054 brand_names.sort();
1055 assert_eq!(brand_names, vec!["BrandA", "BrandB"]);
1056
1057 Ok(())
1058}
1059
1060#[driver_test(id(ID))]
1065pub async fn nested_has_many_then_shared_belongs_to(test: &mut Test) -> Result<()> {
1066 #[derive(Debug, toasty::Model)]
1067 #[allow(dead_code)]
1068 struct Category {
1069 #[key]
1070 #[auto]
1071 id: ID,
1072
1073 name: String,
1074
1075 #[has_many]
1076 items: toasty::HasMany<Item>,
1077 }
1078
1079 #[derive(Debug, toasty::Model)]
1080 #[allow(dead_code)]
1081 struct Brand {
1082 #[key]
1083 #[auto]
1084 id: ID,
1085
1086 name: String,
1087 }
1088
1089 #[derive(Debug, toasty::Model)]
1090 #[allow(dead_code)]
1091 struct Item {
1092 #[key]
1093 #[auto]
1094 id: ID,
1095
1096 title: String,
1097
1098 #[index]
1099 category_id: ID,
1100
1101 #[belongs_to(key = category_id, references = id)]
1102 category: toasty::BelongsTo<Category>,
1103
1104 #[index]
1105 brand_id: ID,
1106
1107 #[belongs_to(key = brand_id, references = id)]
1108 brand: toasty::BelongsTo<Brand>,
1109 }
1110
1111 let mut db = test.setup_db(models!(Category, Brand, Item)).await;
1112
1113 let brand = Brand::create().name("BrandA").exec(&mut db).await?;
1114 let cat = Category::create()
1115 .name("Electronics")
1116 .item(Item::create().title("Phone").brand(&brand))
1117 .item(Item::create().title("Laptop").brand(&brand))
1118 .exec(&mut db)
1119 .await?;
1120
1121 let cat = Category::filter_by_id(cat.id)
1122 .include(Category::fields().items().brand())
1123 .get(&mut db)
1124 .await?;
1125
1126 let items = cat.items.get();
1127 assert_eq!(2, items.len());
1128 for item in items {
1129 assert_eq!("BrandA", item.brand.get().name);
1130 }
1131
1132 Ok(())
1133}
1134
1135#[driver_test(id(ID))]
1138pub async fn nested_has_many_then_belongs_to_optional(test: &mut Test) -> Result<()> {
1139 #[derive(Debug, toasty::Model)]
1140 #[allow(dead_code)]
1141 struct Team {
1142 #[key]
1143 #[auto]
1144 id: ID,
1145
1146 name: String,
1147
1148 #[has_many]
1149 tasks: toasty::HasMany<Task>,
1150 }
1151
1152 #[derive(Debug, toasty::Model)]
1153 #[allow(dead_code)]
1154 struct Assignee {
1155 #[key]
1156 #[auto]
1157 id: ID,
1158
1159 name: String,
1160 }
1161
1162 #[derive(Debug, toasty::Model)]
1163 #[allow(dead_code)]
1164 struct Task {
1165 #[key]
1166 #[auto]
1167 id: ID,
1168
1169 title: String,
1170
1171 #[index]
1172 team_id: ID,
1173
1174 #[belongs_to(key = team_id, references = id)]
1175 team: toasty::BelongsTo<Team>,
1176
1177 #[index]
1178 assignee_id: Option<ID>,
1179
1180 #[belongs_to(key = assignee_id, references = id)]
1181 assignee: toasty::BelongsTo<Option<Assignee>>,
1182 }
1183
1184 let mut db = test.setup_db(models!(Team, Assignee, Task)).await;
1185
1186 let person = Assignee::create().name("Alice").exec(&mut db).await?;
1187
1188 let team = Team::create()
1189 .name("Engineering")
1190 .task(Task::create().title("Assigned").assignee(&person))
1191 .task(Task::create().title("Unassigned"))
1192 .exec(&mut db)
1193 .await?;
1194
1195 let team = Team::filter_by_id(team.id)
1196 .include(Team::fields().tasks().assignee())
1197 .get(&mut db)
1198 .await?;
1199
1200 let tasks = team.tasks.get();
1201 assert_eq!(2, tasks.len());
1202
1203 let mut assigned = 0;
1204 let mut unassigned = 0;
1205 for task in tasks {
1206 match task.assignee.get() {
1207 Some(a) => {
1208 assert_eq!("Alice", a.name);
1209 assigned += 1;
1210 }
1211 None => unassigned += 1,
1212 }
1213 }
1214 assert_eq!(1, assigned);
1215 assert_eq!(1, unassigned);
1216
1217 Ok(())
1218}
1219
1220#[driver_test(id(ID))]
1223pub async fn nested_has_one_optional_then_has_many(test: &mut Test) -> Result<()> {
1224 #[derive(Debug, toasty::Model)]
1225 #[allow(dead_code)]
1226 struct User {
1227 #[key]
1228 #[auto]
1229 id: ID,
1230
1231 name: String,
1232
1233 #[has_one]
1234 profile: toasty::HasOne<Option<Profile>>,
1235 }
1236
1237 #[derive(Debug, toasty::Model)]
1238 #[allow(dead_code)]
1239 struct Profile {
1240 #[key]
1241 #[auto]
1242 id: ID,
1243
1244 bio: String,
1245
1246 #[unique]
1247 user_id: Option<ID>,
1248
1249 #[belongs_to(key = user_id, references = id)]
1250 user: toasty::BelongsTo<Option<User>>,
1251
1252 #[has_many]
1253 badges: toasty::HasMany<Badge>,
1254 }
1255
1256 #[derive(Debug, toasty::Model)]
1257 #[allow(dead_code)]
1258 struct Badge {
1259 #[key]
1260 #[auto]
1261 id: ID,
1262
1263 label: String,
1264
1265 #[index]
1266 profile_id: ID,
1267
1268 #[belongs_to(key = profile_id, references = id)]
1269 profile: toasty::BelongsTo<Profile>,
1270 }
1271
1272 let mut db = test.setup_db(models!(User, Profile, Badge)).await;
1273
1274 let user = User::create()
1276 .name("Alice")
1277 .profile(
1278 Profile::create()
1279 .bio("hi")
1280 .badge(Badge::create().label("Gold"))
1281 .badge(Badge::create().label("Silver")),
1282 )
1283 .exec(&mut db)
1284 .await?;
1285
1286 let user = User::filter_by_id(user.id)
1287 .include(User::fields().profile().badges())
1288 .get(&mut db)
1289 .await?;
1290
1291 let profile = user.profile.get().as_ref().unwrap();
1292 assert_eq!("hi", profile.bio);
1293 let mut labels: Vec<&str> = profile
1294 .badges
1295 .get()
1296 .iter()
1297 .map(|b| b.label.as_str())
1298 .collect();
1299 labels.sort();
1300 assert_eq!(labels, vec!["Gold", "Silver"]);
1301
1302 let user2 = User::create().name("Bob").exec(&mut db).await?;
1304
1305 let user2 = User::filter_by_id(user2.id)
1306 .include(User::fields().profile().badges())
1307 .get(&mut db)
1308 .await?;
1309
1310 assert!(user2.profile.get().is_none());
1311
1312 Ok(())
1313}
1314
1315#[driver_test(id(ID))]
1318pub async fn nested_has_one_required_then_has_many(test: &mut Test) -> Result<()> {
1319 #[derive(Debug, toasty::Model)]
1320 #[allow(dead_code)]
1321 struct Order {
1322 #[key]
1323 #[auto]
1324 id: ID,
1325
1326 label: String,
1327
1328 #[has_one]
1329 invoice: toasty::HasOne<Invoice>,
1330 }
1331
1332 #[derive(Debug, toasty::Model)]
1333 #[allow(dead_code)]
1334 struct Invoice {
1335 #[key]
1336 #[auto]
1337 id: ID,
1338
1339 code: String,
1340
1341 #[unique]
1342 order_id: Option<ID>,
1343
1344 #[belongs_to(key = order_id, references = id)]
1345 order: toasty::BelongsTo<Option<Order>>,
1346
1347 #[has_many]
1348 line_items: toasty::HasMany<LineItem>,
1349 }
1350
1351 #[derive(Debug, toasty::Model)]
1352 #[allow(dead_code)]
1353 struct LineItem {
1354 #[key]
1355 #[auto]
1356 id: ID,
1357
1358 description: String,
1359
1360 #[index]
1361 invoice_id: ID,
1362
1363 #[belongs_to(key = invoice_id, references = id)]
1364 invoice: toasty::BelongsTo<Invoice>,
1365 }
1366
1367 let mut db = test.setup_db(models!(Order, Invoice, LineItem)).await;
1368
1369 let order = Order::create()
1370 .label("Order1")
1371 .invoice(
1372 Invoice::create()
1373 .code("INV-001")
1374 .line_item(LineItem::create().description("Widget"))
1375 .line_item(LineItem::create().description("Gadget")),
1376 )
1377 .exec(&mut db)
1378 .await?;
1379
1380 let order = Order::filter_by_id(order.id)
1381 .include(Order::fields().invoice().line_items())
1382 .get(&mut db)
1383 .await?;
1384
1385 let invoice = order.invoice.get();
1386 assert_eq!("INV-001", invoice.code);
1387 let mut descs: Vec<&str> = invoice
1388 .line_items
1389 .get()
1390 .iter()
1391 .map(|li| li.description.as_str())
1392 .collect();
1393 descs.sort();
1394 assert_eq!(descs, vec!["Gadget", "Widget"]);
1395
1396 Ok(())
1397}
1398
1399#[driver_test(id(ID))]
1402pub async fn nested_has_one_optional_then_has_one_optional(test: &mut Test) -> Result<()> {
1403 #[derive(Debug, toasty::Model)]
1404 #[allow(dead_code)]
1405 struct User {
1406 #[key]
1407 #[auto]
1408 id: ID,
1409
1410 name: String,
1411
1412 #[has_one]
1413 profile: toasty::HasOne<Option<Profile>>,
1414 }
1415
1416 #[derive(Debug, toasty::Model)]
1417 #[allow(dead_code)]
1418 struct Profile {
1419 #[key]
1420 #[auto]
1421 id: ID,
1422
1423 bio: String,
1424
1425 #[unique]
1426 user_id: Option<ID>,
1427
1428 #[belongs_to(key = user_id, references = id)]
1429 user: toasty::BelongsTo<Option<User>>,
1430
1431 #[has_one]
1432 avatar: toasty::HasOne<Option<Avatar>>,
1433 }
1434
1435 #[derive(Debug, toasty::Model)]
1436 #[allow(dead_code)]
1437 struct Avatar {
1438 #[key]
1439 #[auto]
1440 id: ID,
1441
1442 url: String,
1443
1444 #[unique]
1445 profile_id: Option<ID>,
1446
1447 #[belongs_to(key = profile_id, references = id)]
1448 profile: toasty::BelongsTo<Option<Profile>>,
1449 }
1450
1451 let mut db = test.setup_db(models!(User, Profile, Avatar)).await;
1452
1453 let user = User::create()
1455 .name("Alice")
1456 .profile(
1457 Profile::create()
1458 .bio("hi")
1459 .avatar(Avatar::create().url("pic.png")),
1460 )
1461 .exec(&mut db)
1462 .await?;
1463
1464 let user = User::filter_by_id(user.id)
1465 .include(User::fields().profile().avatar())
1466 .get(&mut db)
1467 .await?;
1468
1469 let profile = user.profile.get().as_ref().unwrap();
1470 assert_eq!("hi", profile.bio);
1471 let avatar = profile.avatar.get().as_ref().unwrap();
1472 assert_eq!("pic.png", avatar.url);
1473
1474 let user2 = User::create()
1476 .name("Bob")
1477 .profile(Profile::create().bio("no pic"))
1478 .exec(&mut db)
1479 .await?;
1480
1481 let user2 = User::filter_by_id(user2.id)
1482 .include(User::fields().profile().avatar())
1483 .get(&mut db)
1484 .await?;
1485
1486 let profile2 = user2.profile.get().as_ref().unwrap();
1487 assert_eq!("no pic", profile2.bio);
1488 assert!(profile2.avatar.get().is_none());
1489
1490 let user3 = User::create().name("Carol").exec(&mut db).await?;
1492
1493 let user3 = User::filter_by_id(user3.id)
1494 .include(User::fields().profile().avatar())
1495 .get(&mut db)
1496 .await?;
1497
1498 assert!(user3.profile.get().is_none());
1499
1500 Ok(())
1501}
1502
1503#[driver_test(id(ID))]
1506pub async fn nested_has_one_required_then_has_one_required(test: &mut Test) -> Result<()> {
1507 #[derive(Debug, toasty::Model)]
1508 #[allow(dead_code)]
1509 struct User {
1510 #[key]
1511 #[auto]
1512 id: ID,
1513
1514 name: String,
1515
1516 #[has_one]
1517 profile: toasty::HasOne<Profile>,
1518 }
1519
1520 #[derive(Debug, toasty::Model)]
1521 #[allow(dead_code)]
1522 struct Profile {
1523 #[key]
1524 #[auto]
1525 id: ID,
1526
1527 bio: String,
1528
1529 #[unique]
1530 user_id: Option<ID>,
1531
1532 #[belongs_to(key = user_id, references = id)]
1533 user: toasty::BelongsTo<Option<User>>,
1534
1535 #[has_one]
1536 avatar: toasty::HasOne<Avatar>,
1537 }
1538
1539 #[derive(Debug, toasty::Model)]
1540 #[allow(dead_code)]
1541 struct Avatar {
1542 #[key]
1543 #[auto]
1544 id: ID,
1545
1546 url: String,
1547
1548 #[unique]
1549 profile_id: Option<ID>,
1550
1551 #[belongs_to(key = profile_id, references = id)]
1552 profile: toasty::BelongsTo<Option<Profile>>,
1553 }
1554
1555 let mut db = test.setup_db(models!(User, Profile, Avatar)).await;
1556
1557 let user = User::create()
1558 .name("Alice")
1559 .profile(
1560 Profile::create()
1561 .bio("engineer")
1562 .avatar(Avatar::create().url("alice.jpg")),
1563 )
1564 .exec(&mut db)
1565 .await?;
1566
1567 let user = User::filter_by_id(user.id)
1568 .include(User::fields().profile().avatar())
1569 .get(&mut db)
1570 .await?;
1571
1572 let profile = user.profile.get();
1573 assert_eq!("engineer", profile.bio);
1574 let avatar = profile.avatar.get();
1575 assert_eq!("alice.jpg", avatar.url);
1576
1577 Ok(())
1578}
1579
1580#[driver_test(id(ID))]
1583pub async fn nested_has_one_optional_then_belongs_to_required(test: &mut Test) -> Result<()> {
1584 #[derive(Debug, toasty::Model)]
1585 #[allow(dead_code)]
1586 struct User {
1587 #[key]
1588 #[auto]
1589 id: ID,
1590
1591 name: String,
1592
1593 #[has_one]
1594 review: toasty::HasOne<Option<Review>>,
1595 }
1596
1597 #[derive(Debug, toasty::Model)]
1598 #[allow(dead_code)]
1599 struct Product {
1600 #[key]
1601 #[auto]
1602 id: ID,
1603
1604 name: String,
1605 }
1606
1607 #[derive(Debug, toasty::Model)]
1608 #[allow(dead_code)]
1609 struct Review {
1610 #[key]
1611 #[auto]
1612 id: ID,
1613
1614 body: String,
1615
1616 #[unique]
1617 user_id: Option<ID>,
1618
1619 #[belongs_to(key = user_id, references = id)]
1620 user: toasty::BelongsTo<Option<User>>,
1621
1622 #[index]
1623 product_id: ID,
1624
1625 #[belongs_to(key = product_id, references = id)]
1626 product: toasty::BelongsTo<Product>,
1627 }
1628
1629 let mut db = test.setup_db(models!(User, Product, Review)).await;
1630
1631 let product = Product::create().name("Widget").exec(&mut db).await?;
1632
1633 let user = User::create()
1634 .name("Alice")
1635 .review(Review::create().body("Great!").product(&product))
1636 .exec(&mut db)
1637 .await?;
1638
1639 let user = User::filter_by_id(user.id)
1641 .include(User::fields().review().product())
1642 .get(&mut db)
1643 .await?;
1644
1645 let review = user.review.get().as_ref().unwrap();
1646 assert_eq!("Great!", review.body);
1647 assert_eq!("Widget", review.product.get().name);
1648
1649 let user2 = User::create().name("Bob").exec(&mut db).await?;
1651
1652 let user2 = User::filter_by_id(user2.id)
1653 .include(User::fields().review().product())
1654 .get(&mut db)
1655 .await?;
1656
1657 assert!(user2.review.get().is_none());
1658
1659 Ok(())
1660}
1661
1662#[driver_test(id(ID))]
1665pub async fn nested_belongs_to_required_then_has_many(test: &mut Test) -> Result<()> {
1666 #[derive(Debug, toasty::Model)]
1667 #[allow(dead_code)]
1668 struct Post {
1669 #[key]
1670 #[auto]
1671 id: ID,
1672
1673 title: String,
1674
1675 #[has_many]
1676 tags: toasty::HasMany<Tag>,
1677 }
1678
1679 #[derive(Debug, toasty::Model)]
1680 #[allow(dead_code)]
1681 struct Tag {
1682 #[key]
1683 #[auto]
1684 id: ID,
1685
1686 label: String,
1687
1688 #[index]
1689 post_id: ID,
1690
1691 #[belongs_to(key = post_id, references = id)]
1692 post: toasty::BelongsTo<Post>,
1693 }
1694
1695 #[derive(Debug, toasty::Model)]
1696 #[allow(dead_code)]
1697 struct Comment {
1698 #[key]
1699 #[auto]
1700 id: ID,
1701
1702 body: String,
1703
1704 #[index]
1705 post_id: ID,
1706
1707 #[belongs_to(key = post_id, references = id)]
1708 post: toasty::BelongsTo<Post>,
1709 }
1710
1711 let mut db = test.setup_db(models!(Post, Tag, Comment)).await;
1712
1713 let post = Post::create()
1714 .title("Hello")
1715 .tag(Tag::create().label("rust"))
1716 .tag(Tag::create().label("orm"))
1717 .exec(&mut db)
1718 .await?;
1719
1720 let comment = Comment::create()
1721 .body("Nice post")
1722 .post(&post)
1723 .exec(&mut db)
1724 .await?;
1725
1726 let comment = Comment::filter_by_id(comment.id)
1728 .include(Comment::fields().post().tags())
1729 .get(&mut db)
1730 .await?;
1731
1732 assert_eq!("Hello", comment.post.get().title);
1733 let mut labels: Vec<&str> = comment
1734 .post
1735 .get()
1736 .tags
1737 .get()
1738 .iter()
1739 .map(|t| t.label.as_str())
1740 .collect();
1741 labels.sort();
1742 assert_eq!(labels, vec!["orm", "rust"]);
1743
1744 Ok(())
1745}
1746
1747#[driver_test(id(ID))]
1750pub async fn nested_belongs_to_required_then_has_one_optional(test: &mut Test) -> Result<()> {
1751 #[derive(Debug, toasty::Model)]
1752 #[allow(dead_code)]
1753 struct User {
1754 #[key]
1755 #[auto]
1756 id: ID,
1757
1758 name: String,
1759
1760 #[has_one]
1761 profile: toasty::HasOne<Option<Profile>>,
1762
1763 #[has_many]
1764 todos: toasty::HasMany<Todo>,
1765 }
1766
1767 #[derive(Debug, toasty::Model)]
1768 #[allow(dead_code)]
1769 struct Profile {
1770 #[key]
1771 #[auto]
1772 id: ID,
1773
1774 bio: String,
1775
1776 #[unique]
1777 user_id: Option<ID>,
1778
1779 #[belongs_to(key = user_id, references = id)]
1780 user: toasty::BelongsTo<Option<User>>,
1781 }
1782
1783 #[derive(Debug, toasty::Model)]
1784 #[allow(dead_code)]
1785 struct Todo {
1786 #[key]
1787 #[auto]
1788 id: ID,
1789
1790 title: String,
1791
1792 #[index]
1793 user_id: ID,
1794
1795 #[belongs_to(key = user_id, references = id)]
1796 user: toasty::BelongsTo<User>,
1797 }
1798
1799 let mut db = test.setup_db(models!(User, Profile, Todo)).await;
1800
1801 let user = User::create()
1803 .name("Alice")
1804 .profile(Profile::create().bio("developer"))
1805 .todo(Todo::create().title("Task 1"))
1806 .exec(&mut db)
1807 .await?;
1808
1809 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1810
1811 let todo = Todo::filter_by_id(todo_id)
1812 .include(Todo::fields().user().profile())
1813 .get(&mut db)
1814 .await?;
1815
1816 assert_eq!("Alice", todo.user.get().name);
1817 let profile = todo.user.get().profile.get().as_ref().unwrap();
1818 assert_eq!("developer", profile.bio);
1819
1820 let user2 = User::create()
1822 .name("Bob")
1823 .todo(Todo::create().title("Task 2"))
1824 .exec(&mut db)
1825 .await?;
1826
1827 let todo2_id = Todo::get_by_user_id(&mut db, user2.id).await?.id;
1828
1829 let todo2 = Todo::filter_by_id(todo2_id)
1830 .include(Todo::fields().user().profile())
1831 .get(&mut db)
1832 .await?;
1833
1834 assert_eq!("Bob", todo2.user.get().name);
1835 assert!(todo2.user.get().profile.get().is_none());
1836
1837 Ok(())
1838}
1839
1840#[driver_test(id(ID))]
1843pub async fn nested_belongs_to_required_then_belongs_to_required(test: &mut Test) -> Result<()> {
1844 #[derive(Debug, toasty::Model)]
1845 #[allow(dead_code)]
1846 struct User {
1847 #[key]
1848 #[auto]
1849 id: ID,
1850
1851 name: String,
1852
1853 #[has_many]
1854 todos: toasty::HasMany<Todo>,
1855 }
1856
1857 #[derive(Debug, toasty::Model)]
1858 #[allow(dead_code)]
1859 struct Todo {
1860 #[key]
1861 #[auto]
1862 id: ID,
1863
1864 title: String,
1865
1866 #[index]
1867 user_id: ID,
1868
1869 #[belongs_to(key = user_id, references = id)]
1870 user: toasty::BelongsTo<User>,
1871
1872 #[has_many]
1873 steps: toasty::HasMany<Step>,
1874 }
1875
1876 #[derive(Debug, toasty::Model)]
1877 #[allow(dead_code)]
1878 struct Step {
1879 #[key]
1880 #[auto]
1881 id: ID,
1882
1883 description: String,
1884
1885 #[index]
1886 todo_id: ID,
1887
1888 #[belongs_to(key = todo_id, references = id)]
1889 todo: toasty::BelongsTo<Todo>,
1890 }
1891
1892 let mut db = test.setup_db(models!(User, Todo, Step)).await;
1893
1894 let user = User::create()
1895 .name("Alice")
1896 .todo(
1897 Todo::create()
1898 .title("T1")
1899 .step(Step::create().description("S1")),
1900 )
1901 .exec(&mut db)
1902 .await?;
1903
1904 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1905 let step_id = Step::get_by_todo_id(&mut db, todo_id).await?.id;
1906
1907 let step = Step::filter_by_id(step_id)
1909 .include(Step::fields().todo().user())
1910 .get(&mut db)
1911 .await?;
1912
1913 assert_eq!("T1", step.todo.get().title);
1914 assert_eq!("Alice", step.todo.get().user.get().name);
1915
1916 Ok(())
1917}
1918
1919#[driver_test(id(ID))]
1922pub async fn nested_belongs_to_optional_then_has_many(test: &mut Test) -> Result<()> {
1923 #[derive(Debug, toasty::Model)]
1924 #[allow(dead_code)]
1925 struct Project {
1926 #[key]
1927 #[auto]
1928 id: ID,
1929
1930 name: String,
1931
1932 #[has_many]
1933 members: toasty::HasMany<Member>,
1934 }
1935
1936 #[derive(Debug, toasty::Model)]
1937 #[allow(dead_code)]
1938 struct Member {
1939 #[key]
1940 #[auto]
1941 id: ID,
1942
1943 name: String,
1944
1945 #[index]
1946 project_id: ID,
1947
1948 #[belongs_to(key = project_id, references = id)]
1949 project: toasty::BelongsTo<Project>,
1950 }
1951
1952 #[derive(Debug, toasty::Model)]
1953 #[allow(dead_code)]
1954 struct Task {
1955 #[key]
1956 #[auto]
1957 id: ID,
1958
1959 title: String,
1960
1961 #[index]
1962 project_id: Option<ID>,
1963
1964 #[belongs_to(key = project_id, references = id)]
1965 project: toasty::BelongsTo<Option<Project>>,
1966 }
1967
1968 let mut db = test.setup_db(models!(Project, Member, Task)).await;
1969
1970 let project = Project::create()
1971 .name("Proj1")
1972 .member(Member::create().name("Alice"))
1973 .member(Member::create().name("Bob"))
1974 .exec(&mut db)
1975 .await?;
1976
1977 let task = Task::create()
1979 .title("Linked")
1980 .project(&project)
1981 .exec(&mut db)
1982 .await?;
1983
1984 let task = Task::filter_by_id(task.id)
1985 .include(Task::fields().project().members())
1986 .get(&mut db)
1987 .await?;
1988
1989 let proj = task.project.get().as_ref().unwrap();
1990 assert_eq!("Proj1", proj.name);
1991 let mut names: Vec<&str> = proj.members.get().iter().map(|m| m.name.as_str()).collect();
1992 names.sort();
1993 assert_eq!(names, vec!["Alice", "Bob"]);
1994
1995 let orphan = Task::create().title("Orphan").exec(&mut db).await?;
1997
1998 let orphan = Task::filter_by_id(orphan.id)
1999 .include(Task::fields().project().members())
2000 .get(&mut db)
2001 .await?;
2002
2003 assert!(orphan.project.get().is_none());
2004
2005 Ok(())
2006}
2007
2008#[driver_test(id(ID))]
2011pub async fn nested_belongs_to_optional_then_belongs_to_optional(test: &mut Test) -> Result<()> {
2012 #[derive(Debug, toasty::Model)]
2013 #[allow(dead_code)]
2014 struct Category {
2015 #[key]
2016 #[auto]
2017 id: ID,
2018
2019 name: String,
2020 }
2021
2022 #[derive(Debug, toasty::Model)]
2023 #[allow(dead_code)]
2024 struct Post {
2025 #[key]
2026 #[auto]
2027 id: ID,
2028
2029 title: String,
2030
2031 #[index]
2032 category_id: Option<ID>,
2033
2034 #[belongs_to(key = category_id, references = id)]
2035 category: toasty::BelongsTo<Option<Category>>,
2036 }
2037
2038 #[derive(Debug, toasty::Model)]
2039 #[allow(dead_code)]
2040 struct Comment {
2041 #[key]
2042 #[auto]
2043 id: ID,
2044
2045 body: String,
2046
2047 #[index]
2048 post_id: Option<ID>,
2049
2050 #[belongs_to(key = post_id, references = id)]
2051 post: toasty::BelongsTo<Option<Post>>,
2052 }
2053
2054 let mut db = test.setup_db(models!(Category, Post, Comment)).await;
2055
2056 let cat = Category::create().name("Tech").exec(&mut db).await?;
2057 let post = Post::create()
2058 .title("Hello")
2059 .category(&cat)
2060 .exec(&mut db)
2061 .await?;
2062
2063 let c1 = Comment::create()
2065 .body("Nice")
2066 .post(&post)
2067 .exec(&mut db)
2068 .await?;
2069
2070 let c1 = Comment::filter_by_id(c1.id)
2071 .include(Comment::fields().post().category())
2072 .get(&mut db)
2073 .await?;
2074
2075 let loaded_post = c1.post.get().as_ref().unwrap();
2076 assert_eq!("Hello", loaded_post.title);
2077 let loaded_cat = loaded_post.category.get().as_ref().unwrap();
2078 assert_eq!("Tech", loaded_cat.name);
2079
2080 let post2 = Post::create().title("Uncategorized").exec(&mut db).await?;
2082 let c2 = Comment::create()
2083 .body("Hmm")
2084 .post(&post2)
2085 .exec(&mut db)
2086 .await?;
2087
2088 let c2 = Comment::filter_by_id(c2.id)
2089 .include(Comment::fields().post().category())
2090 .get(&mut db)
2091 .await?;
2092
2093 let loaded_post2 = c2.post.get().as_ref().unwrap();
2094 assert_eq!("Uncategorized", loaded_post2.title);
2095 assert!(loaded_post2.category.get().is_none());
2096
2097 let c3 = Comment::create().body("Orphan").exec(&mut db).await?;
2099
2100 let c3 = Comment::filter_by_id(c3.id)
2101 .include(Comment::fields().post().category())
2102 .get(&mut db)
2103 .await?;
2104
2105 assert!(c3.post.get().is_none());
2106
2107 Ok(())
2108}
2109
2110#[driver_test(id(ID))]
2113pub async fn nested_belongs_to_required_then_has_one_required(test: &mut Test) -> Result<()> {
2114 #[derive(Debug, toasty::Model)]
2115 #[allow(dead_code)]
2116 struct User {
2117 #[key]
2118 #[auto]
2119 id: ID,
2120
2121 name: String,
2122
2123 #[has_one]
2124 config: toasty::HasOne<Config>,
2125
2126 #[has_many]
2127 todos: toasty::HasMany<Todo>,
2128 }
2129
2130 #[derive(Debug, toasty::Model)]
2131 #[allow(dead_code)]
2132 struct Config {
2133 #[key]
2134 #[auto]
2135 id: ID,
2136
2137 theme: String,
2138
2139 #[unique]
2140 user_id: Option<ID>,
2141
2142 #[belongs_to(key = user_id, references = id)]
2143 user: toasty::BelongsTo<Option<User>>,
2144 }
2145
2146 #[derive(Debug, toasty::Model)]
2147 #[allow(dead_code)]
2148 struct Todo {
2149 #[key]
2150 #[auto]
2151 id: ID,
2152
2153 title: String,
2154
2155 #[index]
2156 user_id: ID,
2157
2158 #[belongs_to(key = user_id, references = id)]
2159 user: toasty::BelongsTo<User>,
2160 }
2161
2162 let mut db = test.setup_db(models!(User, Config, Todo)).await;
2163
2164 let user = User::create()
2165 .name("Alice")
2166 .config(Config::create().theme("dark"))
2167 .todo(Todo::create().title("Task"))
2168 .exec(&mut db)
2169 .await?;
2170
2171 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
2172
2173 let todo = Todo::filter_by_id(todo_id)
2174 .include(Todo::fields().user().config())
2175 .get(&mut db)
2176 .await?;
2177
2178 assert_eq!("Alice", todo.user.get().name);
2179 assert_eq!("dark", todo.user.get().config.get().theme);
2180
2181 Ok(())
2182}
2183
2184#[driver_test(id(ID))]
2188pub async fn nested_has_many_then_has_many_with_empty_leaves(test: &mut Test) {
2189 #[derive(Debug, toasty::Model)]
2190 #[allow(dead_code)]
2191 struct User {
2192 #[key]
2193 #[auto]
2194 id: ID,
2195
2196 name: String,
2197
2198 #[has_many]
2199 todos: toasty::HasMany<Todo>,
2200 }
2201
2202 #[derive(Debug, toasty::Model)]
2203 #[allow(dead_code)]
2204 struct Todo {
2205 #[key]
2206 #[auto]
2207 id: ID,
2208
2209 title: String,
2210
2211 #[index]
2212 user_id: ID,
2213
2214 #[belongs_to(key = user_id, references = id)]
2215 user: toasty::BelongsTo<User>,
2216
2217 #[has_many]
2218 steps: toasty::HasMany<Step>,
2219 }
2220
2221 #[derive(Debug, toasty::Model)]
2222 #[allow(dead_code)]
2223 struct Step {
2224 #[key]
2225 #[auto]
2226 id: ID,
2227
2228 description: String,
2229
2230 #[index]
2231 todo_id: ID,
2232
2233 #[belongs_to(key = todo_id, references = id)]
2234 todo: toasty::BelongsTo<Todo>,
2235 }
2236
2237 let mut db = test.setup_db(models!(User, Todo, Step)).await;
2238
2239 let user = User::create()
2240 .name("Alice")
2241 .todo(
2242 Todo::create()
2243 .title("With Steps")
2244 .step(Step::create().description("S1")),
2245 )
2246 .todo(Todo::create().title("No Steps")) .exec(&mut db)
2248 .await
2249 .unwrap();
2250
2251 let user = User::filter_by_id(user.id)
2252 .include(User::fields().todos().steps())
2253 .get(&mut db)
2254 .await
2255 .unwrap();
2256
2257 let todos = user.todos.get();
2258 assert_eq!(2, todos.len());
2259
2260 let mut total_steps = 0;
2261 for todo in todos {
2262 let steps = todo.steps.get();
2263 if todo.title == "With Steps" {
2264 assert_eq!(1, steps.len());
2265 assert_eq!("S1", steps[0].description);
2266 } else {
2267 assert_eq!(0, steps.len());
2268 }
2269 total_steps += steps.len();
2270 }
2271 assert_eq!(1, total_steps);
2272}
2273
2274#[driver_test(
2279 id(ID),
2280 requires(sql),
2281 scenario(crate::scenarios::has_many_multi_relation)
2282)]
2283pub async fn sibling_nested_includes_on_shared_prefix(test: &mut Test) -> Result<()> {
2284 let mut db = setup(test).await;
2285
2286 let category = toasty::create!(Category { name: "Food" })
2287 .exec(&mut db)
2288 .await?;
2289 let user = toasty::create!(User {
2290 name: "Alice",
2291 todos: [
2292 { title: "T1", category: &category },
2293 { title: "T2", category: &category },
2294 ],
2295 })
2296 .exec(&mut db)
2297 .await?;
2298
2299 let loaded = User::filter_by_id(user.id)
2302 .include(User::fields().todos().user())
2303 .include(User::fields().todos().category())
2304 .get(&mut db)
2305 .await?;
2306
2307 let todos = loaded.todos.get();
2308 assert_eq!(2, todos.len());
2309 for todo in todos {
2310 assert_eq!("Alice", todo.user.get().name);
2311 assert_eq!("Food", todo.category.get().name);
2312 }
2313
2314 Ok(())
2315}
2316
2317#[driver_test(
2321 id(ID),
2322 requires(sql),
2323 scenario(crate::scenarios::has_many_multi_relation)
2324)]
2325pub async fn bare_and_nested_includes_on_shared_prefix(test: &mut Test) -> Result<()> {
2326 let mut db = setup(test).await;
2327
2328 let category = toasty::create!(Category { name: "Food" })
2329 .exec(&mut db)
2330 .await?;
2331 let user = toasty::create!(User {
2332 name: "Alice",
2333 todos: [{ title: "T1", category: &category }],
2334 })
2335 .exec(&mut db)
2336 .await?;
2337
2338 let loaded = User::filter_by_id(user.id)
2339 .include(User::fields().todos()) .include(User::fields().todos().user()) .include(User::fields().todos().category()) .get(&mut db)
2343 .await?;
2344
2345 let todos = loaded.todos.get();
2346 assert_eq!(1, todos.len());
2347 assert_eq!("Alice", todos[0].user.get().name);
2348 assert_eq!("Food", todos[0].category.get().name);
2349
2350 Ok(())
2351}
2352
2353#[driver_test(id(ID))]
2358pub async fn sibling_nested_includes_on_shared_prefix_non_sql(test: &mut Test) -> Result<()> {
2359 #[derive(Debug, toasty::Model)]
2360 #[allow(dead_code)]
2361 struct Category {
2362 #[key]
2363 #[auto]
2364 id: ID,
2365
2366 name: String,
2367
2368 #[has_many]
2369 items: toasty::HasMany<Item>,
2370 }
2371
2372 #[derive(Debug, toasty::Model)]
2373 #[allow(dead_code)]
2374 struct Brand {
2375 #[key]
2376 #[auto]
2377 id: ID,
2378
2379 name: String,
2380 }
2381
2382 #[derive(Debug, toasty::Model)]
2383 #[allow(dead_code)]
2384 struct Supplier {
2385 #[key]
2386 #[auto]
2387 id: ID,
2388
2389 name: String,
2390 }
2391
2392 #[derive(Debug, toasty::Model)]
2393 #[allow(dead_code)]
2394 struct Item {
2395 #[key]
2396 #[auto]
2397 id: ID,
2398
2399 title: String,
2400
2401 #[index]
2402 category_id: ID,
2403
2404 #[belongs_to(key = category_id, references = id)]
2405 category: toasty::BelongsTo<Category>,
2406
2407 #[index]
2408 brand_id: ID,
2409
2410 #[belongs_to(key = brand_id, references = id)]
2411 brand: toasty::BelongsTo<Brand>,
2412
2413 #[index]
2414 supplier_id: ID,
2415
2416 #[belongs_to(key = supplier_id, references = id)]
2417 supplier: toasty::BelongsTo<Supplier>,
2418 }
2419
2420 let mut db = test
2421 .setup_db(models!(Category, Brand, Supplier, Item))
2422 .await;
2423
2424 let brand_a = toasty::create!(Brand { name: "BrandA" })
2425 .exec(&mut db)
2426 .await?;
2427 let brand_b = toasty::create!(Brand { name: "BrandB" })
2428 .exec(&mut db)
2429 .await?;
2430 let sup_a = toasty::create!(Supplier { name: "SupA" })
2431 .exec(&mut db)
2432 .await?;
2433 let sup_b = toasty::create!(Supplier { name: "SupB" })
2434 .exec(&mut db)
2435 .await?;
2436
2437 let cat = toasty::create!(Category {
2438 name: "Electronics",
2439 items: [
2440 { title: "Phone", brand: &brand_a, supplier: &sup_a },
2441 { title: "Laptop", brand: &brand_b, supplier: &sup_b },
2442 ],
2443 })
2444 .exec(&mut db)
2445 .await?;
2446
2447 let loaded = Category::filter_by_id(cat.id)
2450 .include(Category::fields().items().brand())
2451 .include(Category::fields().items().supplier())
2452 .get(&mut db)
2453 .await?;
2454
2455 let items = loaded.items.get();
2456 assert_eq!(2, items.len());
2457 let mut pairs: Vec<(&str, &str)> = items
2458 .iter()
2459 .map(|i| (i.brand.get().name.as_str(), i.supplier.get().name.as_str()))
2460 .collect();
2461 pairs.sort();
2462 assert_eq!(pairs, vec![("BrandA", "SupA"), ("BrandB", "SupB")]);
2463
2464 Ok(())
2465}