DSL `model!` & AST
DSL `model! { ... }` : structure attendue
Le parseur attend une structure stricte :
- nom du modèle,
table: "...",pk: id => i32|i64|uuid,fields: { ... },relations: { ... }optionnel,meta: { ... }optionnel.
Exemple concret :
use runique::prelude::*;
model! {
User,
table: "users",
pk: id => i32,
fields: {
username: String [required, max_len(150), unique],
email: String [required, unique],
password: String [required],
is_active: bool,
team_id: i32 [required],
created_at: datetime [auto_now],
},
relations: {
has_many: Post,
belongs_to: Team via team_id,
},
}
AST interne (ce qui est parsé)
La DSL est convertie en AST Model avec notamment :
name,table,pkfields: Vec<FieldDef>relations: Vec<RelationDef>meta: Option<MetaDef>
Types pris en charge
- texte :
String,text,char,varchar(n),var_binary(n) - numériques :
i8/i16/i32/i64/u32/u64/f32/f64,decimal(p,s),decimal - date/temps :
date,time,datetime,timestamp,timestamp_tz,interval - autres :
bool,uuid,json,json_binary,binary(n),binary,blob,enum(A, B, ...),inet,cidr,mac_address
Options de champ
required,nullable,unique,readonlymax_len(n),min_len(n),max(n),min(n),max_f(n),min_f(n)auto_now,auto_now_updatelabel(...),help(...),select_as(...)file(kind),file(kind, "chemin/upload")— champ fichier (voir ci-dessous)
Champs fichier — file()
Un champ String peut être déclaré comme champ fichier avec l'option file(). Le formulaire auto-généré (AdminForm) utilisera alors un FileField au lieu d'un TextField.
model! {
Article,
table: "articles",
pk: id => i32,
fields: {
titre: String [required],
// image — dossier explicite
image: String [file(image, "media/articles")],
// document — dossier auto depuis MEDIA_ROOT + nom du champ
fichier: String [file(document)],
// tout type de fichier
piece_jointe: String [file(any, "media/uploads")],
},
}
Types disponibles :
| Valeur | Extensions autorisées par défaut | Correspondance |
|---|---|---|
image | jpg jpeg png gif webp avif | FileField::image() |
document | pdf doc docx txt odt | FileField::document() |
any | aucun filtre | FileField::any() |
Chemin d'upload :
| Syntaxe | Destination |
|---|---|
file(image, "media/articles") | media/articles/ (chemin exact) |
file(image) | {MEDIA_ROOT}/{nom_du_champ}/ (lit MEDIA_ROOT depuis .env) |
Les fichiers invalides sont supprimés du disque si la validation échoue. Le dossier de destination est créé automatiquement lors du premier upload valide.
Relations
Les relations sont déclarées dans un bloc relations: { ... } optionnel après fields.
| Syntaxe | Contrainte DB | Description |
|---|---|---|
belongs_to: Model via fk_field, | ✅ FOREIGN KEY générée | Clé étrangère vers model.id |
belongs_to: Model via fk_field [cascade], | ✅ ON DELETE CASCADE | FK avec on_delete cascade |
belongs_to: Model via fk_field [cascade, restrict], | ✅ | FK avec on_delete + on_update |
has_many: Model, | ❌ (code uniquement) | Relation 1-N |
has_one: Model, | ❌ (code uniquement) | Relation 1-1 |
many_to_many: Model via pivot_table, | ❌ (code uniquement) | Relation N-N |
Actions FK disponibles : cascade, restrict, set_null, set_default (défaut : no_action).
belongs_togénère automatiquement uneFOREIGN KEYdans la migration. La colonne FK (fk_field) doit être déclarée dansfields.
Meta
Le bloc
metaest réservé aux futures versions (ordering, verbose_name, etc.). Il est parsé sans erreur mais ignoré.