tablo_core/resource/
write.rs1use toasty::{Executor, stmt::IntoInsert};
4use topcoat::{Result, context::Cx};
5
6use super::{Mounted, Resource, require_mounted};
7use crate::{
8 form::{FormValues, Posted, RecordForm},
9 tenancy::require_tenant,
10 toasty_compat::{join::JoinTable, model::AppSchema, pk::pk_filter},
11};
12
13pub async fn write_create<R: Resource>(
26 cx: &Cx,
27 form: R::Form,
28 ex: &mut dyn Executor,
29) -> Result<R::Model> {
30 let resource = require_mounted::<R>(cx)?;
31 let links = form.links();
32 let mut insert = form.into_create().into_insert();
33 match resource.tenancy.column_field() {
34 Some(Ok(column)) => {
35 insert.set(column.index, toasty::stmt::Value::from(require_tenant(cx)?));
36 }
37 Some(Err(error)) => {
38 return Err(crate::error::declaration(
39 crate::DeclarationError::of::<R>(crate::Site::Tenancy, error).to_string(),
40 ));
41 }
42 None => {}
43 }
44 let record: R::Model = ex
45 .exec(insert.into())
46 .await
47 .map_err(|error| -> topcoat::Error { error.into() })?;
48 for (field, keys) in links {
49 let join = join_table(cx, &resource, field)?;
50 join.link(&record, &keys, &mut *ex)
51 .await
52 .map_err(|error| -> topcoat::Error { error.into() })?;
53 }
54 Ok(record)
55}
56
57pub async fn write_update<R: Resource>(
67 cx: &Cx,
68 mut record: R::Model,
69 posted: Posted<R::Form>,
70 ex: &mut dyn Executor,
71) -> Result<R::Model> {
72 let links: Vec<_> = posted
73 .links()
74 .into_iter()
75 .filter(|(field, _)| posted.named(*field))
76 .collect();
77 if !links.is_empty() {
78 let resource = require_mounted::<R>(cx)?;
79 let mut stored = <R::Form as RecordForm>::hydrate(cx, &record);
80 for (field, keys) in links {
81 let join = join_table(cx, &resource, field)?;
82 let key = form_key(&resource, field);
83 if !stored.contains(key) {
84 stored = linked_now::<R>(cx, &record, &mut *ex).await?;
86 }
87 let held = stored.list(key).unwrap_or_default();
88 let removed: Vec<String> = held
89 .iter()
90 .filter(|key| !keys.contains(key))
91 .cloned()
92 .collect();
93 let added: Vec<String> = keys.into_iter().filter(|key| !held.contains(key)).collect();
94 join.unlink(&record, &removed, &mut *ex)
95 .await
96 .map_err(|error| -> topcoat::Error { error.into() })?;
97 join.link(&record, &added, &mut *ex)
98 .await
99 .map_err(|error| -> topcoat::Error { error.into() })?;
100 }
101 }
102 {
105 let pending = posted
106 .into_update(&mut record)
107 .map(|update| <R::Form as RecordForm>::exec_update(update, &mut *ex));
108 if let Some(pending) = pending {
109 pending
110 .await
111 .map_err(|error| -> topcoat::Error { error.into() })?;
112 }
113 }
114 Ok(record)
115}
116
117fn form_key<R: Resource>(resource: &Mounted<R>, field: <R::Form as RecordForm>::Field) -> &str {
119 resource
120 .fields
121 .iter()
122 .find(|claim| claim.field == field)
123 .and_then(|claim| claim.keys.first())
124 .map_or("", String::as_str)
125}
126
127fn join_table<R: Resource>(
133 cx: &Cx,
134 resource: &Mounted<R>,
135 field: <R::Form as RecordForm>::Field,
136) -> Result<JoinTable> {
137 let key = form_key(resource, field);
138 let schema = AppSchema::of(cx).ok_or_else(|| {
139 crate::error::declaration(crate::DeclarationErrorKind::MissingDb.to_string())
140 })?;
141 JoinTable::of::<R::Model>(&schema, key).map_err(|fault| {
142 crate::error::declaration(
143 crate::DeclarationError::of::<R>(
144 crate::Site::Form,
145 crate::DeclarationErrorKind::ManyToMany {
146 field: key.to_string(),
147 fault,
148 },
149 )
150 .to_string(),
151 )
152 })
153}
154
155async fn linked_now<R: Resource>(
157 cx: &Cx,
158 record: &R::Model,
159 ex: &mut dyn Executor,
160) -> Result<FormValues> {
161 let reloaded =
162 toasty::stmt::Query::<toasty::stmt::List<R::Model>>::all().filter(pk_filter(record));
163 let reloaded = <R::Form as RecordForm>::includes()
164 .into_vec()
165 .into_iter()
166 .fold(reloaded, |query, include| query.include(include))
167 .first()
168 .exec(ex)
169 .await
170 .map_err(|error| -> topcoat::Error { error.into() })?;
171 Ok(reloaded
172 .map(|reloaded| <R::Form as RecordForm>::hydrate(cx, &reloaded))
173 .unwrap_or_default())
174}