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