Skip to main content

toasty_driver_integration_suite/tests/
relation_many_to_many.rs

1//! Many-to-many relations use a join model with a `has_many` relation on each
2//! endpoint and a `has_many(via = ...)` relation for direct traversal.
3
4use crate::prelude::*;
5
6/// Both endpoints can traverse the same join rows. The derived query retains
7/// normal query operations such as ordering and returns an empty list when no
8/// join rows exist.
9#[driver_test(requires(sql), scenario(crate::scenarios::user_group_membership))]
10pub async fn query_from_both_sides(test: &mut Test) -> Result<()> {
11    let mut db = setup(test).await;
12    let fixture = seed(&mut db).await?;
13
14    let groups = fixture
15        .alice
16        .groups()
17        .order_by(Group::fields().name().asc())
18        .exec(&mut db)
19        .await?;
20    assert_eq!(
21        groups
22            .iter()
23            .map(|group| &group.name[..])
24            .collect::<Vec<_>>(),
25        ["Databases", "Rust"]
26    );
27
28    let users = fixture
29        .rust
30        .users()
31        .order_by(User::fields().name().asc())
32        .exec(&mut db)
33        .await?;
34    assert_eq!(
35        users.iter().map(|user| &user.name[..]).collect::<Vec<_>>(),
36        ["Alice", "Bob"]
37    );
38
39    assert!(fixture.carol.groups().exec(&mut db).await?.is_empty());
40    assert!(fixture.empty.users().exec(&mut db).await?.is_empty());
41
42    Ok(())
43}
44
45/// Parent queries can use `.any()` on the join relation. The predicate can
46/// traverse to the opposite endpoint and can also inspect fields stored on the
47/// join model.
48#[driver_test(requires(sql), scenario(crate::scenarios::user_group_membership))]
49pub async fn filter_through_join_model(test: &mut Test) -> Result<()> {
50    let mut db = setup(test).await;
51    seed(&mut db).await?;
52
53    let users: Vec<User> = User::filter(
54        User::fields()
55            .memberships()
56            .any(Membership::fields().group().name().eq("Rust")),
57    )
58    .exec(&mut db)
59    .await?;
60    assert_eq_unordered!(users.iter().map(|user| &user.name[..]), ["Alice", "Bob"]);
61
62    let groups: Vec<Group> = Group::filter(
63        Group::fields()
64            .memberships()
65            .any(Membership::fields().user().name().eq("Alice")),
66    )
67    .exec(&mut db)
68    .await?;
69    assert_eq_unordered!(
70        groups.iter().map(|group| &group.name[..]),
71        ["Rust", "Databases"]
72    );
73
74    let owners: Vec<User> = User::filter(
75        User::fields()
76            .memberships()
77            .any(Membership::fields().role().eq("owner")),
78    )
79    .exec(&mut db)
80    .await?;
81    assert_eq!(owners.len(), 1);
82    assert_eq!(owners[0].name, "Alice");
83
84    Ok(())
85}
86
87/// Parent queries can apply `.any()` directly to either derived relation,
88/// without spelling the join model in the predicate.
89#[driver_test(requires(sql), scenario(crate::scenarios::user_group_membership))]
90pub async fn filter_through_via_from_both_sides(test: &mut Test) -> Result<()> {
91    let mut db = setup(test).await;
92    seed(&mut db).await?;
93
94    let users: Vec<User> = User::filter(
95        User::fields()
96            .groups()
97            .any(Group::fields().name().eq("Rust")),
98    )
99    .exec(&mut db)
100    .await?;
101    assert_eq_unordered!(users.iter().map(|user| &user.name[..]), ["Alice", "Bob"]);
102
103    let groups: Vec<Group> = Group::filter(
104        Group::fields()
105            .users()
106            .any(User::fields().name().eq("Alice")),
107    )
108    .exec(&mut db)
109    .await?;
110    assert_eq_unordered!(
111        groups.iter().map(|group| &group.name[..]),
112        ["Rust", "Databases"]
113    );
114
115    Ok(())
116}
117
118/// Preloading works in both directions and keeps results grouped under the
119/// correct endpoint, including endpoints with no join rows.
120#[driver_test(requires(sql), scenario(crate::scenarios::user_group_membership))]
121pub async fn include_from_both_sides(test: &mut Test) -> Result<()> {
122    let mut db = setup(test).await;
123    seed(&mut db).await?;
124
125    let users: Vec<User> = User::all()
126        .include(User::fields().groups())
127        .exec(&mut db)
128        .await?;
129    for user in &users {
130        let groups = user.groups.get().iter().map(|group| &group.name[..]);
131        match &user.name[..] {
132            "Alice" => {
133                assert_eq_unordered!(groups, ["Rust", "Databases"]);
134            }
135            "Bob" => {
136                assert_eq_unordered!(groups, ["Rust"]);
137            }
138            "Carol" => {
139                assert_eq!(groups.count(), 0);
140            }
141            name => panic!("unexpected user {name}"),
142        }
143    }
144
145    let groups: Vec<Group> = Group::all()
146        .include(Group::fields().users())
147        .exec(&mut db)
148        .await?;
149    for group in &groups {
150        let users = group.users.get().iter().map(|user| &user.name[..]);
151        match &group.name[..] {
152            "Rust" => {
153                assert_eq_unordered!(users, ["Alice", "Bob"]);
154            }
155            "Databases" => {
156                assert_eq_unordered!(users, ["Alice"]);
157            }
158            "Empty" => {
159                assert_eq!(users.count(), 0);
160            }
161            name => panic!("unexpected group {name}"),
162        }
163    }
164
165    Ok(())
166}
167
168/// Links are created, updated, and removed by mutating the join model. This
169/// preserves relation data such as `role` and leaves both endpoint records
170/// intact when the link is removed.
171#[driver_test(requires(sql), scenario(crate::scenarios::user_group_membership))]
172pub async fn mutate_link_through_join_model(test: &mut Test) -> Result<()> {
173    let mut db = setup(test).await;
174
175    let user = toasty::create!(User { name: "Alice" })
176        .exec(&mut db)
177        .await?;
178    let group = toasty::create!(Group { name: "Rust" })
179        .exec(&mut db)
180        .await?;
181
182    let mut membership = toasty::create!(Membership {
183        user: &user,
184        group: &group,
185        role: "member"
186    })
187    .exec(&mut db)
188    .await?;
189
190    assert_eq!(user.groups().get(&mut db).await?.id, group.id);
191    assert_eq!(group.users().get(&mut db).await?.id, user.id);
192
193    membership.update().role("owner").exec(&mut db).await?;
194    assert_eq!(membership.role, "owner");
195
196    membership.delete().exec(&mut db).await?;
197    assert!(user.groups().exec(&mut db).await?.is_empty());
198    assert!(group.users().exec(&mut db).await?.is_empty());
199
200    assert_eq!(User::get_by_id(&mut db, user.id).await?.name, "Alice");
201    assert_eq!(Group::get_by_id(&mut db, group.id).await?.name, "Rust");
202
203    Ok(())
204}
205
206/// A composite primary key on the join model prevents duplicate links while
207/// allowing each endpoint to participate in other links.
208#[driver_test(requires(sql), scenario(crate::scenarios::user_group_membership))]
209pub async fn composite_join_key_prevents_duplicate_links(test: &mut Test) -> Result<()> {
210    let mut db = setup(test).await;
211
212    let users = toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }])
213        .exec(&mut db)
214        .await?;
215    let groups = toasty::create!(Group::[{ name: "Rust" }, { name: "Databases" }])
216        .exec(&mut db)
217        .await?;
218
219    toasty::create!(Membership {
220        user: &users[0],
221        group: &groups[0],
222        role: "member"
223    })
224    .exec(&mut db)
225    .await?;
226
227    assert_err!(
228        toasty::create!(Membership {
229            user: &users[0],
230            group: &groups[0],
231            role: "owner"
232        })
233        .exec(&mut db)
234        .await
235    );
236
237    toasty::create!(Membership::[
238        { user: &users[0], group: &groups[1], role: "member" },
239        { user: &users[1], group: &groups[0], role: "member" },
240    ])
241    .exec(&mut db)
242    .await?;
243
244    Ok(())
245}
246
247/// A self-referential many-to-many relation uses pair hints to distinguish
248/// the two foreign keys on the join model. Each direction then exposes its
249/// own `has_many(via = ...)` traversal.
250#[driver_test(requires(sql))]
251pub async fn self_referential_followers_and_following(test: &mut Test) -> Result<()> {
252    #[derive(Debug, toasty::Model)]
253    struct User {
254        #[key]
255        #[auto]
256        id: uuid::Uuid,
257
258        name: String,
259
260        #[has_many(pair = follower)]
261        outgoing_follows: toasty::Deferred<Vec<Follow>>,
262
263        #[has_many(pair = followed)]
264        incoming_follows: toasty::Deferred<Vec<Follow>>,
265
266        #[has_many(via = outgoing_follows.followed)]
267        following: toasty::Deferred<Vec<User>>,
268
269        #[has_many(via = incoming_follows.follower)]
270        followers: toasty::Deferred<Vec<User>>,
271    }
272
273    #[derive(Debug, toasty::Model)]
274    #[key(follower_id, followed_id)]
275    struct Follow {
276        #[index]
277        follower_id: uuid::Uuid,
278
279        #[belongs_to(key = follower_id, references = id)]
280        follower: toasty::Deferred<User>,
281
282        #[index]
283        followed_id: uuid::Uuid,
284
285        #[belongs_to(key = followed_id, references = id)]
286        followed: toasty::Deferred<User>,
287    }
288
289    let mut db = test.setup_db(models!(User, Follow)).await;
290
291    let users = toasty::create!(User::[
292        { name: "Alice" },
293        { name: "Bob" },
294        { name: "Carol" },
295    ])
296    .exec(&mut db)
297    .await?;
298    let (alice, bob, carol) = (&users[0], &users[1], &users[2]);
299
300    toasty::create!(Follow::[
301        { follower: alice, followed: bob   },
302        { follower: alice, followed: carol },
303        { follower: carol, followed: bob   },
304    ])
305    .exec(&mut db)
306    .await?;
307
308    let following = alice.following().exec(&mut db).await?;
309    assert_eq_unordered!(
310        following.iter().map(|user| &user.name[..]),
311        ["Bob", "Carol"]
312    );
313
314    let followers = bob.followers().exec(&mut db).await?;
315    assert_eq_unordered!(
316        followers.iter().map(|user| &user.name[..]),
317        ["Alice", "Carol"]
318    );
319
320    assert!(bob.following().exec(&mut db).await?.is_empty());
321
322    Ok(())
323}