Rustango docs
← Anleitungen

Modelle

Ein Modell ist ein Rust-Struct, das auf eine Datenbanktabelle abgebildet wird. Füge #[derive(Model)] hinzu, annotiere die Felder, und Rustango generiert das Schema, einen typsicheren Abfrage-Einstiegspunkt sowie save/find/delete-Methoden — Djangos Modelle oder Laravels Eloquent, mit dem Compiler, der deine Spalten prüft. Dies ist die Deklarations-Referenz: jeder Feldtyp, jede Primärschlüssel-Option und jedes #[rustango(...)]-Attribut. Für das Abfragen von Modellen, sobald sie deklariert sind, siehe das ORM-Kochbuch.

Modelle in Rustango: ein #[derive(Model)]-Struct bildet Rust-Feldtypen auf dialektspezifische Spalten ab, der Primärschlüssel kann ein auto-inkrementierender Auto<i64> oder ein benutzerdefinierter, anwendungsseitig zugewiesener Schlüssel sein, und das Derive generiert SCHEMA + objects() + save/find

Ein Begriff hier ist neu für dich? model, primary key, foreign key, migration, nullable — siehe das Glossar.

Quelle: rustango::Model (#[derive(Model)]), rustango::core (Model-Trait, ModelSchema, FieldType, Auto, ForeignKey) und die Dialekt-Typabbildungen in rustango::sql::{dialect, mysql, sqlite} — immer kompiliert (wähle ein Backend-Feature: postgres / mysql / sqlite).

Lauffähige Version: die Feldtyp-Round-Trips, der benutzerdefinierte PK und die SCHEMA-Snippets sind aus models_doc.rs kopiert (cargo test -p rustango --features sqlite --test models_doc).

Inhaltsverzeichnis


Anatomie eines Modells

use rustango::{Auto, Model};
use chrono::{DateTime, Utc};

#[derive(Model, Clone, Debug)]
#[rustango(table = "posts", display = "title")]   // model-level attributes
pub struct Post {
    #[rustango(primary_key)]
    pub id: Auto<i64>,                            // field-level attributes

    #[rustango(max_length = 200)]
    pub title: String,

    pub body: String,

    #[rustango(fk = "authors", on = "id")]
    pub author_id: i64,

    #[rustango(auto_now_add)]
    pub created_at: Auto<DateTime<Utc>>,
}

Aus dieser einen Deklaration generiert das Derive:

  • das Schema (Post::SCHEMA — Tabellenname, Spalten, Typen, den PK), das Migrationen und das Admin lesen;
  • einen Abfrage-Einstiegspunkt, Post::objects(), der ein QuerySet<Post> zurückgibt;
  • typisierte Feldkonstanten (Post::title, Post::author_id) für compilergeprüfte Filter — Post::objects().where_(Post::author_id.eq(42));
  • Zeilenmethoden — save, find, delete und mehr (siehe die generierte API).

Der Tabellenname ist standardmäßig der Modellname, wenn du table weglässt; Spaltennamen sind standardmäßig der snake_case-Feldname, sofern du nicht column setzt.


Feldtypen

Der Rust-Typ des Feldes bestimmt seinen Datenbank-Spaltentyp. Rustango bildet jeden Typ pro Dialekt ab, sodass dasselbe Modell auf PostgreSQL, MySQL und SQLite funktioniert:

Rust-TypPostgreSQLMySQLSQLite
i16SMALLINTSMALLINTINTEGER
i32INTEGERINTINTEGER
i64BIGINTBIGINTINTEGER
f32REALFLOATREAL
f64DOUBLE PRECISIONDOUBLEREAL
boolBOOLEANTINYINT(1)INTEGER (0/1)
StringTEXTTEXTTEXT
String + max_length = NVARCHAR(N)VARCHAR(N)TEXT
chrono::DateTime<Utc>TIMESTAMPTZDATETIME(6)TEXT (ISO-8601)
chrono::NaiveDateDATEDATETEXT
chrono::NaiveTimeTIMETIME(6)TEXT
uuid::UuidUUIDCHAR(36)TEXT
serde_json::ValueJSONBJSONTEXT
rust_decimal::DecimalNUMERICDECIMAL(38,10)NUMERIC
Vec<u8>BYTEALONGBLOBBLOB
Option<T>T NULLT NULLT (nullable)

Option<T> ist die Art, eine Spalte nullable zu machen — ein Feld ohne Option ist NOT NULL. All diese durchlaufen einen Round-Trip über savefind, durchgängig verifiziert:

#[derive(Model, Debug, Clone)]
#[rustango(table = "gadget")]
pub struct Gadget {
    #[rustango(primary_key)]
    pub id: Auto<i64>,
    #[rustango(max_length = 100)]
    pub name: String,
    pub qty: i64,
    pub active: bool,
    pub note: Option<String>,        // nullable
    pub made_at: DateTime<Utc>,
    pub meta: serde_json::Value,     // JSON
}

Dezimalpräzision. PostgreSQL NUMERIC ist beliebig genau; MySQL verwendet DECIMAL(38,10) (38 Stellen, 10 Nachkommastellen — die breiteste portable Passung); SQLite verwendet NUMERIC-Affinität. Verwende rust_decimal::Decimal für Geld, niemals f64.

Nur-PostgreSQL-Typen

Diese bilden auf native PostgreSQL-Spaltentypen ab und haben kein MySQL/SQLite- Äquivalent — der Migrations-Writer gibt dort TEXT aus, um gültig zu bleiben, aber das Lesen/Schreiben davon schlägt zur Laufzeit auf jenen Backends mit einem Fehler fehl. Verwende sie nur in PostgreSQL-Deployments:

Rust-TypPostgreSQLAnmerkungen
Array<T>text[] / integer[] / bigint[]native Arrays
Range<T>int4range / int8range / numrange / daterange / tstzrangeRange-Typen
HStorehstoreflache String→String-Map (benötigt die Extension)
Vector + #[rustango(vector(dims = N))]vector(N)pgvector-Embeddings
Point + #[rustango(geometry(srid = N))]geometry(Point, N)PostGIS

Primärschlüssel

Jedes Modell braucht einen Primärschlüssel. Markiere ein Feld mit #[rustango(primary_key)]; wenn du keines markierst, sucht das Schema nach einer Spalte namens id.

Der Standard und häufigste PK ist eine auto-inkrementierende 64-Bit-Ganzzahl, deklariert als Auto<i64>:

#[rustango(primary_key)]
pub id: Auto<i64>,

Auto<T>-Semantik. Ein Auto<T>-Feld ist entweder Unset (der Wert, den die DB zuweisen wird) oder Set(v). Beim Einfügen wird ein Unset-PK aus der Spaltenliste ausgelassen, sodass die Datenbank ihn generiert, dann wird der Wert zurückgelesen (RETURNING auf PostgreSQL/SQLite, LAST_INSERT_ID() auf MySQL) und auf deinem Struct gespeichert:

let mut g = Gadget { id: Auto::default(), /* … */ };   // Unset
g.save_pool(&pool).await?;                              // DB assigns the id
let new_id = g.id.get().copied().unwrap();             // now populated

Unterstützte innere Auto<T>-Typen sind i32, i64 und Uuid.

Benutzerdefinierte Primärschlüssel

Der PK muss keine auto-inkrementierende Ganzzahl sein. Jeder Typ, der auf eine Spalte abgebildet wird, kann der PK sein; du weist den Wert selbst zu:

PK-DeklarationSpaltentypWer weist ihn zu
Auto<i64> (Standard)BIGSERIAL / BIGINT AUTO_INCREMENT / INTEGER … AUTOINCREMENTDatenbank
Auto<i32>SERIAL / INT AUTO_INCREMENT / INTEGER …Datenbank
Auto<Uuid> + auto_uuidUUIDRust-seitig (uuid v4)
Auto<Uuid> + default_uuid_v7UUIDRust-seitig (sortierbares uuid v7)
String + primary_keyVARCHAR(N) / TEXTdu (Anwendung)
Uuid + primary_keyUUID / CHAR(36) / TEXTdu (Anwendung)
i64 / i32 + primary_keyBIGINT / INTEGERdu (Anwendung)

Ein natürlicher String-Schlüssel (z. B. ein Gutscheincode) — beachte, dass du den Wert selbst bereitstellst und mit insert_pool einfügst, da es kein Auto::Unset gibt, das save erkennen könnte:

#[derive(Model, Debug, Clone)]
#[rustango(table = "coupon")]
pub struct Coupon {
    #[rustango(primary_key, max_length = 32)]
    pub code: String,        // you assign this
    pub discount: i64,
}

let c = Coupon { code: "SAVE10".into(), discount: 10 };
c.insert_pool(&pool).await?;                       // explicit INSERT
let back = Coupon::find_or_fail("SAVE10".to_string(), &pool).await?;   // look up by the string PK

Benenne die PK-Spalte um mit column (das Rust-Feld bleibt number, die SQL-Spalte ist account_no):

#[rustango(primary_key, column = "account_no")]
pub number: i64,
// Introspect it via the schema:
let pk = Account::SCHEMA.primary_key().unwrap();
assert_eq!(pk.name, "number");        // Rust field
assert_eq!(pk.column, "account_no");  // SQL column

UUID-Primärschlüssel generieren den Wert Rust-seitig: auto_uuid gibt ein zufälliges v4, default_uuid_v7 ein zeitlich sortierbares v7 (besser für Index-Lokalität):

#[rustango(primary_key, auto_uuid)]
pub id: Auto<uuid::Uuid>,

Zusammengesetzte Primärschlüssel

Native mehrspaltige Primärschlüssel werden nicht unterstützt — genau ein Feld darf primary_key sein. Das ausgelieferte Muster ist ein Surrogat-Auto<i64>-PK plus ein deklarierter zusammengesetzter Unique-Constraint, was index-äquivalent ist:

#[derive(Model)]
#[rustango(table = "line_item", unique_together = "invoice_id, line_no")]
pub struct LineItem {
    #[rustango(primary_key)]
    pub id: Auto<i64>,        // surrogate PK
    pub invoice_id: i64,
    pub line_no: i32,         // (invoice_id, line_no) is unique together
}

Schlage Zeilen nach mit .where_(LineItem::invoice_id.eq(..)).where_(LineItem::line_no.eq(..)).


Beziehungen

Ein Fremdschlüssel ist eine _id-Spalte plus ein optionaler typisierter Accessor:

// Plain FK column — store the parent's id:
#[rustango(fk = "authors", on = "id")]
pub author_id: i64,

// Typed FK — lazy-loads the parent on demand:
pub author: ForeignKey<Author>,

ForeignKey<T> setzt seinen Schlüsseltyp standardmäßig auf i64; wenn der PK des Elternobjekts einen anderen Typ hat, benenne ihn: ForeignKey<User, String>. Eins-zu-eins verwendet #[rustango(o2o)]; viele-zu-viele ist eine separate Tabelle — siehe ORM-Kochbuch → Viele-zu-viele. Lade verwandte Zeilen eager mit select_related (ebenfalls im ORM-Leitfaden).


Übliche Feldattribute

#[rustango(...)] auf einem Feld. Die, die du ständig verwenden wirst:

AttributBeispielWirkung
primary_key#[rustango(primary_key)]markiert den PK
max_length = N#[rustango(max_length = 200)]VARCHAR(N) + Längenprüfung beim Schreiben
default = "…"#[rustango(default = "'draft'")]DB-Spaltenstandard (SQL-Literal)
unique#[rustango(unique)]Unique-Constraint auf der Spalte
choices = "…"#[rustango(choices = "draft:Draft, published:Published")]aufgezählte Werte (value:Label); Admin-<select> + Validierung
auto_now_add#[rustango(auto_now_add)]beim Einfügen auf jetzt setzen (auf einem Auto<DateTime<Utc>>)
auto_now#[rustango(auto_now)]bei jedem Speichern auf jetzt setzen
column = "…"#[rustango(column = "account_no")]die SQL-Spalte umbenennen
null / Option<T>pub note: Option<String>nullable Spalte
min / max#[rustango(min = 0, max = 100)]Bereichsvalidierung beim Schreiben
blank / editable#[rustango(editable = false)]Formular-/Admin-Verhalten
db_comment = "…"#[rustango(db_comment = "cents")]Spalten-COMMENT

choices, default, auto_now_add und Soft-Delete zusammen (alle verifiziert):

#[rustango(max_length = 20, default = "'draft'", choices = "draft:Draft, published:Published")]
pub status: String,
#[rustango(auto_now_add)]
pub created_at: Auto<DateTime<Utc>>,
#[rustango(soft_delete)]
pub deleted_at: Option<DateTime<Utc>>,

Indizes und Constraints

Auf dem Modell deklariert:

#[rustango(
    table = "posts",
    index("status, published_at"),                 // composite btree index
    unique_together = "author_id, slug",           // multi-column unique
    check(name = "qty_nonneg", expr = "qty >= 0"), // CHECK constraint
)]
  • index(...) — standardmäßig ein Btree-Index; wähle für PostgreSQL eine Methode mit index(columns = "body", method = "gin") (auch gist, brin, hash, bloom, spgist).
  • unique_together / index_together — mehrspaltig unique / nicht-unique.
  • Partielle Indizesunique_when(...) / index_when(...) fügen eine WHERE- Bedingung hinzu.
  • check(name, expr) — ein CHECK-Constraint; exclude(...) ist ein PostgreSQL-EXCLUDE-Constraint.

