Skip to main content

rustbasic_core/
macros.rs

1#[macro_export]
2#[doc(hidden)]
3macro_rules! __eager_load_belongs_to {
4    ($relation_name:ident, $fk:ident, $lk:ident, $related_model:path, $db:expr, $models:expr) => {
5        let ids: Vec<_> = $models.iter()
6            .map(|m| $crate::serde_json::to_value(&m.$fk).unwrap_or($crate::serde_json::Value::Null))
7            .filter(|v| !v.is_null())
8            .collect();
9        let related = <$related_model>::query($db).where_in(stringify!($lk), ids).get::<$related_model>().await?;
10        for m in &mut *$models {
11            m.$relation_name = related.iter().find(|r| {
12                let r_val = $crate::serde_json::to_value(&r.$lk).unwrap_or($crate::serde_json::Value::Null);
13                let m_val = $crate::serde_json::to_value(&m.$fk).unwrap_or($crate::serde_json::Value::Null);
14                r_val == m_val && !r_val.is_null()
15            }).cloned();
16        }
17    };
18}
19
20#[macro_export]
21#[doc(hidden)]
22macro_rules! __eager_load_has_many {
23    ($relation_name:ident, $fk:ident, $lk:ident, $related_model:path, $db:expr, $models:expr) => {
24        let ids: Vec<_> = $models.iter()
25            .map(|m| $crate::serde_json::to_value(&m.$lk).unwrap_or($crate::serde_json::Value::Null))
26            .filter(|v| !v.is_null())
27            .collect();
28        let related = <$related_model>::query($db).where_in(stringify!($fk), ids).get::<$related_model>().await?;
29        for m in &mut *$models {
30            let matched: Vec<$related_model> = related.iter().filter(|r| {
31                let r_val = $crate::serde_json::to_value(&r.$fk).unwrap_or($crate::serde_json::Value::Null);
32                let m_val = $crate::serde_json::to_value(&m.$lk).unwrap_or($crate::serde_json::Value::Null);
33                r_val == m_val && !r_val.is_null()
34            }).cloned().collect();
35            m.$relation_name = Some(matched);
36        }
37    };
38}
39
40#[macro_export]
41#[doc(hidden)]
42macro_rules! __eager_load_dispatcher {
43    (belongs_to, $relation_name:ident, $fk:ident, $lk:ident, $related_model:path, $db:expr, $models:expr) => {
44        $crate::__eager_load_belongs_to!($relation_name, $fk, $lk, $related_model, $db, $models);
45    };
46    (has_many, $relation_name:ident, $fk:ident, $lk:ident, $related_model:path, $db:expr, $models:expr) => {
47        $crate::__eager_load_has_many!($relation_name, $fk, $lk, $related_model, $db, $models);
48    };
49}
50
51#[macro_export]
52macro_rules! model {
53    (
54        table: $table_name:expr,
55        $(timestamps: $ts:expr,)?
56        $(soft_deletes: $sd:expr,)?
57        $(fillable: [ $($fill:ident),* ],)?
58        $(guarded: [ $($guard:ident),* ],)?
59        $(scopes: {
60            $( $scope_name:ident ( $($arg_name:ident : $arg_type:ty),* ) => $scope_body:expr ),* $(,)?
61        },)?
62        $(global_scopes: {
63            $( $gs_name:ident => $gs_body:expr ),* $(,)?
64        },)?
65        $(relations: {
66            $( $relation_name:ident ( $rel_type:ident, foreign_key: $fk:ident, local_key: $lk:ident ) => $related_model:path ),* $(,)?
67        },)?
68        Model {
69            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
70        }
71    ) => {
72        #[derive(Clone, Debug, PartialEq, $crate::serde::Serialize, $crate::serde::Deserialize)]
73        pub struct Model {
74            $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
75        }
76
77        pub type Entity = Model;
78
79        $(
80            pub trait ModelScopes<'a> {
81                $(
82                    fn $scope_name(self, $($arg_name: $arg_type),*) -> Self;
83                )*
84            }
85
86            impl<'a> ModelScopes<'a> for $crate::database::QueryBuilder<'a> {
87                $(
88                    fn $scope_name(self, $($arg_name: $arg_type),*) -> Self {
89                        let f: fn($crate::database::QueryBuilder<'a>, $($arg_type),*) -> $crate::database::QueryBuilder<'a> = $scope_body;
90                        f(self, $($arg_name),*)
91                    }
92                )*
93            }
94        )?
95
96        pub struct ModelQuery<'a> {
97            builder: $crate::database::QueryBuilder<'a>,
98            relations: Vec<String>,
99        }
100
101        impl<'a> ModelQuery<'a> {
102            pub fn new(db: &'a $crate::sqlx::AnyPool) -> Self {
103                Self {
104                    builder: Model::query(db),
105                    relations: Vec::new(),
106                }
107            }
108
109            pub fn with(mut self, relations: &[&str]) -> Self {
110                self.relations.extend(relations.iter().map(|r| r.to_string()));
111                self
112            }
113
114            pub fn where_(mut self, column: &str, value: impl serde::Serialize) -> Self {
115                self.builder = self.builder.where_(column, value);
116                self
117            }
118
119            pub fn where_op(mut self, column: &str, operator: &str, value: impl serde::Serialize) -> Self {
120                self.builder = self.builder.where_op(column, operator, value);
121                self
122            }
123
124            pub fn where_raw(mut self, sql: &str, binds: Vec<$crate::serde_json::Value>) -> Self {
125                self.builder = self.builder.where_raw(sql, binds);
126                self
127            }
128
129            pub fn order_by(mut self, column: &str, direction: &str) -> Self {
130                self.builder = self.builder.order_by(column, direction);
131                self
132            }
133
134            pub fn limit(mut self, limit: usize) -> Self {
135                self.builder = self.builder.limit(limit);
136                self
137            }
138
139            pub async fn get(self) -> Result<Vec<Model>, $crate::sqlx::Error> {
140                let db = self.builder.pool();
141                let mut models = self.builder.get::<Model>().await?;
142
143                // Apply eager loading for relations if defined
144                $(
145                    for rel in &self.relations {
146                        match rel.as_str() {
147                            $(
148                                stringify!($relation_name) => {
149                                    $crate::__eager_load_dispatcher!($rel_type, $relation_name, $fk, $lk, $related_model, db, &mut models);
150                                }
151                            )*
152                            _ => {}
153                        }
154                    }
155                )?
156
157                Ok(models)
158            }
159
160            pub async fn first(self) -> Result<Option<Model>, $crate::sqlx::Error> {
161                let mut models = self.get().await?;
162                if models.is_empty() {
163                    Ok(None)
164                } else {
165                    Ok(Some(models.remove(0)))
166                }
167            }
168        }
169
170        impl Model {
171            /// Mulai memuat relasi secara eager (Model::with([...]))
172            pub fn with<'a>(db: &'a $crate::sqlx::AnyPool, relations: &[&str]) -> ModelQuery<'a> {
173                ModelQuery::new(db).with(relations)
174            }
175
176            /// Mulai Query Builder baru untuk tabel model ini (Model::query())
177            pub fn query<'a>(db: &'a $crate::sqlx::AnyPool) -> $crate::database::QueryBuilder<'a> {
178                let mut q = $crate::database::DB::table(db, $table_name);
179                let mut has_soft_deletes = false;
180                $(
181                    if $sd {
182                        has_soft_deletes = true;
183                    }
184                )?
185                if has_soft_deletes {
186                    q = q.where_raw("`deleted_at` IS NULL", vec![]);
187                }
188
189                // Terapkan scope global khusus
190                $(
191                    $(
192                        q = {
193                            let f: fn($crate::database::QueryBuilder<'a>) -> $crate::database::QueryBuilder<'a> = $gs_body;
194                            f(q)
195                        };
196                    )*
197                )?
198
199                q
200            }
201
202            /// Mulai query builder tanpa menerapkan scope global apa pun (termasuk soft deletes)
203            pub fn query_without_global_scopes<'a>(db: &'a $crate::sqlx::AnyPool) -> $crate::database::QueryBuilder<'a> {
204                $crate::database::DB::table(db, $table_name)
205            }
206
207            /// Ambil semua catatan dari model ini (Model::all())
208            pub async fn all(db: &$crate::sqlx::AnyPool) -> Result<Vec<Self>, $crate::sqlx::Error> {
209                Self::query(db).get::<Self>().await
210            }
211
212            /// Ambil catatan pertama dari model ini (Model::first())
213            pub async fn first(db: &$crate::sqlx::AnyPool) -> Result<Option<Self>, $crate::sqlx::Error> {
214                Self::query(db).first::<Self>().await
215            }
216
217            /// Cari catatan berdasarkan ID numeriknya (Model::find($id))
218            pub async fn find(db: &$crate::sqlx::AnyPool, id: i32) -> Result<Option<Self>, $crate::sqlx::Error> {
219                Self::query(db).where_("id", id).first::<Self>().await
220            }
221
222            /// Mengambil total jumlah data dari model ini (Model::count())
223            pub async fn count(db: &$crate::sqlx::AnyPool) -> Result<i64, $crate::sqlx::Error> {
224                Self::query(db).count().await
225            }
226
227            /// Menghapus data berdasarkan ID (Model::destroy($id))
228            pub async fn destroy(db: &$crate::sqlx::AnyPool, id: i32) -> Result<u64, $crate::sqlx::Error> {
229                let mut has_soft_deletes = false;
230                $(
231                    if $sd {
232                        has_soft_deletes = true;
233                    }
234                )?
235                if has_soft_deletes {
236                    $crate::database::DB::table(db, $table_name)
237                        .where_("id", id)
238                        .update($crate::serde_json::json!({
239                            "deleted_at": $crate::chrono::Utc::now().naive_utc()
240                        }))
241                        .await
242                } else {
243                    $crate::database::DB::table(db, $table_name).where_("id", id).delete().await
244                }
245            }
246
247            /// Mulai query builder dengan menyertakan data yang telah dihapus secara lunak (Model::withTrashed())
248            pub fn query_with_trashed<'a>(db: &'a $crate::sqlx::AnyPool) -> $crate::database::QueryBuilder<'a> {
249                $crate::database::DB::table(db, $table_name)
250            }
251
252            /// Mulai query builder hanya untuk data yang telah dihapus secara lunak (Model::onlyTrashed())
253            pub fn query_only_trashed<'a>(db: &'a $crate::sqlx::AnyPool) -> $crate::database::QueryBuilder<'a> {
254                $crate::database::DB::table(db, $table_name).where_raw("`deleted_at` IS NOT NULL", vec![])
255            }
256
257            /// Memulihkan data yang telah dihapus secara lunak ($model->restore())
258            pub async fn restore(db: &$crate::sqlx::AnyPool, id: i32) -> Result<u64, $crate::sqlx::Error> {
259                $crate::database::DB::table(db, $table_name)
260                    .where_("id", id)
261                    .update($crate::serde_json::json!({
262                        "deleted_at": $crate::serde_json::Value::Null
263                    }))
264                    .await
265            }
266
267            /// Menghapus data secara permanen ($model->force_destroy(id))
268            pub async fn force_destroy(db: &$crate::sqlx::AnyPool, id: i32) -> Result<u64, $crate::sqlx::Error> {
269                $crate::database::DB::table(db, $table_name).where_("id", id).delete().await
270            }
271
272            $(
273                $(
274                    pub fn $scope_name<'a>(db: &'a $crate::sqlx::AnyPool, $($arg_name: $arg_type),*) -> $crate::database::QueryBuilder<'a> {
275                        use self::ModelScopes;
276                        Self::query(db).$scope_name($($arg_name),*)
277                    }
278                )*
279            )?
280
281            pub async fn create(db: &$crate::sqlx::AnyPool, mut data: $crate::serde_json::Value) -> Result<Self, $crate::sqlx::Error> {
282                let mut data_to_insert = data.clone();
283
284                // Terapkan penyaringan Fillable jika didefinisikan
285                $(
286                    let fillable_keys: Vec<&str> = vec![ $( stringify!($fill) ),* ];
287                    if let Some(obj) = data.as_object() {
288                        let mut filtered_obj = $crate::serde_json::Map::new();
289                        for key in fillable_keys {
290                            if let Some(val) = obj.get(key) {
291                                filtered_obj.insert(key.to_string(), val.clone());
292                            }
293                        }
294                        data_to_insert = $crate::serde_json::Value::Object(filtered_obj);
295                    }
296                )?
297
298                // Terapkan penyaringan Guarded jika didefinisikan
299                $(
300                    let guarded_keys: Vec<&str> = vec![ $( stringify!($guard) ),* ];
301                    if let Some(obj) = data_to_insert.as_object_mut() {
302                        for key in guarded_keys {
303                            obj.remove(key);
304                        }
305                    }
306                )?
307
308                let res = $crate::database::DB::table(db, $table_name).insert_get_id(data_to_insert.clone()).await;
309                match res {
310                    Ok(id) => {
311                        if let Some(obj) = data.as_object_mut() {
312                            if !obj.contains_key("id") {
313                                obj.insert("id".to_string(), $crate::serde_json::json!(id));
314                            }
315                        }
316                    }
317                    Err(e) => {
318                        println!("insert_get_id failed: {:?}", e);
319                        $crate::database::DB::table(db, $table_name).insert(data_to_insert.clone()).await?;
320                    }
321                }
322                let parsed = $crate::serde_json::from_value::<Self>(data)
323                    .map_err(|e| $crate::sqlx::Error::Protocol(format!("Deserialization error: {}", e)))?;
324                Ok(parsed)
325            }
326        }
327    };
328}
329
330#[macro_export]
331macro_rules! seeder {
332    (
333        $name:ident,
334        run($db:ident) $body:block
335    ) => {
336        pub struct $name;
337
338        #[$crate::async_trait]
339        impl $crate::seeder::SeederTrait for $name {
340            async fn run<'a>(&'a self, $db: &'a $crate::sqlx::AnyPool) -> Result<(), $crate::sqlx::Error> $body
341        }
342    };
343
344    (
345        run($db:ident) $body:block
346    ) => {
347        $crate::seeder! {
348            DatabaseSeeder,
349            run($db) $body
350        }
351    };
352}