1use crate::builder::QueryBuilder;
9use crate::value::{FromValue, Value};
10use crate::{Database, Row};
11use rustlavel_core::{Error, Json, Result};
12use std::collections::HashMap;
13
14pub trait Model: Sized + Default + Send + Sync {
16 type Key: FromValue + Into<Value> + Clone + Send + Sync + std::fmt::Debug;
18
19 const TABLE: &'static str;
20 const PRIMARY_KEY: &'static str;
21 const COLUMNS: &'static str;
23
24 fn from_row(row: &Row) -> Result<Self>;
25 fn key(&self) -> Self::Key;
26 fn set_key(&mut self, key: Self::Key);
27
28 fn values(&self) -> Vec<(&'static str, Value)>;
31}
32
33pub trait ModelExt: Model {
37 fn query() -> QueryBuilder {
39 QueryBuilder::new(Self::TABLE)
40 }
41
42 fn hydrate(rows: &[Row]) -> Result<Vec<Self>> {
44 rows.iter().map(Self::from_row).collect()
45 }
46
47 fn all(db: &Database) -> impl Future<Output = Result<Vec<Self>>> + Send
48 where
49 Self: Send,
50 {
51 async move { Self::hydrate(&Self::query().get(db).await?) }
52 }
53
54 fn find(db: &Database, key: Self::Key) -> impl Future<Output = Result<Option<Self>>> + Send
56 where
57 Self: Send,
58 {
59 async move {
60 let row = Self::query().filter(Self::PRIMARY_KEY, key).first(db).await?;
61 row.as_ref().map(Self::from_row).transpose()
62 }
63 }
64
65 fn find_or_fail(db: &Database, key: Self::Key) -> impl Future<Output = Result<Self>> + Send
67 where
68 Self: Send,
69 {
70 async move {
71 let described = format!("{:?}", key);
72 Self::find(db, key).await?.ok_or_else(|| {
73 Error::msg(format!(
74 "no {} with {} = {described}",
75 Self::TABLE,
76 Self::PRIMARY_KEY
77 ))
78 })
79 }
80 }
81
82 fn get(db: &Database, query: QueryBuilder) -> impl Future<Output = Result<Vec<Self>>> + Send
84 where
85 Self: Send,
86 {
87 async move { Self::hydrate(&query.get(db).await?) }
88 }
89
90 fn first(db: &Database, query: QueryBuilder) -> impl Future<Output = Result<Option<Self>>> + Send
91 where
92 Self: Send,
93 {
94 async move {
95 let row = query.first(db).await?;
96 row.as_ref().map(Self::from_row).transpose()
97 }
98 }
99
100 fn count(db: &Database) -> impl Future<Output = Result<i64>> + Send
101 where
102 Self: Send,
103 {
104 async move { Self::query().count(db).await }
105 }
106
107 fn insert(&mut self, db: &Database) -> impl Future<Output = Result<()>> + Send
109 where
110 Self: Send,
111 {
112 async move {
113 let values = self.values();
114 let borrowed: Vec<(&str, Value)> =
115 values.iter().map(|(name, value)| (*name, value.clone())).collect();
116
117 let row = QueryBuilder::new(Self::TABLE)
118 .insert_returning(db, &borrowed, Self::PRIMARY_KEY)
119 .await?;
120 self.set_key(Self::Key::from_value(&row)?);
121 Ok(())
122 }
123 }
124
125 fn update(&self, db: &Database) -> impl Future<Output = Result<u64>> + Send
127 where
128 Self: Send + Sync,
129 {
130 async move {
131 let values = self.values();
132 let borrowed: Vec<(&str, Value)> =
133 values.iter().map(|(name, value)| (*name, value.clone())).collect();
134
135 Self::query()
136 .filter(Self::PRIMARY_KEY, self.key())
137 .update(db, &borrowed)
138 .await
139 }
140 }
141
142 fn delete(&self, db: &Database) -> impl Future<Output = Result<u64>> + Send
143 where
144 Self: Send + Sync,
145 {
146 async move { Self::query().filter(Self::PRIMARY_KEY, self.key()).delete(db).await }
147 }
148
149 fn to_json(&self) -> Json
151 where
152 Self: Sync,
153 {
154 let mut fields: Vec<(String, Json)> = vec![(
155 Self::PRIMARY_KEY.to_string(),
156 Json::from(self.key().into()),
157 )];
158 fields.extend(
159 self.values()
160 .into_iter()
161 .map(|(name, value)| (name.to_string(), Json::from(value))),
162 );
163 Json::object(fields)
164 }
165}
166
167impl<T: Model> ModelExt for T {}
168
169pub async fn has_many<P, C>(
179 db: &Database,
180 parents: &[P],
181 foreign_key: &str,
182) -> Result<Vec<Vec<C>>>
183where
184 P: Model,
185 C: Model,
186{
187 if parents.is_empty() {
188 return Ok(Vec::new());
189 }
190
191 let keys: Vec<Value> = parents.iter().map(|parent| parent.key().into()).collect();
192 let rows = C::query().filter_in(foreign_key, keys).get(db).await?;
193
194 let mut grouped: HashMap<String, Vec<C>> = HashMap::new();
196 for row in &rows {
197 let owner = row.value(foreign_key)?.to_display();
198 grouped.entry(owner).or_default().push(C::from_row(row)?);
199 }
200
201 Ok(parents
202 .iter()
203 .map(|parent| {
204 let key: Value = parent.key().into();
205 grouped.remove(&key.to_display()).unwrap_or_default()
206 })
207 .collect())
208}
209
210pub async fn belongs_to<C, P>(
212 db: &Database,
213 children: &[C],
214 foreign_key: &str,
215) -> Result<Vec<Option<P>>>
216where
217 C: Model,
218 P: Model,
219{
220 if children.is_empty() {
221 return Ok(Vec::new());
222 }
223
224 let child_keys: Vec<Value> = children.iter().map(|child| child.key().into()).collect();
227
228 let pairs = QueryBuilder::new(C::TABLE)
229 .select(&[C::PRIMARY_KEY, foreign_key])
230 .filter_in(C::PRIMARY_KEY, child_keys)
231 .get(db)
232 .await?;
233
234 let mut owner_of: HashMap<String, String> = HashMap::new();
235 let mut parent_keys: Vec<Value> = Vec::new();
236 for row in &pairs {
237 let child = row.value(C::PRIMARY_KEY)?.to_display();
238 let parent = row.value(foreign_key)?.clone();
239 if !parent.is_null() {
240 owner_of.insert(child, parent.to_display());
241 parent_keys.push(parent);
242 }
243 }
244
245 let parent_rows = P::query().filter_in(P::PRIMARY_KEY, parent_keys).get(db).await?;
246
247 let mut rows_by_key: HashMap<String, &Row> = HashMap::new();
250 for row in &parent_rows {
251 rows_by_key.insert(row.value(P::PRIMARY_KEY)?.to_display(), row);
252 }
253
254 children
255 .iter()
256 .map(|child| {
257 let key: Value = child.key().into();
258 match owner_of.get(&key.to_display()).and_then(|parent| rows_by_key.get(parent)) {
259 Some(row) => P::from_row(row).map(Some),
260 None => Ok(None),
261 }
262 })
263 .collect()
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[derive(Default, Debug, PartialEq)]
273 struct User {
274 id: i64,
275 name: String,
276 email: Option<String>,
277 }
278
279 impl Model for User {
280 type Key = i64;
281
282 const TABLE: &'static str = "users";
283 const PRIMARY_KEY: &'static str = "id";
284 const COLUMNS: &'static str = "\"id\", \"name\", \"email\"";
285
286 fn from_row(row: &Row) -> Result<Self> {
287 Ok(User {
288 id: row.get("id")?,
289 name: row.get("name")?,
290 email: row.get("email")?,
291 })
292 }
293
294 fn key(&self) -> i64 {
295 self.id
296 }
297
298 fn set_key(&mut self, key: i64) {
299 self.id = key;
300 }
301
302 fn values(&self) -> Vec<(&'static str, Value)> {
303 vec![
304 ("name", Value::from(self.name.clone())),
305 ("email", Value::from(self.email.clone())),
306 ]
307 }
308 }
309
310 fn row(id: i64, name: &str, email: Option<&str>) -> Row {
311 let columns =
312 std::sync::Arc::new(vec!["id".to_string(), "name".to_string(), "email".to_string()]);
313 Row::new(
314 columns,
315 vec![
316 Value::Int(id),
317 Value::Text(name.into()),
318 email.map_or(Value::Null, |e| Value::Text(e.into())),
319 ],
320 )
321 }
322
323 #[test]
324 fn hydrates_rows_into_models() {
325 let users = User::hydrate(&[row(1, "Ada", Some("ada@example.com")), row(2, "Grace", None)])
326 .unwrap();
327
328 assert_eq!(users[0].name, "Ada");
329 assert_eq!(users[0].email.as_deref(), Some("ada@example.com"));
330 assert_eq!(users[1].email, None);
331 }
332
333 #[test]
334 fn a_query_targets_the_models_table() {
335 let (sql, _) =
336 User::query().filter("name", "Ada").to_sql(&crate::dialect::Postgres).unwrap();
337 assert_eq!(sql, r#"select * from "users" where "name" = $1"#);
338 }
339
340 #[test]
341 fn json_includes_the_primary_key_and_every_value() {
342 let user = User { id: 7, name: "Ada".into(), email: None };
343
344 assert_eq!(user.to_json().to_string(), r#"{"email":null,"id":7,"name":"Ada"}"#);
345 }
346
347 #[test]
348 fn a_missing_column_names_itself() {
349 let columns = std::sync::Arc::new(vec!["id".to_string()]);
350 let incomplete = Row::new(columns, vec![Value::Int(1)]);
351
352 let error = User::from_row(&incomplete).unwrap_err().to_string();
353 assert!(error.contains("no column `name`"));
354 }
355}