1use crate::prelude::*;
9
10#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
14pub async fn user_todos_category(test: &mut Test) -> Result<()> {
15 let mut db = setup(test).await;
16
17 let user = toasty::create!(User { name: "Anchovy" })
18 .exec(&mut db)
19 .await?;
20 let other_user = toasty::create!(User { name: "Other" })
21 .exec(&mut db)
22 .await?;
23
24 let food = toasty::create!(Category { name: "Food" })
25 .exec(&mut db)
26 .await?;
27 let drink = toasty::create!(Category { name: "Drink" })
28 .exec(&mut db)
29 .await?;
30 let unused = toasty::create!(Category { name: "Unused" })
31 .exec(&mut db)
32 .await?;
33
34 toasty::create!(Todo::[
35 { title: "salad", user: &user, category: &food },
36 { title: "tea", user: &user, category: &drink },
37 { title: "sushi", user: &user, category: &food },
38 { title: "wine", user: &other_user, category: &unused },
39 ])
40 .exec(&mut db)
41 .await?;
42
43 let mut categories = user.todos().category().exec(&mut db).await?;
44 categories.sort_by_key(|c| c.name.clone());
45
46 let ids: Vec<_> = categories.iter().map(|c| c.id).collect();
47 assert_unique!(ids);
48 assert_eq!(categories.len(), 2);
49 assert_eq!(categories[0].id, drink.id);
50 assert_eq!(categories[1].id, food.id);
51 Ok(())
52}
53
54#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
56pub async fn chain_from_empty_source_is_empty(test: &mut Test) -> Result<()> {
57 let mut db = setup(test).await;
58
59 let user = toasty::create!(User { name: "Lonely" })
60 .exec(&mut db)
61 .await?;
62
63 let other = toasty::create!(User { name: "Busy" }).exec(&mut db).await?;
66 let food = toasty::create!(Category { name: "Food" })
67 .exec(&mut db)
68 .await?;
69 toasty::create!(Todo {
70 title: "salad",
71 user: &other,
72 category: &food
73 })
74 .exec(&mut db)
75 .await?;
76
77 let categories = user.todos().category().exec(&mut db).await?;
78 assert!(categories.is_empty());
79 Ok(())
80}
81
82#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
85pub async fn chain_dedupes_when_todos_share_category(test: &mut Test) -> Result<()> {
86 let mut db = setup(test).await;
87
88 let user = toasty::create!(User { name: "Cooky" })
89 .exec(&mut db)
90 .await?;
91 let food = toasty::create!(Category { name: "Food" })
92 .exec(&mut db)
93 .await?;
94
95 for i in 0..5 {
96 let title = format!("todo {i}");
97 toasty::create!(Todo {
98 title,
99 user: &user,
100 category: &food
101 })
102 .exec(&mut db)
103 .await?;
104 }
105
106 let categories = user.todos().category().exec(&mut db).await?;
107 assert_eq!(categories.len(), 1);
108 assert_eq!(categories[0].id, food.id);
109 Ok(())
110}
111
112#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
115pub async fn chain_scopes_per_starting_user(test: &mut Test) -> Result<()> {
116 let mut db = setup(test).await;
117
118 let alice = toasty::create!(User { name: "Alice" })
119 .exec(&mut db)
120 .await?;
121 let bob = toasty::create!(User { name: "Bob" }).exec(&mut db).await?;
122
123 let a = toasty::create!(Category { name: "A" })
124 .exec(&mut db)
125 .await?;
126 let b = toasty::create!(Category { name: "B" })
127 .exec(&mut db)
128 .await?;
129 let c = toasty::create!(Category { name: "C" })
130 .exec(&mut db)
131 .await?;
132
133 toasty::create!(Todo::[
134 { title: "a1", user: &alice, category: &a },
135 { title: "a2", user: &alice, category: &b },
136 { title: "b1", user: &bob, category: &b },
137 { title: "b2", user: &bob, category: &c },
138 ])
139 .exec(&mut db)
140 .await?;
141
142 let mut alice_cats = alice.todos().category().exec(&mut db).await?;
143 alice_cats.sort_by_key(|c| c.name.clone());
144 let alice_ids: Vec<_> = alice_cats.iter().map(|c| c.id).collect();
145 assert_eq!(alice_ids, vec![a.id, b.id]);
146
147 let mut bob_cats = bob.todos().category().exec(&mut db).await?;
148 bob_cats.sort_by_key(|c| c.name.clone());
149 let bob_ids: Vec<_> = bob_cats.iter().map(|c| c.id).collect();
150 assert_eq!(bob_ids, vec![b.id, c.id]);
151 Ok(())
152}
153
154#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
157pub async fn chain_then_filter(test: &mut Test) -> Result<()> {
158 let mut db = setup(test).await;
159
160 let user = toasty::create!(User { name: "Filty" })
161 .exec(&mut db)
162 .await?;
163 let food = toasty::create!(Category { name: "Food" })
164 .exec(&mut db)
165 .await?;
166 let drink = toasty::create!(Category { name: "Drink" })
167 .exec(&mut db)
168 .await?;
169
170 toasty::create!(Todo::[
171 { title: "salad", user: &user, category: &food },
172 { title: "tea", user: &user, category: &drink },
173 ])
174 .exec(&mut db)
175 .await?;
176
177 let only_food = user
178 .todos()
179 .category()
180 .filter(Category::fields().name().eq("Food"))
181 .exec(&mut db)
182 .await?;
183 assert_eq!(only_food.len(), 1);
184 assert_eq!(only_food[0].id, food.id);
185 Ok(())
186}
187
188#[driver_test]
191pub async fn has_many_through_has_many(test: &mut Test) -> Result<()> {
192 #[derive(Debug, toasty::Model)]
193 struct Author {
194 #[key]
195 #[auto]
196 id: uuid::Uuid,
197 name: String,
198 #[has_many]
199 posts: toasty::HasMany<Post>,
200 }
201
202 #[derive(Debug, toasty::Model)]
203 struct Post {
204 #[key]
205 #[auto]
206 id: uuid::Uuid,
207 #[index]
208 author_id: uuid::Uuid,
209 #[belongs_to(key = author_id, references = id)]
210 author: toasty::BelongsTo<Author>,
211 title: String,
212 #[has_many]
213 comments: toasty::HasMany<Comment>,
214 }
215
216 #[derive(Debug, toasty::Model)]
217 struct Comment {
218 #[key]
219 #[auto]
220 id: uuid::Uuid,
221 #[index]
222 post_id: uuid::Uuid,
223 #[belongs_to(key = post_id, references = id)]
224 post: toasty::BelongsTo<Post>,
225 body: String,
226 }
227
228 let mut db = test.setup_db(models!(Author, Post, Comment)).await;
229
230 let alice = toasty::create!(Author { name: "Alice" })
231 .exec(&mut db)
232 .await?;
233 let bob = toasty::create!(Author { name: "Bob" })
234 .exec(&mut db)
235 .await?;
236
237 let p1 = toasty::create!(Post {
238 title: "p1",
239 author: &alice
240 })
241 .exec(&mut db)
242 .await?;
243 let p2 = toasty::create!(Post {
244 title: "p2",
245 author: &alice
246 })
247 .exec(&mut db)
248 .await?;
249 let p3 = toasty::create!(Post {
250 title: "p3",
251 author: &bob
252 })
253 .exec(&mut db)
254 .await?;
255
256 toasty::create!(Comment::[
257 { body: "c1", post: &p1 },
258 { body: "c2", post: &p1 },
259 { body: "c3", post: &p2 },
260 { body: "c4", post: &p3 },
261 ])
262 .exec(&mut db)
263 .await?;
264
265 let mut alice_comments = alice.posts().comments().exec(&mut db).await?;
266 alice_comments.sort_by_key(|c| c.body.clone());
267 let bodies: Vec<_> = alice_comments.iter().map(|c| c.body.clone()).collect();
268 assert_eq!(bodies, vec!["c1", "c2", "c3"]);
269
270 let bob_comments = bob.posts().comments().exec(&mut db).await?;
271 assert_eq!(bob_comments.len(), 1);
272 assert_eq!(bob_comments[0].body, "c4");
273 Ok(())
274}
275
276#[driver_test]
280pub async fn three_step_chain(test: &mut Test) -> Result<()> {
281 #[derive(Debug, toasty::Model)]
282 struct User {
283 #[key]
284 #[auto]
285 id: uuid::Uuid,
286 name: String,
287 #[has_many]
288 projects: toasty::HasMany<Project>,
289 }
290
291 #[derive(Debug, toasty::Model)]
292 struct Project {
293 #[key]
294 #[auto]
295 id: uuid::Uuid,
296 #[index]
297 user_id: uuid::Uuid,
298 #[belongs_to(key = user_id, references = id)]
299 user: toasty::BelongsTo<User>,
300 name: String,
301 #[has_many]
302 tasks: toasty::HasMany<Task>,
303 }
304
305 #[derive(Debug, toasty::Model)]
306 struct Task {
307 #[key]
308 #[auto]
309 id: uuid::Uuid,
310 #[index]
311 project_id: uuid::Uuid,
312 #[belongs_to(key = project_id, references = id)]
313 project: toasty::BelongsTo<Project>,
314 title: String,
315 #[index]
316 tag_id: uuid::Uuid,
317 #[belongs_to(key = tag_id, references = id)]
318 tag: toasty::BelongsTo<Tag>,
319 }
320
321 #[derive(Debug, toasty::Model)]
322 struct Tag {
323 #[key]
324 #[auto]
325 id: uuid::Uuid,
326 name: String,
327 #[has_many]
328 tasks: toasty::HasMany<Task>,
329 }
330
331 let mut db = test.setup_db(models!(User, Project, Task, Tag)).await;
332
333 let user = toasty::create!(User { name: "Owner" })
334 .exec(&mut db)
335 .await?;
336 let other = toasty::create!(User { name: "Other" })
337 .exec(&mut db)
338 .await?;
339
340 let backend = toasty::create!(Project {
341 name: "Backend",
342 user: &user
343 })
344 .exec(&mut db)
345 .await?;
346 let frontend = toasty::create!(Project {
347 name: "Frontend",
348 user: &user
349 })
350 .exec(&mut db)
351 .await?;
352 let unrelated = toasty::create!(Project {
353 name: "Unrelated",
354 user: &other
355 })
356 .exec(&mut db)
357 .await?;
358
359 let bug = toasty::create!(Tag { name: "bug" }).exec(&mut db).await?;
360 let feat = toasty::create!(Tag { name: "feature" })
361 .exec(&mut db)
362 .await?;
363 let chore = toasty::create!(Tag { name: "chore" }).exec(&mut db).await?;
364
365 toasty::create!(Task::[
366 { title: "fix login", project: &backend, tag: &bug },
367 { title: "add dark mode", project: &frontend, tag: &feat },
368 { title: "rotate keys", project: &backend, tag: &chore },
369 { title: "different user", project: &unrelated, tag: &bug },
370 ])
371 .exec(&mut db)
372 .await?;
373
374 let mut tags = user.projects().tasks().tag().exec(&mut db).await?;
375 tags.sort_by_key(|t| t.name.clone());
376 let ids: Vec<_> = tags.iter().map(|t| t.id).collect();
377 assert_unique!(ids);
378 let names: Vec<_> = tags.iter().map(|t| t.name.clone()).collect();
379 assert_eq!(names, vec!["bug", "chore", "feature"]);
380 Ok(())
381}
382
383#[driver_test]
388pub async fn four_step_chain(test: &mut Test) -> Result<()> {
389 #[derive(Debug, toasty::Model)]
390 struct Org {
391 #[key]
392 #[auto]
393 id: uuid::Uuid,
394 name: String,
395 #[has_many]
396 teams: toasty::HasMany<Team>,
397 }
398
399 #[derive(Debug, toasty::Model)]
400 struct Team {
401 #[key]
402 #[auto]
403 id: uuid::Uuid,
404 #[index]
405 org_id: uuid::Uuid,
406 #[belongs_to(key = org_id, references = id)]
407 org: toasty::BelongsTo<Org>,
408 name: String,
409 #[has_many]
410 projects: toasty::HasMany<Project>,
411 }
412
413 #[derive(Debug, toasty::Model)]
414 struct Project {
415 #[key]
416 #[auto]
417 id: uuid::Uuid,
418 #[index]
419 team_id: uuid::Uuid,
420 #[belongs_to(key = team_id, references = id)]
421 team: toasty::BelongsTo<Team>,
422 name: String,
423 #[has_many]
424 issues: toasty::HasMany<Issue>,
425 }
426
427 #[derive(Debug, toasty::Model)]
428 struct Issue {
429 #[key]
430 #[auto]
431 id: uuid::Uuid,
432 #[index]
433 project_id: uuid::Uuid,
434 #[belongs_to(key = project_id, references = id)]
435 project: toasty::BelongsTo<Project>,
436 title: String,
437 #[index]
438 tag_id: uuid::Uuid,
439 #[belongs_to(key = tag_id, references = id)]
440 tag: toasty::BelongsTo<Tag>,
441 }
442
443 #[derive(Debug, toasty::Model)]
444 struct Tag {
445 #[key]
446 #[auto]
447 id: uuid::Uuid,
448 name: String,
449 #[has_many]
450 issues: toasty::HasMany<Issue>,
451 }
452
453 let mut db = test.setup_db(models!(Org, Team, Project, Issue, Tag)).await;
454
455 let mine = toasty::create!(Org { name: "Mine" }).exec(&mut db).await?;
456 let theirs = toasty::create!(Org { name: "Theirs" })
457 .exec(&mut db)
458 .await?;
459
460 let core = toasty::create!(Team {
461 name: "core",
462 org: &mine
463 })
464 .exec(&mut db)
465 .await?;
466 let ops = toasty::create!(Team {
467 name: "ops",
468 org: &mine
469 })
470 .exec(&mut db)
471 .await?;
472 let outside = toasty::create!(Team {
473 name: "outside",
474 org: &theirs
475 })
476 .exec(&mut db)
477 .await?;
478
479 let backend = toasty::create!(Project {
480 name: "backend",
481 team: &core
482 })
483 .exec(&mut db)
484 .await?;
485 let frontend = toasty::create!(Project {
486 name: "frontend",
487 team: &core
488 })
489 .exec(&mut db)
490 .await?;
491 let infra = toasty::create!(Project {
492 name: "infra",
493 team: &ops
494 })
495 .exec(&mut db)
496 .await?;
497 let unrelated = toasty::create!(Project {
498 name: "unrelated",
499 team: &outside
500 })
501 .exec(&mut db)
502 .await?;
503
504 let bug = toasty::create!(Tag { name: "bug" }).exec(&mut db).await?;
505 let feat = toasty::create!(Tag { name: "feature" })
506 .exec(&mut db)
507 .await?;
508 let chore = toasty::create!(Tag { name: "chore" }).exec(&mut db).await?;
509 let unused = toasty::create!(Tag { name: "unused" })
510 .exec(&mut db)
511 .await?;
512
513 toasty::create!(Issue::[
514 { title: "fix login", project: &backend, tag: &bug },
515 { title: "dark mode", project: &frontend, tag: &feat },
516 { title: "rotate keys", project: &infra, tag: &chore },
517 { title: "duplicate", project: &backend, tag: &bug },
518 { title: "their issue", project: &unrelated, tag: &unused },
519 ])
520 .exec(&mut db)
521 .await?;
522
523 let mut tags = mine.teams().projects().issues().tag().exec(&mut db).await?;
524 tags.sort_by_key(|t| t.name.clone());
525
526 let ids: Vec<_> = tags.iter().map(|t| t.id).collect();
527 assert_unique!(ids);
528 let names: Vec<_> = tags.iter().map(|t| t.name.clone()).collect();
529 assert_eq!(names, vec!["bug", "chore", "feature"]);
530 Ok(())
531}
532
533#[driver_test]
536pub async fn chain_skips_null_belongs_to(test: &mut Test) -> Result<()> {
537 #[derive(Debug, toasty::Model)]
538 struct User {
539 #[key]
540 #[auto]
541 id: uuid::Uuid,
542 name: String,
543 #[has_many]
544 todos: toasty::HasMany<Todo>,
545 }
546
547 #[derive(Debug, toasty::Model)]
548 struct Todo {
549 #[key]
550 #[auto]
551 id: uuid::Uuid,
552 #[index]
553 user_id: uuid::Uuid,
554 #[belongs_to(key = user_id, references = id)]
555 user: toasty::BelongsTo<User>,
556 title: String,
557 #[index]
558 category_id: Option<uuid::Uuid>,
559 #[belongs_to(key = category_id, references = id)]
560 category: toasty::BelongsTo<Option<Category>>,
561 }
562
563 #[derive(Debug, toasty::Model)]
564 struct Category {
565 #[key]
566 #[auto]
567 id: uuid::Uuid,
568 name: String,
569 }
570
571 let mut db = test.setup_db(models!(User, Todo, Category)).await;
572
573 let user = toasty::create!(User { name: "Tester" })
574 .exec(&mut db)
575 .await?;
576 let cat = toasty::create!(Category { name: "Only" })
577 .exec(&mut db)
578 .await?;
579
580 toasty::create!(Todo::[
581 { title: "with cat", user: &user, category: &cat },
582 { title: "no cat 1", user: &user },
583 { title: "no cat 2", user: &user },
584 ])
585 .exec(&mut db)
586 .await?;
587
588 let cats = user.todos().category().exec(&mut db).await?;
589 assert_eq!(cats.len(), 1);
590 assert_eq!(cats[0].id, cat.id);
591 Ok(())
592}