Übliche Modellattribute

#[rustango(...)] auf dem Struct:

AttributBeispielWirkung
table = "…"table = "posts"Tabellenname (standardmäßig der Struct-Name)
display = "…"display = "title"das Feld, das angezeigt wird, wenn eine Zeile referenziert wird (FK-Labels, Admin)
app = "…"app = "blog"gruppiert das Modell unter einer App
default_order = "…"default_order = "-created_at"Standardsortierung für Abfragen
default_permissionsdefault_permissions = "add, change"welche Auto-Berechtigungen erstellt werden
soft_delete (Feld)#[rustango(soft_delete)] deleted_at: Option<…>Soft-Delete aktivieren (markieren, nicht entfernen)
audit(track = "…")audit(track = "title, status")Änderungshistorie pro Zeile aufzeichnen
scope = "…"scope = "tenant"Multi-Tenancy-Scope (Registry vs. Mandant)
admin(...)admin(list_display = "…")Admin-UI-Konfiguration — siehe das Admin

Die generierte API

#[derive(Model)] implementiert das Model-Trait (Post::SCHEMA) und generiert:

  • Post::objects() (Alias Post::query()) → ein QuerySet<Post> zum Filtern, Ordnen und Abrufen (das ORM-Kochbuch deckt die Abfrage-API ab).
  • Typisierte FeldkonstantenPost::title, Post::author_id — verwendet in .where_(Post::author_id.eq(42)) für compilergeprüfte Filter.
  • Finderfind(pk, &pool)Option<Self>; find_or_fail(pk, &pool)Self (Fehler, wenn nicht vorhanden); find_many(pks, &pool); find_or_insert(...).
  • Writersave/save_pool, save_partial(&["title"], &pool) (nur einige Spalten aktualisieren), insert_pool (explizites Einfügen), delete.
  • Soft-Delete (wenn aktiviert) — soft_delete, restore, force_delete; QuerySet::active() / with_trashed() / only_trashed().

