1use crate::prelude::*;
2
3#[driver_test(scenario(crate::scenarios::has_one_optional_belongs_to::id_uuid))]
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(scenario(crate::scenarios::has_many_belongs_to::id_uuid))]
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 .todos([Todo::create().title("todo 1")])
49 .todos([Todo::create().title("todo 2")])
50 .todos([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(scenario(crate::scenarios::user_two_children))]
76pub async fn multiple_includes_same_model(test: &mut Test) -> Result<()> {
77 let mut db = setup(test).await;
78
79 let user = User::create().name("Test User").exec(&mut db).await?;
81
82 Post::create()
84 .title("Post 1")
85 .user(&user)
86 .exec(&mut db)
87 .await?;
88
89 Post::create()
90 .title("Post 2")
91 .user(&user)
92 .exec(&mut db)
93 .await?;
94
95 Comment::create()
97 .text("Comment 1")
98 .user(&user)
99 .exec(&mut db)
100 .await?;
101
102 Comment::create()
103 .text("Comment 2")
104 .user(&user)
105 .exec(&mut db)
106 .await?;
107
108 Comment::create()
109 .text("Comment 3")
110 .user(&user)
111 .exec(&mut db)
112 .await?;
113
114 let user_with_posts = User::filter_by_id(user.id)
116 .include(User::fields().posts())
117 .get(&mut db)
118 .await?;
119 assert_eq!(2, user_with_posts.posts.get().len());
120
121 let user_with_comments = User::filter_by_id(user.id)
122 .include(User::fields().comments())
123 .get(&mut db)
124 .await?;
125 assert_eq!(3, user_with_comments.comments.get().len());
126
127 let loaded_user = User::filter_by_id(user.id)
129 .include(User::fields().posts()) .include(User::fields().comments()) .get(&mut db)
132 .await?;
133
134 assert_eq!(2, loaded_user.posts.get().len());
135 assert_eq!(3, loaded_user.comments.get().len());
136 Ok(())
137}
138
139#[driver_test(scenario(crate::scenarios::has_one_optional_belongs_to::id_uuid))]
140pub async fn basic_has_one_and_belongs_to_preload(test: &mut Test) -> Result<()> {
141 let mut db = setup(test).await;
142
143 let user = User::create()
145 .name("John Doe")
146 .profile(Profile::create().bio("A person"))
147 .exec(&mut db)
148 .await?;
149
150 let user = User::filter_by_id(user.id)
152 .include(User::fields().profile())
153 .get(&mut db)
154 .await?;
155
156 let profile = user.profile.get().as_ref().unwrap();
158 assert_eq!("A person", profile.bio);
159 assert_eq!(user.id, *profile.user_id.as_ref().unwrap());
160
161 let profile_id = profile.id;
162
163 let profile = Profile::filter_by_id(profile_id)
165 .include(Profile::fields().user())
166 .get(&mut db)
167 .await?;
168
169 assert_eq!(user.id, profile.user.get().as_ref().unwrap().id);
170 assert_eq!("John Doe", profile.user.get().as_ref().unwrap().name);
171 Ok(())
172}
173
174#[driver_test(scenario(crate::scenarios::user_profile_settings))]
175pub async fn multiple_includes_with_has_one(test: &mut Test) -> Result<()> {
176 let mut db = setup(test).await;
177
178 let user = User::create()
180 .name("Jane Doe")
181 .profile(Profile::create().bio("Software engineer"))
182 .settings(Settings::create().theme("dark"))
183 .exec(&mut db)
184 .await?;
185
186 let user_with_profile = User::filter_by_id(user.id)
188 .include(User::fields().profile())
189 .get(&mut db)
190 .await?;
191 assert!(user_with_profile.profile.get().is_some());
192 assert_eq!(
193 "Software engineer",
194 user_with_profile.profile.get().as_ref().unwrap().bio
195 );
196
197 let user_with_settings = User::filter_by_id(user.id)
198 .include(User::fields().settings())
199 .get(&mut db)
200 .await?;
201 assert!(user_with_settings.settings.get().is_some());
202 assert_eq!(
203 "dark",
204 user_with_settings.settings.get().as_ref().unwrap().theme
205 );
206
207 let loaded_user = User::filter_by_id(user.id)
209 .include(User::fields().profile()) .include(User::fields().settings()) .get(&mut db)
212 .await?;
213
214 assert!(loaded_user.profile.get().is_some());
215 assert_eq!(
216 "Software engineer",
217 loaded_user.profile.get().as_ref().unwrap().bio
218 );
219 assert!(loaded_user.settings.get().is_some());
220 assert_eq!("dark", loaded_user.settings.get().as_ref().unwrap().theme);
221 Ok(())
222}
223
224#[driver_test(id(ID), scenario(crate::scenarios::user_profile_and_todos))]
225pub async fn combined_has_many_and_has_one_preload(test: &mut Test) -> Result<()> {
226 let mut db = setup(test).await;
227
228 let user = if driver_test_cfg!(id_u64) && !test.capability().returning_from_mutation {
232 let user = User::create()
233 .name("Bob Smith")
234 .profile(Profile::create().bio("Developer"))
235 .exec(&mut db)
236 .await?;
237
238 for title in ["Task 1", "Task 2", "Task 3"] {
239 user.todos().create().title(title).exec(&mut db).await?;
240 }
241
242 user
243 } else {
244 User::create()
245 .name("Bob Smith")
246 .profile(Profile::create().bio("Developer"))
247 .todos([Todo::create().title("Task 1")])
248 .todos([Todo::create().title("Task 2")])
249 .todos([Todo::create().title("Task 3")])
250 .exec(&mut db)
251 .await?
252 };
253
254 let loaded_user = User::filter_by_id(user.id)
256 .include(User::fields().profile()) .include(User::fields().todos()) .get(&mut db)
259 .await?;
260
261 assert!(loaded_user.profile.get().is_some());
263 assert_eq!("Developer", loaded_user.profile.get().as_ref().unwrap().bio);
264
265 assert_eq!(3, loaded_user.todos.get().len());
267 let todo_titles: Vec<&str> = loaded_user
268 .todos
269 .get()
270 .iter()
271 .map(|t| t.title.as_str())
272 .collect();
273 assert!(todo_titles.contains(&"Task 1"));
274 assert!(todo_titles.contains(&"Task 2"));
275 assert!(todo_titles.contains(&"Task 3"));
276 Ok(())
277}
278
279#[driver_test(
280 requires(scan),
281 scenario(crate::scenarios::has_many_belongs_to::id_uuid)
282)]
283pub async fn preload_on_empty_table(test: &mut Test) -> Result<()> {
284 let mut db = setup(test).await;
285
286 let users: Vec<User> = User::all()
288 .include(User::fields().todos())
289 .exec(&mut db)
290 .await?;
291
292 assert_eq!(0, users.len());
293 Ok(())
294}
295
296#[driver_test(scenario(crate::scenarios::has_many_belongs_to::id_uuid))]
297pub async fn preload_on_empty_query(test: &mut Test) -> Result<()> {
298 let mut db = setup(test).await;
299
300 let users: Vec<User> = User::filter_by_name("foo")
302 .include(User::fields().todos())
303 .exec(&mut db)
304 .await?;
305
306 assert_eq!(0, users.len());
307 Ok(())
308}
309
310#[driver_test(scenario(crate::scenarios::has_many_nullable_fk))]
313pub async fn preload_has_many_with_optional_belongs_to(test: &mut Test) -> Result<()> {
314 let mut db = setup(test).await;
315
316 let user = User::create()
318 .todos([Todo::create().title("Task 1")])
319 .todos([Todo::create().title("Task 2")])
320 .exec(&mut db)
321 .await?;
322
323 let user = User::filter_by_id(user.id)
325 .include(User::fields().todos())
326 .get(&mut db)
327 .await?;
328
329 assert_eq!(2, user.todos.get().len());
330
331 let todo_id = user.todos.get()[0].id;
332
333 let todo = Todo::filter_by_id(todo_id)
335 .include(Todo::fields().user())
336 .get(&mut db)
337 .await?;
338
339 assert_eq!(user.id, todo.user.get().as_ref().unwrap().id);
340
341 let orphan = Todo::create().title("Orphan").exec(&mut db).await?;
343
344 let orphan = Todo::filter_by_id(orphan.id)
346 .include(Todo::fields().user())
347 .get(&mut db)
348 .await?;
349
350 assert!(orphan.user.get().is_none());
351
352 Ok(())
353}
354
355#[driver_test]
359pub async fn preload_has_one_optional_with_required_belongs_to(test: &mut Test) -> Result<()> {
360 #[derive(Debug, toasty::Model)]
361 struct User {
362 #[key]
363 #[auto]
364 id: uuid::Uuid,
365
366 name: String,
367
368 #[has_one]
369 profile: toasty::Deferred<Option<Profile>>,
370 }
371
372 #[derive(Debug, toasty::Model)]
373 struct Profile {
374 #[key]
375 #[auto]
376 id: uuid::Uuid,
377
378 bio: String,
379
380 #[unique]
381 user_id: uuid::Uuid,
382
383 #[belongs_to(key = user_id, references = id)]
384 user: toasty::Deferred<User>,
385 }
386
387 let mut db = test.setup_db(models!(User, Profile)).await;
388
389 let user_with = User::create()
391 .name("Has Profile")
392 .profile(Profile::create().bio("hello"))
393 .exec(&mut db)
394 .await?;
395
396 let user_without = User::create().name("No Profile").exec(&mut db).await?;
398
399 let loaded = User::filter_by_id(user_with.id)
401 .include(User::fields().profile())
402 .get(&mut db)
403 .await?;
404
405 let profile = loaded.profile.get().as_ref().unwrap();
406 assert_eq!("hello", profile.bio);
407 assert_eq!(user_with.id, profile.user_id);
408
409 let loaded = User::filter_by_id(user_without.id)
411 .include(User::fields().profile())
412 .get(&mut db)
413 .await?;
414
415 assert!(loaded.profile.get().is_none());
416
417 let profile = Profile::filter_by_user_id(user_with.id)
419 .include(Profile::fields().user())
420 .get(&mut db)
421 .await?;
422
423 assert_eq!(user_with.id, profile.user.get().id);
424 assert_eq!("Has Profile", profile.user.get().name);
425
426 Ok(())
427}
428
429#[driver_test]
433pub async fn preload_has_one_required_with_optional_belongs_to(test: &mut Test) -> Result<()> {
434 #[derive(Debug, toasty::Model)]
435 struct User {
436 #[key]
437 #[auto]
438 id: uuid::Uuid,
439
440 name: String,
441
442 #[has_one]
443 profile: toasty::Deferred<Profile>,
444 }
445
446 #[derive(Debug, toasty::Model)]
447 struct Profile {
448 #[key]
449 #[auto]
450 id: uuid::Uuid,
451
452 bio: String,
453
454 #[unique]
455 user_id: Option<uuid::Uuid>,
456
457 #[belongs_to(key = user_id, references = id)]
458 user: toasty::Deferred<Option<User>>,
459 }
460
461 let mut db = test.setup_db(models!(User, Profile)).await;
462
463 let user = User::create()
465 .name("Alice")
466 .profile(Profile::create().bio("a bio"))
467 .exec(&mut db)
468 .await?;
469
470 let loaded = User::filter_by_id(user.id)
472 .include(User::fields().profile())
473 .get(&mut db)
474 .await?;
475
476 let profile = loaded.profile.get();
477 assert_eq!("a bio", profile.bio);
478 assert_eq!(user.id, *profile.user_id.as_ref().unwrap());
479
480 let profile = Profile::filter_by_id(profile.id)
482 .include(Profile::fields().user())
483 .get(&mut db)
484 .await?;
485
486 assert_eq!(user.id, profile.user.get().as_ref().unwrap().id);
487 assert_eq!("Alice", profile.user.get().as_ref().unwrap().name);
488
489 Ok(())
490}
491
492#[driver_test(scenario(crate::scenarios::user_todo_step))]
493pub async fn nested_has_many_preload(test: &mut Test) {
494 let mut db = setup(test).await;
495
496 let user = User::create()
498 .name("Alice")
499 .todos([Todo::create()
500 .title("Todo 1")
501 .steps([Step::create().description("Step 1a")])
502 .steps([Step::create().description("Step 1b")])])
503 .todos([Todo::create()
504 .title("Todo 2")
505 .steps([Step::create().description("Step 2a")])
506 .steps([Step::create().description("Step 2b")])
507 .steps([Step::create().description("Step 2c")])])
508 .exec(&mut db)
509 .await
510 .unwrap();
511
512 let user = User::filter_by_id(user.id)
514 .include(User::fields().todos().steps())
515 .get(&mut db)
516 .await
517 .unwrap();
518
519 let todos = user.todos.get();
521 assert_eq!(2, todos.len());
522
523 let mut all_step_descriptions: Vec<&str> = Vec::new();
525 for todo in todos {
526 let steps = todo.steps.get();
527 for step in steps {
528 all_step_descriptions.push(&step.description);
529 }
530 }
531 all_step_descriptions.sort();
532 assert_eq!(
533 all_step_descriptions,
534 vec!["Step 1a", "Step 1b", "Step 2a", "Step 2b", "Step 2c"]
535 );
536}
537
538#[driver_test]
541pub async fn nested_has_many_then_has_one_optional(test: &mut Test) -> Result<()> {
542 #[derive(Debug, toasty::Model)]
543 #[allow(dead_code)]
544 struct User {
545 #[key]
546 #[auto]
547 id: uuid::Uuid,
548
549 name: String,
550
551 #[has_many]
552 posts: toasty::Deferred<Vec<Post>>,
553 }
554
555 #[derive(Debug, toasty::Model)]
556 #[allow(dead_code)]
557 struct Post {
558 #[key]
559 #[auto]
560 id: uuid::Uuid,
561
562 title: String,
563
564 #[index]
565 user_id: uuid::Uuid,
566
567 #[belongs_to(key = user_id, references = id)]
568 user: toasty::Deferred<User>,
569
570 #[has_one]
571 detail: toasty::Deferred<Option<Detail>>,
572 }
573
574 #[derive(Debug, toasty::Model)]
575 #[allow(dead_code)]
576 struct Detail {
577 #[key]
578 #[auto]
579 id: uuid::Uuid,
580
581 body: String,
582
583 #[unique]
584 post_id: Option<uuid::Uuid>,
585
586 #[belongs_to(key = post_id, references = id)]
587 post: toasty::Deferred<Option<Post>>,
588 }
589
590 let mut db = test.setup_db(models!(User, Post, Detail)).await;
591
592 let user = User::create()
593 .name("Alice")
594 .posts([Post::create()
595 .title("P1")
596 .detail(Detail::create().body("D1"))])
597 .posts([Post::create().title("P2")]) .exec(&mut db)
599 .await?;
600
601 let user = User::filter_by_id(user.id)
602 .include(User::fields().posts().detail())
603 .get(&mut db)
604 .await?;
605
606 let posts = user.posts.get();
607 assert_eq!(2, posts.len());
608
609 let mut with_detail = 0;
610 let mut without_detail = 0;
611 for post in posts {
612 match post.detail.get() {
613 Some(d) => {
614 assert_eq!("D1", d.body);
615 with_detail += 1;
616 }
617 None => without_detail += 1,
618 }
619 }
620 assert_eq!(1, with_detail);
621 assert_eq!(1, without_detail);
622
623 Ok(())
624}
625
626#[driver_test]
629pub async fn nested_has_many_then_has_one_required(test: &mut Test) -> Result<()> {
630 #[derive(Debug, toasty::Model)]
631 #[allow(dead_code)]
632 struct User {
633 #[key]
634 #[auto]
635 id: uuid::Uuid,
636
637 name: String,
638
639 #[has_many]
640 accounts: toasty::Deferred<Vec<Account>>,
641 }
642
643 #[derive(Debug, toasty::Model)]
644 #[allow(dead_code)]
645 struct Account {
646 #[key]
647 #[auto]
648 id: uuid::Uuid,
649
650 label: String,
651
652 #[index]
653 user_id: uuid::Uuid,
654
655 #[belongs_to(key = user_id, references = id)]
656 user: toasty::Deferred<User>,
657
658 #[has_one]
659 settings: toasty::Deferred<Settings>,
660 }
661
662 #[derive(Debug, toasty::Model)]
663 #[allow(dead_code)]
664 struct Settings {
665 #[key]
666 #[auto]
667 id: uuid::Uuid,
668
669 theme: String,
670
671 #[unique]
672 account_id: Option<uuid::Uuid>,
673
674 #[belongs_to(key = account_id, references = id)]
675 account: toasty::Deferred<Option<Account>>,
676 }
677
678 let mut db = test.setup_db(models!(User, Account, Settings)).await;
679
680 let user = User::create()
681 .name("Bob")
682 .accounts([Account::create()
683 .label("A1")
684 .settings(Settings::create().theme("dark"))])
685 .accounts([Account::create()
686 .label("A2")
687 .settings(Settings::create().theme("light"))])
688 .exec(&mut db)
689 .await?;
690
691 let user = User::filter_by_id(user.id)
692 .include(User::fields().accounts().settings())
693 .get(&mut db)
694 .await?;
695
696 let accounts = user.accounts.get();
697 assert_eq!(2, accounts.len());
698
699 let mut themes: Vec<&str> = accounts
700 .iter()
701 .map(|a| a.settings.get().theme.as_str())
702 .collect();
703 themes.sort();
704 assert_eq!(themes, vec!["dark", "light"]);
705
706 Ok(())
707}
708
709#[driver_test]
712pub async fn nested_has_many_then_belongs_to_required(test: &mut Test) -> Result<()> {
713 #[derive(Debug, toasty::Model)]
714 #[allow(dead_code)]
715 struct Category {
716 #[key]
717 #[auto]
718 id: uuid::Uuid,
719
720 name: String,
721
722 #[has_many]
723 items: toasty::Deferred<Vec<Item>>,
724 }
725
726 #[derive(Debug, toasty::Model)]
727 #[allow(dead_code)]
728 struct Brand {
729 #[key]
730 #[auto]
731 id: uuid::Uuid,
732
733 name: String,
734 }
735
736 #[derive(Debug, toasty::Model)]
737 #[allow(dead_code)]
738 struct Item {
739 #[key]
740 #[auto]
741 id: uuid::Uuid,
742
743 title: String,
744
745 #[index]
746 category_id: uuid::Uuid,
747
748 #[belongs_to(key = category_id, references = id)]
749 category: toasty::Deferred<Category>,
750
751 #[index]
752 brand_id: uuid::Uuid,
753
754 #[belongs_to(key = brand_id, references = id)]
755 brand: toasty::Deferred<Brand>,
756 }
757
758 let mut db = test.setup_db(models!(Category, Brand, Item)).await;
759
760 let brand_a = Brand::create().name("BrandA").exec(&mut db).await?;
761 let brand_b = Brand::create().name("BrandB").exec(&mut db).await?;
762
763 let cat = Category::create()
764 .name("Electronics")
765 .items([Item::create().title("Phone").brand(&brand_a)])
766 .items([Item::create().title("Laptop").brand(&brand_b)])
767 .exec(&mut db)
768 .await?;
769
770 let cat = Category::filter_by_id(cat.id)
771 .include(Category::fields().items().brand())
772 .get(&mut db)
773 .await?;
774
775 let items = cat.items.get();
776 assert_eq!(2, items.len());
777
778 let mut brand_names: Vec<&str> = items.iter().map(|i| i.brand.get().name.as_str()).collect();
779 brand_names.sort();
780 assert_eq!(brand_names, vec!["BrandA", "BrandB"]);
781
782 let shared = Category::create()
785 .name("Shared brand")
786 .items([Item::create().title("Watch").brand(&brand_a)])
787 .items([Item::create().title("Tablet").brand(&brand_a)])
788 .exec(&mut db)
789 .await?;
790
791 let shared = Category::filter_by_id(shared.id)
792 .include(Category::fields().items().brand())
793 .get(&mut db)
794 .await?;
795
796 let items = shared.items.get();
797 assert_eq!(2, items.len());
798 for item in items {
799 assert_eq!("BrandA", item.brand.get().name);
800 }
801
802 Ok(())
803}
804
805#[driver_test]
808pub async fn nested_has_many_then_belongs_to_optional(test: &mut Test) -> Result<()> {
809 #[derive(Debug, toasty::Model)]
810 #[allow(dead_code)]
811 struct Team {
812 #[key]
813 #[auto]
814 id: uuid::Uuid,
815
816 name: String,
817
818 #[has_many]
819 tasks: toasty::Deferred<Vec<Task>>,
820 }
821
822 #[derive(Debug, toasty::Model)]
823 #[allow(dead_code)]
824 struct Assignee {
825 #[key]
826 #[auto]
827 id: uuid::Uuid,
828
829 name: String,
830 }
831
832 #[derive(Debug, toasty::Model)]
833 #[allow(dead_code)]
834 struct Task {
835 #[key]
836 #[auto]
837 id: uuid::Uuid,
838
839 title: String,
840
841 #[index]
842 team_id: uuid::Uuid,
843
844 #[belongs_to(key = team_id, references = id)]
845 team: toasty::Deferred<Team>,
846
847 #[index]
848 assignee_id: Option<uuid::Uuid>,
849
850 #[belongs_to(key = assignee_id, references = id)]
851 assignee: toasty::Deferred<Option<Assignee>>,
852 }
853
854 let mut db = test.setup_db(models!(Team, Assignee, Task)).await;
855
856 let person = Assignee::create().name("Alice").exec(&mut db).await?;
857
858 let team = Team::create()
859 .name("Engineering")
860 .tasks([Task::create().title("Assigned").assignee(&person)])
861 .tasks([Task::create().title("Unassigned")])
862 .exec(&mut db)
863 .await?;
864
865 let team = Team::filter_by_id(team.id)
866 .include(Team::fields().tasks().assignee())
867 .get(&mut db)
868 .await?;
869
870 let tasks = team.tasks.get();
871 assert_eq!(2, tasks.len());
872
873 let mut assigned = 0;
874 let mut unassigned = 0;
875 for task in tasks {
876 match task.assignee.get() {
877 Some(a) => {
878 assert_eq!("Alice", a.name);
879 assigned += 1;
880 }
881 None => unassigned += 1,
882 }
883 }
884 assert_eq!(1, assigned);
885 assert_eq!(1, unassigned);
886
887 Ok(())
888}
889
890#[driver_test]
893pub async fn nested_has_one_optional_then_has_many(test: &mut Test) -> Result<()> {
894 #[derive(Debug, toasty::Model)]
895 #[allow(dead_code)]
896 struct User {
897 #[key]
898 #[auto]
899 id: uuid::Uuid,
900
901 name: String,
902
903 #[has_one]
904 profile: toasty::Deferred<Option<Profile>>,
905 }
906
907 #[derive(Debug, toasty::Model)]
908 #[allow(dead_code)]
909 struct Profile {
910 #[key]
911 #[auto]
912 id: uuid::Uuid,
913
914 bio: String,
915
916 #[unique]
917 user_id: Option<uuid::Uuid>,
918
919 #[belongs_to(key = user_id, references = id)]
920 user: toasty::Deferred<Option<User>>,
921
922 #[has_many]
923 badges: toasty::Deferred<Vec<Badge>>,
924 }
925
926 #[derive(Debug, toasty::Model)]
927 #[allow(dead_code)]
928 struct Badge {
929 #[key]
930 #[auto]
931 id: uuid::Uuid,
932
933 label: String,
934
935 #[index]
936 profile_id: uuid::Uuid,
937
938 #[belongs_to(key = profile_id, references = id)]
939 profile: toasty::Deferred<Profile>,
940 }
941
942 let mut db = test.setup_db(models!(User, Profile, Badge)).await;
943
944 let user = User::create()
946 .name("Alice")
947 .profile(
948 Profile::create()
949 .bio("hi")
950 .badges([Badge::create().label("Gold")])
951 .badges([Badge::create().label("Silver")]),
952 )
953 .exec(&mut db)
954 .await?;
955
956 let user = User::filter_by_id(user.id)
957 .include(User::fields().profile().badges())
958 .get(&mut db)
959 .await?;
960
961 let profile = user.profile.get().as_ref().unwrap();
962 assert_eq!("hi", profile.bio);
963 let mut labels: Vec<&str> = profile
964 .badges
965 .get()
966 .iter()
967 .map(|b| b.label.as_str())
968 .collect();
969 labels.sort();
970 assert_eq!(labels, vec!["Gold", "Silver"]);
971
972 let user2 = User::create().name("Bob").exec(&mut db).await?;
974
975 let user2 = User::filter_by_id(user2.id)
976 .include(User::fields().profile().badges())
977 .get(&mut db)
978 .await?;
979
980 assert!(user2.profile.get().is_none());
981
982 Ok(())
983}
984
985#[driver_test]
988pub async fn nested_has_one_required_then_has_many(test: &mut Test) -> Result<()> {
989 #[derive(Debug, toasty::Model)]
990 #[allow(dead_code)]
991 struct Order {
992 #[key]
993 #[auto]
994 id: uuid::Uuid,
995
996 label: String,
997
998 #[has_one]
999 invoice: toasty::Deferred<Invoice>,
1000 }
1001
1002 #[derive(Debug, toasty::Model)]
1003 #[allow(dead_code)]
1004 struct Invoice {
1005 #[key]
1006 #[auto]
1007 id: uuid::Uuid,
1008
1009 code: String,
1010
1011 #[unique]
1012 order_id: Option<uuid::Uuid>,
1013
1014 #[belongs_to(key = order_id, references = id)]
1015 order: toasty::Deferred<Option<Order>>,
1016
1017 #[has_many]
1018 line_items: toasty::Deferred<Vec<LineItem>>,
1019 }
1020
1021 #[derive(Debug, toasty::Model)]
1022 #[allow(dead_code)]
1023 struct LineItem {
1024 #[key]
1025 #[auto]
1026 id: uuid::Uuid,
1027
1028 description: String,
1029
1030 #[index]
1031 invoice_id: uuid::Uuid,
1032
1033 #[belongs_to(key = invoice_id, references = id)]
1034 invoice: toasty::Deferred<Invoice>,
1035 }
1036
1037 let mut db = test.setup_db(models!(Order, Invoice, LineItem)).await;
1038
1039 let order = Order::create()
1040 .label("Order1")
1041 .invoice(
1042 Invoice::create()
1043 .code("INV-001")
1044 .line_items([LineItem::create().description("Widget")])
1045 .line_items([LineItem::create().description("Gadget")]),
1046 )
1047 .exec(&mut db)
1048 .await?;
1049
1050 let order = Order::filter_by_id(order.id)
1051 .include(Order::fields().invoice().line_items())
1052 .get(&mut db)
1053 .await?;
1054
1055 let invoice = order.invoice.get();
1056 assert_eq!("INV-001", invoice.code);
1057 let mut descs: Vec<&str> = invoice
1058 .line_items
1059 .get()
1060 .iter()
1061 .map(|li| li.description.as_str())
1062 .collect();
1063 descs.sort();
1064 assert_eq!(descs, vec!["Gadget", "Widget"]);
1065
1066 Ok(())
1067}
1068
1069#[driver_test]
1072pub async fn nested_has_one_optional_then_has_one_optional(test: &mut Test) -> Result<()> {
1073 #[derive(Debug, toasty::Model)]
1074 #[allow(dead_code)]
1075 struct User {
1076 #[key]
1077 #[auto]
1078 id: uuid::Uuid,
1079
1080 name: String,
1081
1082 #[has_one]
1083 profile: toasty::Deferred<Option<Profile>>,
1084 }
1085
1086 #[derive(Debug, toasty::Model)]
1087 #[allow(dead_code)]
1088 struct Profile {
1089 #[key]
1090 #[auto]
1091 id: uuid::Uuid,
1092
1093 bio: String,
1094
1095 #[unique]
1096 user_id: Option<uuid::Uuid>,
1097
1098 #[belongs_to(key = user_id, references = id)]
1099 user: toasty::Deferred<Option<User>>,
1100
1101 #[has_one]
1102 avatar: toasty::Deferred<Option<Avatar>>,
1103 }
1104
1105 #[derive(Debug, toasty::Model)]
1106 #[allow(dead_code)]
1107 struct Avatar {
1108 #[key]
1109 #[auto]
1110 id: uuid::Uuid,
1111
1112 url: String,
1113
1114 #[unique]
1115 profile_id: Option<uuid::Uuid>,
1116
1117 #[belongs_to(key = profile_id, references = id)]
1118 profile: toasty::Deferred<Option<Profile>>,
1119 }
1120
1121 let mut db = test.setup_db(models!(User, Profile, Avatar)).await;
1122
1123 let user = User::create()
1125 .name("Alice")
1126 .profile(
1127 Profile::create()
1128 .bio("hi")
1129 .avatar(Avatar::create().url("pic.png")),
1130 )
1131 .exec(&mut db)
1132 .await?;
1133
1134 let user = User::filter_by_id(user.id)
1135 .include(User::fields().profile().avatar())
1136 .get(&mut db)
1137 .await?;
1138
1139 let profile = user.profile.get().as_ref().unwrap();
1140 assert_eq!("hi", profile.bio);
1141 let avatar = profile.avatar.get().as_ref().unwrap();
1142 assert_eq!("pic.png", avatar.url);
1143
1144 let user2 = User::create()
1146 .name("Bob")
1147 .profile(Profile::create().bio("no pic"))
1148 .exec(&mut db)
1149 .await?;
1150
1151 let user2 = User::filter_by_id(user2.id)
1152 .include(User::fields().profile().avatar())
1153 .get(&mut db)
1154 .await?;
1155
1156 let profile2 = user2.profile.get().as_ref().unwrap();
1157 assert_eq!("no pic", profile2.bio);
1158 assert!(profile2.avatar.get().is_none());
1159
1160 let user3 = User::create().name("Carol").exec(&mut db).await?;
1162
1163 let user3 = User::filter_by_id(user3.id)
1164 .include(User::fields().profile().avatar())
1165 .get(&mut db)
1166 .await?;
1167
1168 assert!(user3.profile.get().is_none());
1169
1170 Ok(())
1171}
1172
1173#[driver_test]
1176pub async fn nested_has_one_required_then_has_one_required(test: &mut Test) -> Result<()> {
1177 #[derive(Debug, toasty::Model)]
1178 #[allow(dead_code)]
1179 struct User {
1180 #[key]
1181 #[auto]
1182 id: uuid::Uuid,
1183
1184 name: String,
1185
1186 #[has_one]
1187 profile: toasty::Deferred<Profile>,
1188 }
1189
1190 #[derive(Debug, toasty::Model)]
1191 #[allow(dead_code)]
1192 struct Profile {
1193 #[key]
1194 #[auto]
1195 id: uuid::Uuid,
1196
1197 bio: String,
1198
1199 #[unique]
1200 user_id: Option<uuid::Uuid>,
1201
1202 #[belongs_to(key = user_id, references = id)]
1203 user: toasty::Deferred<Option<User>>,
1204
1205 #[has_one]
1206 avatar: toasty::Deferred<Avatar>,
1207 }
1208
1209 #[derive(Debug, toasty::Model)]
1210 #[allow(dead_code)]
1211 struct Avatar {
1212 #[key]
1213 #[auto]
1214 id: uuid::Uuid,
1215
1216 url: String,
1217
1218 #[unique]
1219 profile_id: Option<uuid::Uuid>,
1220
1221 #[belongs_to(key = profile_id, references = id)]
1222 profile: toasty::Deferred<Option<Profile>>,
1223 }
1224
1225 let mut db = test.setup_db(models!(User, Profile, Avatar)).await;
1226
1227 let user = User::create()
1228 .name("Alice")
1229 .profile(
1230 Profile::create()
1231 .bio("engineer")
1232 .avatar(Avatar::create().url("alice.jpg")),
1233 )
1234 .exec(&mut db)
1235 .await?;
1236
1237 let user = User::filter_by_id(user.id)
1238 .include(User::fields().profile().avatar())
1239 .get(&mut db)
1240 .await?;
1241
1242 let profile = user.profile.get();
1243 assert_eq!("engineer", profile.bio);
1244 let avatar = profile.avatar.get();
1245 assert_eq!("alice.jpg", avatar.url);
1246
1247 Ok(())
1248}
1249
1250#[driver_test]
1253pub async fn nested_has_one_optional_then_belongs_to_required(test: &mut Test) -> Result<()> {
1254 #[derive(Debug, toasty::Model)]
1255 #[allow(dead_code)]
1256 struct User {
1257 #[key]
1258 #[auto]
1259 id: uuid::Uuid,
1260
1261 name: String,
1262
1263 #[has_one]
1264 review: toasty::Deferred<Option<Review>>,
1265 }
1266
1267 #[derive(Debug, toasty::Model)]
1268 #[allow(dead_code)]
1269 struct Product {
1270 #[key]
1271 #[auto]
1272 id: uuid::Uuid,
1273
1274 name: String,
1275 }
1276
1277 #[derive(Debug, toasty::Model)]
1278 #[allow(dead_code)]
1279 struct Review {
1280 #[key]
1281 #[auto]
1282 id: uuid::Uuid,
1283
1284 body: String,
1285
1286 #[unique]
1287 user_id: Option<uuid::Uuid>,
1288
1289 #[belongs_to(key = user_id, references = id)]
1290 user: toasty::Deferred<Option<User>>,
1291
1292 #[index]
1293 product_id: uuid::Uuid,
1294
1295 #[belongs_to(key = product_id, references = id)]
1296 product: toasty::Deferred<Product>,
1297 }
1298
1299 let mut db = test.setup_db(models!(User, Product, Review)).await;
1300
1301 let product = Product::create().name("Widget").exec(&mut db).await?;
1302
1303 let user = User::create()
1304 .name("Alice")
1305 .review(Review::create().body("Great!").product(&product))
1306 .exec(&mut db)
1307 .await?;
1308
1309 let user = User::filter_by_id(user.id)
1311 .include(User::fields().review().product())
1312 .get(&mut db)
1313 .await?;
1314
1315 let review = user.review.get().as_ref().unwrap();
1316 assert_eq!("Great!", review.body);
1317 assert_eq!("Widget", review.product.get().name);
1318
1319 let user2 = User::create().name("Bob").exec(&mut db).await?;
1321
1322 let user2 = User::filter_by_id(user2.id)
1323 .include(User::fields().review().product())
1324 .get(&mut db)
1325 .await?;
1326
1327 assert!(user2.review.get().is_none());
1328
1329 Ok(())
1330}
1331
1332#[driver_test]
1335pub async fn nested_belongs_to_required_then_has_many(test: &mut Test) -> Result<()> {
1336 #[derive(Debug, toasty::Model)]
1337 #[allow(dead_code)]
1338 struct Post {
1339 #[key]
1340 #[auto]
1341 id: uuid::Uuid,
1342
1343 title: String,
1344
1345 #[has_many]
1346 tags: toasty::Deferred<Vec<Tag>>,
1347 }
1348
1349 #[derive(Debug, toasty::Model)]
1350 #[allow(dead_code)]
1351 struct Tag {
1352 #[key]
1353 #[auto]
1354 id: uuid::Uuid,
1355
1356 label: String,
1357
1358 #[index]
1359 post_id: uuid::Uuid,
1360
1361 #[belongs_to(key = post_id, references = id)]
1362 post: toasty::Deferred<Post>,
1363 }
1364
1365 #[derive(Debug, toasty::Model)]
1366 #[allow(dead_code)]
1367 struct Comment {
1368 #[key]
1369 #[auto]
1370 id: uuid::Uuid,
1371
1372 body: String,
1373
1374 #[index]
1375 post_id: uuid::Uuid,
1376
1377 #[belongs_to(key = post_id, references = id)]
1378 post: toasty::Deferred<Post>,
1379 }
1380
1381 let mut db = test.setup_db(models!(Post, Tag, Comment)).await;
1382
1383 let post = Post::create()
1384 .title("Hello")
1385 .tags([Tag::create().label("rust")])
1386 .tags([Tag::create().label("orm")])
1387 .exec(&mut db)
1388 .await?;
1389
1390 let comment = Comment::create()
1391 .body("Nice post")
1392 .post(&post)
1393 .exec(&mut db)
1394 .await?;
1395
1396 let comment = Comment::filter_by_id(comment.id)
1398 .include(Comment::fields().post().tags())
1399 .get(&mut db)
1400 .await?;
1401
1402 assert_eq!("Hello", comment.post.get().title);
1403 let mut labels: Vec<&str> = comment
1404 .post
1405 .get()
1406 .tags
1407 .get()
1408 .iter()
1409 .map(|t| t.label.as_str())
1410 .collect();
1411 labels.sort();
1412 assert_eq!(labels, vec!["orm", "rust"]);
1413
1414 Ok(())
1415}
1416
1417#[driver_test]
1420pub async fn nested_belongs_to_required_then_has_one_optional(test: &mut Test) -> Result<()> {
1421 #[derive(Debug, toasty::Model)]
1422 #[allow(dead_code)]
1423 struct User {
1424 #[key]
1425 #[auto]
1426 id: uuid::Uuid,
1427
1428 name: String,
1429
1430 #[has_one]
1431 profile: toasty::Deferred<Option<Profile>>,
1432
1433 #[has_many]
1434 todos: toasty::Deferred<Vec<Todo>>,
1435 }
1436
1437 #[derive(Debug, toasty::Model)]
1438 #[allow(dead_code)]
1439 struct Profile {
1440 #[key]
1441 #[auto]
1442 id: uuid::Uuid,
1443
1444 bio: String,
1445
1446 #[unique]
1447 user_id: Option<uuid::Uuid>,
1448
1449 #[belongs_to(key = user_id, references = id)]
1450 user: toasty::Deferred<Option<User>>,
1451 }
1452
1453 #[derive(Debug, toasty::Model)]
1454 #[allow(dead_code)]
1455 struct Todo {
1456 #[key]
1457 #[auto]
1458 id: uuid::Uuid,
1459
1460 title: String,
1461
1462 #[index]
1463 user_id: uuid::Uuid,
1464
1465 #[belongs_to(key = user_id, references = id)]
1466 user: toasty::Deferred<User>,
1467 }
1468
1469 let mut db = test.setup_db(models!(User, Profile, Todo)).await;
1470
1471 let user = User::create()
1473 .name("Alice")
1474 .profile(Profile::create().bio("developer"))
1475 .todos([Todo::create().title("Task 1")])
1476 .exec(&mut db)
1477 .await?;
1478
1479 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1480
1481 let todo = Todo::filter_by_id(todo_id)
1482 .include(Todo::fields().user().profile())
1483 .get(&mut db)
1484 .await?;
1485
1486 assert_eq!("Alice", todo.user.get().name);
1487 let profile = todo.user.get().profile.get().as_ref().unwrap();
1488 assert_eq!("developer", profile.bio);
1489
1490 let user2 = User::create()
1492 .name("Bob")
1493 .todos([Todo::create().title("Task 2")])
1494 .exec(&mut db)
1495 .await?;
1496
1497 let todo2_id = Todo::get_by_user_id(&mut db, user2.id).await?.id;
1498
1499 let todo2 = Todo::filter_by_id(todo2_id)
1500 .include(Todo::fields().user().profile())
1501 .get(&mut db)
1502 .await?;
1503
1504 assert_eq!("Bob", todo2.user.get().name);
1505 assert!(todo2.user.get().profile.get().is_none());
1506
1507 Ok(())
1508}
1509
1510#[driver_test(scenario(crate::scenarios::user_todo_step))]
1513pub async fn nested_belongs_to_required_then_belongs_to_required(test: &mut Test) -> Result<()> {
1514 let mut db = setup(test).await;
1515
1516 let user = User::create()
1517 .name("Alice")
1518 .todos([Todo::create()
1519 .title("T1")
1520 .steps([Step::create().description("S1")])])
1521 .exec(&mut db)
1522 .await?;
1523
1524 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1525 let step_id = Step::get_by_todo_id(&mut db, todo_id).await?.id;
1526
1527 let step = Step::filter_by_id(step_id)
1529 .include(Step::fields().todo().user())
1530 .get(&mut db)
1531 .await?;
1532
1533 assert_eq!("T1", step.todo.get().title);
1534 assert_eq!("Alice", step.todo.get().user.get().name);
1535
1536 Ok(())
1537}
1538
1539#[driver_test]
1542pub async fn nested_belongs_to_optional_then_has_many(test: &mut Test) -> Result<()> {
1543 #[derive(Debug, toasty::Model)]
1544 #[allow(dead_code)]
1545 struct Project {
1546 #[key]
1547 #[auto]
1548 id: uuid::Uuid,
1549
1550 name: String,
1551
1552 #[has_many]
1553 members: toasty::Deferred<Vec<Member>>,
1554 }
1555
1556 #[derive(Debug, toasty::Model)]
1557 #[allow(dead_code)]
1558 struct Member {
1559 #[key]
1560 #[auto]
1561 id: uuid::Uuid,
1562
1563 name: String,
1564
1565 #[index]
1566 project_id: uuid::Uuid,
1567
1568 #[belongs_to(key = project_id, references = id)]
1569 project: toasty::Deferred<Project>,
1570 }
1571
1572 #[derive(Debug, toasty::Model)]
1573 #[allow(dead_code)]
1574 struct Task {
1575 #[key]
1576 #[auto]
1577 id: uuid::Uuid,
1578
1579 title: String,
1580
1581 #[index]
1582 project_id: Option<uuid::Uuid>,
1583
1584 #[belongs_to(key = project_id, references = id)]
1585 project: toasty::Deferred<Option<Project>>,
1586 }
1587
1588 let mut db = test.setup_db(models!(Project, Member, Task)).await;
1589
1590 let project = Project::create()
1591 .name("Proj1")
1592 .members([Member::create().name("Alice")])
1593 .members([Member::create().name("Bob")])
1594 .exec(&mut db)
1595 .await?;
1596
1597 let task = Task::create()
1599 .title("Linked")
1600 .project(&project)
1601 .exec(&mut db)
1602 .await?;
1603
1604 let task = Task::filter_by_id(task.id)
1605 .include(Task::fields().project().members())
1606 .get(&mut db)
1607 .await?;
1608
1609 let proj = task.project.get().as_ref().unwrap();
1610 assert_eq!("Proj1", proj.name);
1611 let mut names: Vec<&str> = proj.members.get().iter().map(|m| m.name.as_str()).collect();
1612 names.sort();
1613 assert_eq!(names, vec!["Alice", "Bob"]);
1614
1615 let orphan = Task::create().title("Orphan").exec(&mut db).await?;
1617
1618 let orphan = Task::filter_by_id(orphan.id)
1619 .include(Task::fields().project().members())
1620 .get(&mut db)
1621 .await?;
1622
1623 assert!(orphan.project.get().is_none());
1624
1625 Ok(())
1626}
1627
1628#[driver_test]
1631pub async fn nested_belongs_to_optional_then_belongs_to_optional(test: &mut Test) -> Result<()> {
1632 #[derive(Debug, toasty::Model)]
1633 #[allow(dead_code)]
1634 struct Category {
1635 #[key]
1636 #[auto]
1637 id: uuid::Uuid,
1638
1639 name: String,
1640 }
1641
1642 #[derive(Debug, toasty::Model)]
1643 #[allow(dead_code)]
1644 struct Post {
1645 #[key]
1646 #[auto]
1647 id: uuid::Uuid,
1648
1649 title: String,
1650
1651 #[index]
1652 category_id: Option<uuid::Uuid>,
1653
1654 #[belongs_to(key = category_id, references = id)]
1655 category: toasty::Deferred<Option<Category>>,
1656 }
1657
1658 #[derive(Debug, toasty::Model)]
1659 #[allow(dead_code)]
1660 struct Comment {
1661 #[key]
1662 #[auto]
1663 id: uuid::Uuid,
1664
1665 body: String,
1666
1667 #[index]
1668 post_id: Option<uuid::Uuid>,
1669
1670 #[belongs_to(key = post_id, references = id)]
1671 post: toasty::Deferred<Option<Post>>,
1672 }
1673
1674 let mut db = test.setup_db(models!(Category, Post, Comment)).await;
1675
1676 let cat = Category::create().name("Tech").exec(&mut db).await?;
1677 let post = Post::create()
1678 .title("Hello")
1679 .category(&cat)
1680 .exec(&mut db)
1681 .await?;
1682
1683 let c1 = Comment::create()
1685 .body("Nice")
1686 .post(&post)
1687 .exec(&mut db)
1688 .await?;
1689
1690 let c1 = Comment::filter_by_id(c1.id)
1691 .include(Comment::fields().post().category())
1692 .get(&mut db)
1693 .await?;
1694
1695 let loaded_post = c1.post.get().as_ref().unwrap();
1696 assert_eq!("Hello", loaded_post.title);
1697 let loaded_cat = loaded_post.category.get().as_ref().unwrap();
1698 assert_eq!("Tech", loaded_cat.name);
1699
1700 let post2 = Post::create().title("Uncategorized").exec(&mut db).await?;
1702 let c2 = Comment::create()
1703 .body("Hmm")
1704 .post(&post2)
1705 .exec(&mut db)
1706 .await?;
1707
1708 let c2 = Comment::filter_by_id(c2.id)
1709 .include(Comment::fields().post().category())
1710 .get(&mut db)
1711 .await?;
1712
1713 let loaded_post2 = c2.post.get().as_ref().unwrap();
1714 assert_eq!("Uncategorized", loaded_post2.title);
1715 assert!(loaded_post2.category.get().is_none());
1716
1717 let c3 = Comment::create().body("Orphan").exec(&mut db).await?;
1719
1720 let c3 = Comment::filter_by_id(c3.id)
1721 .include(Comment::fields().post().category())
1722 .get(&mut db)
1723 .await?;
1724
1725 assert!(c3.post.get().is_none());
1726
1727 Ok(())
1728}
1729
1730#[driver_test]
1733pub async fn nested_belongs_to_required_then_has_one_required(test: &mut Test) -> Result<()> {
1734 #[derive(Debug, toasty::Model)]
1735 #[allow(dead_code)]
1736 struct User {
1737 #[key]
1738 #[auto]
1739 id: uuid::Uuid,
1740
1741 name: String,
1742
1743 #[has_one]
1744 config: toasty::Deferred<Config>,
1745
1746 #[has_many]
1747 todos: toasty::Deferred<Vec<Todo>>,
1748 }
1749
1750 #[derive(Debug, toasty::Model)]
1751 #[allow(dead_code)]
1752 struct Config {
1753 #[key]
1754 #[auto]
1755 id: uuid::Uuid,
1756
1757 theme: String,
1758
1759 #[unique]
1760 user_id: Option<uuid::Uuid>,
1761
1762 #[belongs_to(key = user_id, references = id)]
1763 user: toasty::Deferred<Option<User>>,
1764 }
1765
1766 #[derive(Debug, toasty::Model)]
1767 #[allow(dead_code)]
1768 struct Todo {
1769 #[key]
1770 #[auto]
1771 id: uuid::Uuid,
1772
1773 title: String,
1774
1775 #[index]
1776 user_id: uuid::Uuid,
1777
1778 #[belongs_to(key = user_id, references = id)]
1779 user: toasty::Deferred<User>,
1780 }
1781
1782 let mut db = test.setup_db(models!(User, Config, Todo)).await;
1783
1784 let user = User::create()
1785 .name("Alice")
1786 .config(Config::create().theme("dark"))
1787 .todos([Todo::create().title("Task")])
1788 .exec(&mut db)
1789 .await?;
1790
1791 let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1792
1793 let todo = Todo::filter_by_id(todo_id)
1794 .include(Todo::fields().user().config())
1795 .get(&mut db)
1796 .await?;
1797
1798 assert_eq!("Alice", todo.user.get().name);
1799 assert_eq!("dark", todo.user.get().config.get().theme);
1800
1801 Ok(())
1802}
1803
1804#[driver_test(scenario(crate::scenarios::user_todo_step))]
1808pub async fn nested_has_many_then_has_many_with_empty_leaves(test: &mut Test) {
1809 let mut db = setup(test).await;
1810
1811 let user = User::create()
1812 .name("Alice")
1813 .todos([Todo::create()
1814 .title("With Steps")
1815 .steps([Step::create().description("S1")])])
1816 .todos([Todo::create().title("No Steps")]) .exec(&mut db)
1818 .await
1819 .unwrap();
1820
1821 let user = User::filter_by_id(user.id)
1822 .include(User::fields().todos().steps())
1823 .get(&mut db)
1824 .await
1825 .unwrap();
1826
1827 let todos = user.todos.get();
1828 assert_eq!(2, todos.len());
1829
1830 let mut total_steps = 0;
1831 for todo in todos {
1832 let steps = todo.steps.get();
1833 if todo.title == "With Steps" {
1834 assert_eq!(1, steps.len());
1835 assert_eq!("S1", steps[0].description);
1836 } else {
1837 assert_eq!(0, steps.len());
1838 }
1839 total_steps += steps.len();
1840 }
1841 assert_eq!(1, total_steps);
1842}
1843
1844#[driver_test(requires(sql), scenario(crate::scenarios::has_many_multi_relation))]
1849pub async fn sibling_nested_includes_on_shared_prefix(test: &mut Test) -> Result<()> {
1850 let mut db = setup(test).await;
1851
1852 let category = toasty::create!(Category { name: "Food" })
1853 .exec(&mut db)
1854 .await?;
1855 let user = toasty::create!(User {
1856 name: "Alice",
1857 todos: [
1858 { title: "T1", category: &category },
1859 { title: "T2", category: &category },
1860 ],
1861 })
1862 .exec(&mut db)
1863 .await?;
1864
1865 let loaded = User::filter_by_id(user.id)
1868 .include(User::fields().todos().user())
1869 .include(User::fields().todos().category())
1870 .get(&mut db)
1871 .await?;
1872
1873 let todos = loaded.todos.get();
1874 assert_eq!(2, todos.len());
1875 for todo in todos {
1876 assert_eq!("Alice", todo.user.get().name);
1877 assert_eq!("Food", todo.category.get().name);
1878 }
1879
1880 Ok(())
1881}
1882
1883#[driver_test(requires(sql), scenario(crate::scenarios::has_many_multi_relation))]
1887pub async fn bare_and_nested_includes_on_shared_prefix(test: &mut Test) -> Result<()> {
1888 let mut db = setup(test).await;
1889
1890 let category = toasty::create!(Category { name: "Food" })
1891 .exec(&mut db)
1892 .await?;
1893 let user = toasty::create!(User {
1894 name: "Alice",
1895 todos: [{ title: "T1", category: &category }],
1896 })
1897 .exec(&mut db)
1898 .await?;
1899
1900 let loaded = User::filter_by_id(user.id)
1901 .include(User::fields().todos()) .include(User::fields().todos().user()) .include(User::fields().todos().category()) .get(&mut db)
1905 .await?;
1906
1907 let todos = loaded.todos.get();
1908 assert_eq!(1, todos.len());
1909 assert_eq!("Alice", todos[0].user.get().name);
1910 assert_eq!("Food", todos[0].category.get().name);
1911
1912 Ok(())
1913}
1914
1915#[driver_test]
1920pub async fn sibling_nested_includes_on_shared_prefix_non_sql(test: &mut Test) -> Result<()> {
1921 #[derive(Debug, toasty::Model)]
1922 #[allow(dead_code)]
1923 struct Category {
1924 #[key]
1925 #[auto]
1926 id: uuid::Uuid,
1927
1928 name: String,
1929
1930 #[has_many]
1931 items: toasty::Deferred<Vec<Item>>,
1932 }
1933
1934 #[derive(Debug, toasty::Model)]
1935 #[allow(dead_code)]
1936 struct Brand {
1937 #[key]
1938 #[auto]
1939 id: uuid::Uuid,
1940
1941 name: String,
1942 }
1943
1944 #[derive(Debug, toasty::Model)]
1945 #[allow(dead_code)]
1946 struct Supplier {
1947 #[key]
1948 #[auto]
1949 id: uuid::Uuid,
1950
1951 name: String,
1952 }
1953
1954 #[derive(Debug, toasty::Model)]
1955 #[allow(dead_code)]
1956 struct Item {
1957 #[key]
1958 #[auto]
1959 id: uuid::Uuid,
1960
1961 title: String,
1962
1963 #[index]
1964 category_id: uuid::Uuid,
1965
1966 #[belongs_to(key = category_id, references = id)]
1967 category: toasty::Deferred<Category>,
1968
1969 #[index]
1970 brand_id: uuid::Uuid,
1971
1972 #[belongs_to(key = brand_id, references = id)]
1973 brand: toasty::Deferred<Brand>,
1974
1975 #[index]
1976 supplier_id: uuid::Uuid,
1977
1978 #[belongs_to(key = supplier_id, references = id)]
1979 supplier: toasty::Deferred<Supplier>,
1980 }
1981
1982 let mut db = test
1983 .setup_db(models!(Category, Brand, Supplier, Item))
1984 .await;
1985
1986 let brand_a = toasty::create!(Brand { name: "BrandA" })
1987 .exec(&mut db)
1988 .await?;
1989 let brand_b = toasty::create!(Brand { name: "BrandB" })
1990 .exec(&mut db)
1991 .await?;
1992 let sup_a = toasty::create!(Supplier { name: "SupA" })
1993 .exec(&mut db)
1994 .await?;
1995 let sup_b = toasty::create!(Supplier { name: "SupB" })
1996 .exec(&mut db)
1997 .await?;
1998
1999 let cat = toasty::create!(Category {
2000 name: "Electronics",
2001 items: [
2002 { title: "Phone", brand: &brand_a, supplier: &sup_a },
2003 { title: "Laptop", brand: &brand_b, supplier: &sup_b },
2004 ],
2005 })
2006 .exec(&mut db)
2007 .await?;
2008
2009 let loaded = Category::filter_by_id(cat.id)
2012 .include(Category::fields().items().brand())
2013 .include(Category::fields().items().supplier())
2014 .get(&mut db)
2015 .await?;
2016
2017 let items = loaded.items.get();
2018 assert_eq!(2, items.len());
2019 let mut pairs: Vec<(&str, &str)> = items
2020 .iter()
2021 .map(|i| (i.brand.get().name.as_str(), i.supplier.get().name.as_str()))
2022 .collect();
2023 pairs.sort();
2024 assert_eq!(pairs, vec![("BrandA", "SupA"), ("BrandB", "SupB")]);
2025
2026 Ok(())
2027}