Skip to main content

toasty_driver_integration_suite/tests/
relation_has_many_via.rs

1//! Multi-step (`via`) has_many relations: a `has_many` reached by following a
2//! path of existing relations rather than a single foreign key.
3//!
4//! The shape under test is `User` → `Comment` → `Article`: a user has many
5//! comments, each comment belongs to an article, so a user has many
6//! `commented_articles` via `comments.article`.
7
8use crate::prelude::*;
9
10/// Querying a `via` relation returns the distinct targets reachable through
11/// the path — a target is listed once however many intermediates reach it.
12#[driver_test(
13    id(ID),
14    requires(sql),
15    scenario(crate::scenarios::user_comment_article)
16)]
17pub async fn query_returns_distinct_targets(test: &mut Test) -> Result<()> {
18    let mut db = setup(test).await;
19
20    let users = toasty::create!(User::[
21        { name: "Alice" },
22        { name: "Bob" },
23    ])
24    .exec(&mut db)
25    .await?;
26    let (alice, bob) = (&users[0], &users[1]);
27
28    let articles = toasty::create!(Article::[
29        { title: "Rust" },
30        { title: "Toasty" },
31        { title: "SQL" },
32    ])
33    .exec(&mut db)
34    .await?;
35    let (rust, toasty_article, sql) = (&articles[0], &articles[1], &articles[2]);
36
37    // Alice comments on Rust twice and Toasty once; Bob comments on SQL.
38    toasty::create!(Comment::[
39        { body: "a1", user: alice, article: rust },
40        { body: "a2", user: alice, article: rust },
41        { body: "a3", user: alice, article: toasty_article },
42        { body: "b1", user: bob, article: sql },
43    ])
44    .exec(&mut db)
45    .await?;
46
47    // Alice has commented on Rust and Toasty. Rust appears once even though
48    // she commented on it twice — `via` yields distinct targets.
49    let commented = alice.commented_articles().exec(&mut db).await?;
50    assert_eq_unordered!(commented.iter().map(|a| &a.title[..]), ["Rust", "Toasty"]);
51
52    // Bob has commented only on SQL.
53    let commented = bob.commented_articles().exec(&mut db).await?;
54    assert_eq_unordered!(commented.iter().map(|a| &a.title[..]), ["SQL"]);
55
56    Ok(())
57}
58
59/// A user with no comments reaches no articles — an empty result, no error.
60#[driver_test(
61    id(ID),
62    requires(sql),
63    scenario(crate::scenarios::user_comment_article)
64)]
65pub async fn query_with_no_intermediates_is_empty(test: &mut Test) -> Result<()> {
66    let mut db = setup(test).await;
67
68    let user = toasty::create!(User { name: "Alice" })
69        .exec(&mut db)
70        .await?;
71    toasty::create!(Article { title: "Rust" })
72        .exec(&mut db)
73        .await?;
74
75    let commented = user.commented_articles().exec(&mut db).await?;
76    assert!(commented.is_empty());
77
78    Ok(())
79}
80
81/// A `via` relation query can be further filtered, like any other relation
82/// query.
83#[driver_test(
84    id(ID),
85    requires(sql),
86    scenario(crate::scenarios::user_comment_article)
87)]
88pub async fn via_relation_query_can_be_filtered(test: &mut Test) -> Result<()> {
89    let mut db = setup(test).await;
90
91    let alice = toasty::create!(User { name: "Alice" })
92        .exec(&mut db)
93        .await?;
94
95    let articles = toasty::create!(Article::[
96        { title: "Rust" },
97        { title: "Toasty" },
98        { title: "SQL" },
99    ])
100    .exec(&mut db)
101    .await?;
102    let (rust, toasty_article, sql) = (&articles[0], &articles[1], &articles[2]);
103
104    toasty::create!(Comment::[
105        { body: "a1", user: &alice, article: rust },
106        { body: "a2", user: &alice, article: toasty_article },
107        { body: "a3", user: &alice, article: sql },
108    ])
109    .exec(&mut db)
110    .await?;
111
112    let filtered: Vec<_> = alice
113        .commented_articles()
114        .filter(Article::fields().title().eq("Toasty"))
115        .exec(&mut db)
116        .await?;
117    assert_eq_unordered!(filtered.iter().map(|a| &a.title[..]), ["Toasty"]);
118
119    Ok(())
120}