save vs insert

Das bringt Leute durcheinander, daher ist es die klare Aussage wert:

MethodeVerhalten
save_pool(&mut self, &pool)INSERT, wenn der Auto-PK Unset ist, andernfalls UPDATE
insert_pool(&self, &pool)immer INSERT

Für den Standard-Auto<i64>-PK macht save_pool automatisch das Richtige. Für einen anwendungsseitig zugewiesenen PK (ein String/Uuid, den du selbst setzt) gibt es keinen Unset-Zustand — daher würde save_pool eine (möglicherweise nicht existierende) Zeile UPDATEn. Verwende insert_pool, um eine brandneue Zeile mit einem benutzerdefinierten PK einzufügen (im begleitenden Test verifiziert).


Vollständige Attributreferenz

Jeder #[rustango(...)]-Schlüssel, den das Derive akzeptiert. Die üblichen sind oben behandelt; dies ist die vollständige Liste, einschließlich fortgeschrittener/PostgreSQL- spezifischer.

Auf Modellebene (auf dem Struct)

AttributWertWirkung
table"name"Tabellenname
display"field"menschenlesbares Label für eine Zeile
app"name"App-Gruppierung
default_order"-field"Standardsortierung
default_permissions"add, change, delete, view"zu erstellende Auto-Berechtigungen
default_related_name"posts"Name des Rückwärts-Accessors auf dem Elternobjekt
base_manager_name"all_objects"Name des Basis- (ungefilterten) Managers
manager(ext = "Trait")Trait-Pfadein benutzerdefiniertes Manager-Erweiterungs-Trait generieren
manager_fn"published"einen Manager-Accessor über objects() hinaus hinzufügen
get_latest_by"created_at"Standardspalte für latest()/earliest()
order_with_respect_to"parent"Django elternrelative Ordnung
index(...)columns, method, nameSekundärindex (btree/gin/gist/brin/hash/bloom/spgist)
unique_together"a, b"zusammengesetzter Unique-Constraint
index_together"a, b"zusammengesetzter Nicht-Unique-Index
unique_when(...) / index_when(...)Spalten + conditionpartieller (bedingter) Index
check(...)name, exprCHECK-Constraint
exclude(...)Operator-SpezifikationPostgreSQL-EXCLUDE-Constraint
audit(track = "…")FeldlisteÄnderungshistorie pro Zeile
scope"tenant" / "registry"Multi-Tenancy-Scope
proxyFlagProxy-Modell (teilt sich die Tabelle eines anderen)
global_scope(name, apply = fn)Name + fnFilter, der automatisch auf alle Abfragen angewendet wird
through(...)Relationsspezifikationbenutzerdefinierter Through-Relation-Accessor
reverse_has(...) / generic_has(...)RelationsspezifikationRückwärts-has-many / Rückwärts-generischer-FK-Accessor
required_db_features / required_db_vendorListe / VendorDeployment-Validierungs-Constraints
db_table_comment"…"Tabellen-COMMENT
admin(...)Admin-OptionenAdmin-UI-Konfiguration (siehe admin.md)

