Defining Models
A Rango model is a regular Rust struct annotated with #[derive(Model)].
#![allow(unused)] fn main() { use rango::prelude::*; #[derive(Model)] pub struct Article { pub id: FieldUuid, pub title: FieldVarchar<1, 255>, pub body: FieldText, pub published: FieldBool, pub created_at: FieldDateTime, } }
A model is a normal struct, so it can derive other traits next to Model, for example #[derive(Model, Clone, Debug)]. A struct with a FieldPassword field prints <redacted> for it in Debug. FieldPassword::new, verify and from_hash are documented in Field types.
Table name
By default, the table name is the snake_case of the struct name. Tables are never prefixed.
Article → article
Override with #[model(table = "...")]:
#![allow(unused)] fn main() { #[derive(Model)] #[model(table = "cms_articles")] pub struct Article { ... } }
Primary key
Declare a field named id — it becomes the primary key automatically:
#![allow(unused)] fn main() { pub id: FieldUuid, // UUID primary key (recommended) pub id: FieldAutoBigInt, // Integer generated by the database }
FieldAutoInt / FieldAutoBigInt hold None until the row is inserted; the database assigns the id and insert returns the model with Some(id). Build new rows with FieldAutoBigInt::default(). bulk_create takes models that all have an id, or none. ForeignKey<M> and ManyToMany<M> follow the primary key type of the target model (FieldUuid, FieldInt, FieldAutoInt, FieldBigInt or FieldAutoBigInt). Text or composite keys cannot be the target of a relation.
Or use #[field(primary_key)] for a custom name:
#![allow(unused)] fn main() { #[field(primary_key)] pub uuid: FieldUuid, }
Unsupported field types
Fields use Rango's Field* types, not bare Rust types. The derive points at the offending type and names the replacement:
error: `String` is not a Rango field type: use `FieldText` or `FieldVarchar<MIN, MAX>`. ...
--> src/models.rs:6:11
|
6 | name: String,
| ^^^^^^
| Rust type | Rango type |
|---|---|
String | FieldText or FieldVarchar<MIN, MAX> |
Vec<u8> | FieldBytes |
bool | FieldBool |
i16 / i32 / i64 | FieldSmallInt / FieldInt / FieldBigInt |
f32 / f64 | FieldFloat / FieldDouble |
Uuid | FieldUuid (ForeignKey<M> for a reference) |
DateTime<Utc> / NaiveDate / NaiveTime | FieldDateTime / FieldDate / FieldTime |
serde_json::Value | FieldJson |
Wrapping any of them in Option<..> makes the column nullable.
Building a model value
There is no generated constructor: the fields are the API, so every field is written out and the compiler tells you which one is missing. Field types convert from their Rust equivalent, so .into() is the short form, and new(..) validates (FieldVarchar, FieldEmail, FieldUrl):
#![allow(unused)] fn main() { let article = Article { id: uuid::Uuid::new_v4().into(), title: FieldVarchar::new("Hello")?, body: "text".into(), published: true.into(), created_at: chrono::Utc::now().into(), }; }
ForeignKey<M> and ManyToMany<M> are Clone / Debug (and ManyToMany is Default) whatever M is: M does not need to implement them. ForeignKey<M> is also Copy, Eq and Hash. ManyToMany::default() is an empty relation.
Nullable fields
Use Option<T> for nullable columns:
#![allow(unused)] fn main() { pub bio: Option<FieldText>, // NULL allowed pub score: Option<FieldInt>, // NULL allowed pub name: FieldVarchar<1, 100>, // NOT NULL }
Field attributes
#![allow(unused)] fn main() { #[field(unique)] // UNIQUE constraint #[field(index)] // creates an index #[field(column = "custom_name")] // override column name #[field(primary_key)] // mark as primary key #[field(auto_now_add)] // set to now() on INSERT (FieldDateTime only) #[field(auto_now)] // update to now() on every UPDATE #[field(default = "0")] // SQL default value }
Model attributes
#![allow(unused)] fn main() { #[model(table = "custom_name")] #[model(managed = false)] // exclude from migrations (external table) #[model(ordering = [asc("name"), desc("created_at")])] #[model(constraints = [ CheckConstraint::new("age >= 0").name("chk_age"), UniqueConstraint::on(&["email", "tenant_id"]).name("uniq_email_tenant"), ])] }