Skip to main content

toasty_driver_integration_suite/tests/
crud_update_macro.rs

1use crate::prelude::*;
2
3/// Basic single-field update through an instance target.
4#[driver_test(scenario(crate::scenarios::two_models))]
5pub async fn update_macro_simple(test: &mut Test) -> Result<()> {
6    let mut db = setup(test).await;
7
8    let mut user = User::create().name("Carl").exec(&mut db).await?;
9
10    toasty::update!(user { name: "Carlos" })
11        .exec(&mut db)
12        .await?;
13
14    assert_eq!(user.name, "Carlos");
15
16    let reloaded = User::get_by_id(&mut db, &user.id).await?;
17    assert_eq!(reloaded.name, "Carlos");
18
19    Ok(())
20}
21
22/// Multiple fields in one update call.
23#[driver_test(scenario(crate::scenarios::user_name_email))]
24pub async fn update_macro_multiple_fields(test: &mut Test) -> Result<()> {
25    let mut db = setup(test).await;
26
27    let mut user = User::create()
28        .name("Carl")
29        .email("carl@example.com")
30        .exec(&mut db)
31        .await?;
32
33    toasty::update!(user {
34        name: "Carlos",
35        email: "carlos@example.com",
36    })
37    .exec(&mut db)
38    .await?;
39
40    assert_eq!(user.name, "Carlos");
41    assert_eq!(user.email, "carlos@example.com");
42
43    Ok(())
44}
45
46/// Field shorthand — `name` as a bare ident expands to `name: name`.
47#[driver_test(scenario(crate::scenarios::two_models))]
48pub async fn update_macro_shorthand(test: &mut Test) -> Result<()> {
49    let mut db = setup(test).await;
50
51    let mut user = User::create().name("Carl").exec(&mut db).await?;
52
53    let name = "Carlos";
54    toasty::update!(user { name }).exec(&mut db).await?;
55
56    assert_eq!(user.name, "Carlos");
57
58    Ok(())
59}
60
61/// Method shorthand: `field.set(value)` lowers to
62/// `field: stmt::set(value)`.
63#[driver_test(scenario(crate::scenarios::two_models))]
64pub async fn update_macro_method_shorthand_set(test: &mut Test) -> Result<()> {
65    let mut db = setup(test).await;
66
67    let mut user = User::create().name("Carl").exec(&mut db).await?;
68
69    toasty::update!(user { name.set("Carlos") })
70        .exec(&mut db)
71        .await?;
72
73    assert_eq!(user.name, "Carlos");
74
75    Ok(())
76}
77
78/// Method shorthand on a `Vec<scalar>` field: `tags.push("rust")`
79/// lowers to `tags: stmt::push("rust")` for an atomic append.
80#[driver_test(requires(vec_scalar))]
81pub async fn update_macro_method_shorthand_push(test: &mut Test) -> Result<()> {
82    #[derive(Debug, toasty::Model)]
83    #[allow(dead_code)]
84    struct Item {
85        #[key]
86        #[auto]
87        id: uuid::Uuid,
88        tags: Vec<String>,
89    }
90
91    let mut db = test.setup_db(models!(Item)).await;
92
93    let mut item = toasty::create!(Item {
94        tags: vec!["a".to_string()],
95    })
96    .exec(&mut db)
97    .await?;
98
99    toasty::update!(item { tags.push("b") })
100        .exec(&mut db)
101        .await?;
102
103    assert_eq!(item.tags, vec!["a".to_string(), "b".to_string()]);
104
105    let reloaded = Item::get_by_id(&mut db, &item.id).await?;
106    assert_eq!(reloaded.tags, vec!["a".to_string(), "b".to_string()]);
107
108    Ok(())
109}
110
111/// Method shorthand on a `Vec<scalar>` field: `tags.extend([..])`
112/// lowers to `tags: stmt::extend([..])` for an atomic batch append.
113#[driver_test(requires(vec_scalar))]
114pub async fn update_macro_method_shorthand_extend(test: &mut Test) -> Result<()> {
115    #[derive(Debug, toasty::Model)]
116    #[allow(dead_code)]
117    struct Item {
118        #[key]
119        #[auto]
120        id: uuid::Uuid,
121        tags: Vec<String>,
122    }
123
124    let mut db = test.setup_db(models!(Item)).await;
125
126    let mut item = toasty::create!(Item {
127        tags: vec!["a".to_string()],
128    })
129    .exec(&mut db)
130    .await?;
131
132    toasty::update!(item { tags.extend(["b", "c"]) })
133        .exec(&mut db)
134        .await?;
135
136    let expected = vec!["a".to_string(), "b".to_string(), "c".to_string()];
137    assert_eq!(item.tags, expected);
138
139    let reloaded = Item::get_by_id(&mut db, &item.id).await?;
140    assert_eq!(reloaded.tags, expected);
141
142    Ok(())
143}
144
145/// Method shorthand on a `Vec<scalar>` field: `tags.pop()` lowers to
146/// `tags: stmt::pop()` for an atomic trailing-element drop.
147#[driver_test(requires(vec_pop))]
148pub async fn update_macro_method_shorthand_pop(test: &mut Test) -> Result<()> {
149    #[derive(Debug, toasty::Model)]
150    #[allow(dead_code)]
151    struct Item {
152        #[key]
153        #[auto]
154        id: uuid::Uuid,
155        tags: Vec<String>,
156    }
157
158    let mut db = test.setup_db(models!(Item)).await;
159
160    let mut item = toasty::create!(Item {
161        tags: vec!["a".to_string(), "b".to_string(), "c".to_string()],
162    })
163    .exec(&mut db)
164    .await?;
165
166    toasty::update!(item { tags.pop() }).exec(&mut db).await?;
167
168    let expected = vec!["a".to_string(), "b".to_string()];
169    assert_eq!(item.tags, expected);
170
171    let reloaded = Item::get_by_id(&mut db, &item.id).await?;
172    assert_eq!(reloaded.tags, expected);
173
174    Ok(())
175}
176
177/// Method shorthand on a `Vec<scalar>` field: `tags.clear()` lowers to
178/// `tags: stmt::clear()`, replacing the field with an empty list.
179#[driver_test(requires(vec_scalar))]
180pub async fn update_macro_method_shorthand_clear(test: &mut Test) -> Result<()> {
181    #[derive(Debug, toasty::Model)]
182    #[allow(dead_code)]
183    struct Item {
184        #[key]
185        #[auto]
186        id: uuid::Uuid,
187        tags: Vec<String>,
188    }
189
190    let mut db = test.setup_db(models!(Item)).await;
191
192    let mut item = toasty::create!(Item {
193        tags: vec!["a".to_string(), "b".to_string()],
194    })
195    .exec(&mut db)
196    .await?;
197
198    toasty::update!(item { tags.clear() }).exec(&mut db).await?;
199
200    assert!(item.tags.is_empty());
201
202    let reloaded = Item::get_by_id(&mut db, &item.id).await?;
203    assert!(reloaded.tags.is_empty());
204
205    Ok(())
206}
207
208/// Value expressions may read the target instance's own fields inline
209/// (issue #1073). The macro evaluates values before `.update()` borrows
210/// the target, so `active: !user.active` compiles.
211#[driver_test(scenario(crate::scenarios::user_with_active))]
212pub async fn update_macro_value_reads_target_field(test: &mut Test) -> Result<()> {
213    let mut db = setup(test).await;
214
215    let mut user = toasty::create!(User {
216        name: "Carl",
217        age: 30,
218        active: false,
219    })
220    .exec(&mut db)
221    .await?;
222
223    toasty::update!(user {
224        active: !user.active,
225        age: user.age + 1,
226    })
227    .exec(&mut db)
228    .await?;
229
230    assert_struct!(user, { active: true, age: 31 });
231
232    let reloaded = User::get_by_id(&mut db, &user.id).await?;
233    assert_struct!(reloaded, { active: true, age: 31 });
234
235    Ok(())
236}
237
238/// Method-shorthand arguments may also read the target instance's own
239/// fields inline (issue #1073).
240#[driver_test(scenario(crate::scenarios::two_models))]
241pub async fn update_macro_method_shorthand_reads_target_field(test: &mut Test) -> Result<()> {
242    let mut db = setup(test).await;
243
244    let mut user = User::create().name("carl").exec(&mut db).await?;
245
246    toasty::update!(user { name.set(user.name.to_uppercase()) })
247        .exec(&mut db)
248        .await?;
249
250    assert_eq!(user.name, "CARL");
251
252    Ok(())
253}
254
255/// Update through a query builder target — no instance required.
256#[driver_test(scenario(crate::scenarios::two_models))]
257pub async fn update_macro_query_target(test: &mut Test) -> Result<()> {
258    let mut db = setup(test).await;
259    let user = User::create().name("Alice").exec(&mut db).await?;
260
261    toasty::update!(User::filter_by_id(user.id) { name: "Bob" })
262        .exec(&mut db)
263        .await?;
264
265    let reloaded = User::get_by_id(&mut db, &user.id).await?;
266    assert_eq!(reloaded.name, "Bob");
267
268    Ok(())
269}
270
271/// Embedded partial update via brace block: `meta: { version: 2 }`
272/// lowers to `meta: stmt::apply([stmt::patch(<Metadata>::fields().version(), 2)])`.
273#[driver_test]
274pub async fn update_macro_embedded_patch_and_mixed_shapes(test: &mut Test) -> Result<()> {
275    #[derive(Debug, toasty::Embed)]
276    #[allow(dead_code)]
277    struct Metadata {
278        version: i64,
279        status: String,
280    }
281
282    #[derive(Debug, toasty::Model)]
283    #[allow(dead_code)]
284    struct Document {
285        #[key]
286        #[auto]
287        id: uuid::Uuid,
288        title: String,
289        meta: Metadata,
290    }
291
292    let mut db = test.setup_db(models!(Document)).await;
293
294    let mut doc = toasty::create!(Document {
295        title: "Doc",
296        meta: Metadata {
297            version: 1,
298            status: "draft".to_string(),
299        },
300    })
301    .exec(&mut db)
302    .await?;
303
304    toasty::update!(doc {
305        meta: { version: 2, status: "published" },
306    })
307    .exec(&mut db)
308    .await?;
309
310    let reloaded = Document::get_by_id(&mut db, &doc.id).await?;
311    assert_eq!(reloaded.meta.version, 2);
312    assert_eq!(reloaded.meta.status, "published");
313
314    // Mix a scalar set, embedded patch, and method-shorthand call. Omitting
315    // `status` from the second patch must leave its existing value untouched.
316    toasty::update!(doc {
317        title.set("New title"),
318        meta: { version: 3 },
319    })
320    .exec(&mut db)
321    .await?;
322
323    let reloaded = Document::get_by_id(&mut db, &doc.id).await?;
324    assert_eq!(reloaded.title, "New title");
325    assert_eq!(reloaded.meta.version, 3);
326    assert_eq!(reloaded.meta.status, "published");
327
328    Ok(())
329}
330
331/// Has-many insert via bracket-of-braces: `todos: [{ title: "x" }]`
332/// lowers to
333/// `todos: stmt::apply([stmt::insert(Todo::create().title("x"))])`.
334#[driver_test(scenario(crate::scenarios::has_many_belongs_to::id_uuid))]
335pub async fn update_macro_has_many_insert(test: &mut Test) -> Result<()> {
336    let mut db = setup(test).await;
337
338    let mut user = User::create().name("Alice").exec(&mut db).await?;
339
340    toasty::update!(user {
341        todos: [{ title: "buy milk" }, { title: "walk dog" }],
342    })
343    .exec(&mut db)
344    .await?;
345
346    let mut titles: Vec<_> = user
347        .todos()
348        .exec(&mut db)
349        .await?
350        .into_iter()
351        .map(|t| t.title)
352        .collect();
353    titles.sort();
354    assert_eq!(titles, ["buy milk", "walk dog"]);
355
356    Ok(())
357}
358
359/// Has-many list with mixed builders and plain expressions — the macro
360/// passes plain entries through and wraps inline builders in
361/// `stmt::insert`.
362#[driver_test(scenario(crate::scenarios::has_many_belongs_to::id_uuid))]
363pub async fn update_macro_has_many_mixed_list(test: &mut Test) -> Result<()> {
364    let mut db = setup(test).await;
365
366    let mut user = User::create().name("Alice").exec(&mut db).await?;
367    let old = user.todos().create().title("old").exec(&mut db).await?;
368
369    toasty::update!(user {
370        todos: [
371            { title: "new" },
372            toasty::stmt::remove(&old),
373        ],
374    })
375    .exec(&mut db)
376    .await?;
377
378    let titles: Vec<_> = user
379        .todos()
380        .exec(&mut db)
381        .await?
382        .into_iter()
383        .map(|t| t.title)
384        .collect();
385    assert_eq!(titles, ["new"]);
386
387    Ok(())
388}