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(id(ID), 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(id(ID), 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(id(ID), 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(id(ID), 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(id(ID), 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: ID,
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(id(ID), 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: ID,
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(id(ID), 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: ID,
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(id(ID), 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: ID,
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(id(ID), 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(id(ID), 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(id(ID), 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(id(ID))]
274pub async fn update_macro_embedded_patch(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: ID,
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    Ok(())
315}
316
317/// Has-many insert via bracket-of-braces: `todos: [{ title: "x" }]`
318/// lowers to
319/// `todos: stmt::apply([stmt::insert(Todo::create().title("x"))])`.
320#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
321pub async fn update_macro_has_many_insert(test: &mut Test) -> Result<()> {
322    let mut db = setup(test).await;
323
324    let mut user = User::create().name("Alice").exec(&mut db).await?;
325
326    toasty::update!(user {
327        todos: [{ title: "buy milk" }, { title: "walk dog" }],
328    })
329    .exec(&mut db)
330    .await?;
331
332    let mut titles: Vec<_> = user
333        .todos()
334        .exec(&mut db)
335        .await?
336        .into_iter()
337        .map(|t| t.title)
338        .collect();
339    titles.sort();
340    assert_eq!(titles, ["buy milk", "walk dog"]);
341
342    Ok(())
343}
344
345/// Has-many list with mixed builders and plain expressions — the macro
346/// passes plain entries through and wraps inline builders in
347/// `stmt::insert`.
348#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
349pub async fn update_macro_has_many_mixed_list(test: &mut Test) -> Result<()> {
350    let mut db = setup(test).await;
351
352    let mut user = User::create().name("Alice").exec(&mut db).await?;
353    let old = user.todos().create().title("old").exec(&mut db).await?;
354
355    toasty::update!(user {
356        todos: [
357            { title: "new" },
358            toasty::stmt::remove(&old),
359        ],
360    })
361    .exec(&mut db)
362    .await?;
363
364    let titles: Vec<_> = user
365        .todos()
366        .exec(&mut db)
367        .await?
368        .into_iter()
369        .map(|t| t.title)
370        .collect();
371    assert_eq!(titles, ["new"]);
372
373    Ok(())
374}
375
376/// Mixing a scalar set, an embedded patch, and a method-shorthand call
377/// in one invocation.
378#[driver_test(id(ID))]
379pub async fn update_macro_mixed_shapes(test: &mut Test) -> Result<()> {
380    #[derive(Debug, toasty::Embed)]
381    #[allow(dead_code)]
382    struct Metadata {
383        version: i64,
384        status: String,
385    }
386
387    #[derive(Debug, toasty::Model)]
388    #[allow(dead_code)]
389    struct Document {
390        #[key]
391        #[auto]
392        id: ID,
393        title: String,
394        meta: Metadata,
395    }
396
397    let mut db = test.setup_db(models!(Document)).await;
398
399    let mut doc = toasty::create!(Document {
400        title: "Doc",
401        meta: Metadata {
402            version: 1,
403            status: "draft".to_string(),
404        },
405    })
406    .exec(&mut db)
407    .await?;
408
409    toasty::update!(doc {
410        title.set("New title"),
411        meta: { version: 2 },
412    })
413    .exec(&mut db)
414    .await?;
415
416    let reloaded = Document::get_by_id(&mut db, &doc.id).await?;
417    assert_eq!(reloaded.title, "New title");
418    assert_eq!(reloaded.meta.version, 2);
419    // status untouched
420    assert_eq!(reloaded.meta.status, "draft");
421
422    Ok(())
423}