Auf Feldebene (auf einem Feld)

AttributWertWirkung
primary_keyFlagmarkiert den PK
column"name"die SQL-Spalte umbenennen
max_lengthNVARCHAR(N) + Längenvalidierung
default"sql literal"Spalten-DEFAULT
nullFlagnullable (oder verwende Option<T>)
uniqueFlagUnique-Constraint
choices"v:Label, …"aufgezählte Werte
min / maxZahlBereichsvalidierung
blankFlagLeereingabe in Formularen/Admin erlauben
editabletrue/falseBearbeitbarkeit in Formular/Admin
auto_nowFlagbei jedem Speichern auf jetzt setzen
auto_now_addFlagbeim Einfügen auf jetzt setzen
auto_uuidFlagRust-seitiges UUID v4 (auf Auto<Uuid>)
default_uuid_v7FlagRust-seitiges sortierbares UUID v7
fk + on"table", "col"Fremdschlüsselspalte
cascadeFlagON DELETE CASCADE
o2oFlagEins-zu-eins-Beziehung
fk_composite(...) / generic_fk(...)Spezifikationzusammengesetzter FK / generischer (Content-Type-) FK
generated_as"expr"DB-berechnete (generierte) Spalte
citextFlagcase-insensitiver Text (PostgreSQL CITEXT)
vector(dims = N)Npgvector-Dimension
geometry(srid = N)NPostGIS-Raumbezugs-ID
db_comment"…"Spalten-COMMENT

Siehe auch

  • ORM-Kochbuch — Abfragen, Filter, Aggregationen, Joins, Transaktionen (was mit einem Modell zu tun ist, sobald es deklariert ist).
  • Serializer — ein Modell für eine API in JSON formen.
  • Das Admin — der admin(...)-Block und die generierte UI.
  • Scaffolding · manage-CLI — ein Modell und seine Migration generieren.