Skip to main content

Model

Trait Model 

Source
pub trait Model:
    Sized
    + Send
    + Sync
    + Unpin
    + 'static {
    type PrimaryKey: PrimaryKey;
Show 15 associated constants and 1 method const NAME: &'static str; const TABLE: &'static str; const FIELDS: &'static [FieldSpec]; const APP_LABEL: &'static str = "app"; const DISPLAY: &'static str = Self::NAME; const ICON: &'static str = "database"; const DATABASE: Option<&'static str> = None; const SINGLETON: bool = false; const SOFT_DELETE: bool = false; const UNIQUE_TOGETHER: &'static [&'static [&'static str]] = _; const INDEXES: &'static [&'static [&'static str]] = _; const ORDERING: &'static [(&'static str, bool)] = _; const M2M_RELATIONS: &'static [M2MRelationSpec] = _; const REVERSE_FK_RELATIONS: &'static [ReverseFkRelationSpec] = _; const ONE_TO_ONE_RELATIONS: &'static [OneToOneRelationSpec] = _; // Required method fn primary_key(&self) -> Self::PrimaryKey;
}
Expand description

The trait every model implements.

Read at runtime to build queries (T::TABLE, T::FIELDS), at boot to validate field/backend compatibility (M4), and at migration time to diff against the last snapshot (M5).

Model is metadata-only — it carries no row-materialization bound. QuerySet terminals add for<'r> FromRow<'r, R> for the row type they need at the call site (sqlite or postgres). User structs pick up both impls via a single #[derive(sqlx::FromRow)] because sqlx’s derive emits a generic-over-R impl.

Required Associated Constants§

Source

const NAME: &'static str

The struct name, used by the migration engine (M5) to label snapshot entries and to map autodetected operations back to the model that produced them. The M3 derive emits the struct ident verbatim (“Post”, “Comment”, etc.).

Source

const TABLE: &'static str

The SQL table name. M3’s derive defaults this to the snake_case of the struct name unless #[umbral(table = "...")] overrides it.

Source

const FIELDS: &'static [FieldSpec]

Static metadata for every field on the model.

One FieldSpec per field, in declaration order. Read by the QuerySet (to build the SELECT column list), by the system check (M4) for field/backend compatibility, and by the migration engine (M5) for snapshot diffing.

Provided Associated Constants§

Source

const APP_LABEL: &'static str = "app"

The app label (the owning plugin’s name) this model belongs to.

Sourced from #[umbral(plugin = "...")]; defaults to "app" (the registry’s default key) when the attribute is absent. Authoritative for permission codenames (gaps2 #80g): umbral-permissions reads this to build <app_label>.<verb>_<model> codenames, instead of splitting the table name at the first _ (which collided distinct models).

Source

const DISPLAY: &'static str = Self::NAME

Human-readable display name for this model, used by the admin sidebar as the default label. Defaults to Self::NAME.

Override via #[umbral(display = "Users")] on the struct.

Source

const ICON: &'static str = "database"

Lucide icon slug shown next to this model in the admin sidebar. Defaults to "database". Any valid Lucide icon name works; unknown names are silently ignored by Lucide at render time.

Override via #[umbral(icon = "users")] on the struct.

Source

const DATABASE: Option<&'static str> = None

Database alias this model lives on, when the app registers more than one pool via AppBuilder::database(...). None (the default) means “use whatever the owning plugin chose via Plugin::database(), or \"default\" if neither side overrode.”

Override via #[umbral(database = "analytics")] on the struct. Per-model wins over per-plugin — useful for a single plugin that owns one model on the primary DB and another on an archive/analytics DB.

Source

const SINGLETON: bool = false

Single-row-marker. When true, the admin auto-redirects the list view to the (sole) row’s edit form, hides the “+ New” button, and surfaces the model as a settings-style screen. The single-row settings model pattern. Set via #[umbral(singleton)] on the struct. Closes BUG-9 in bugs/tests/testBugs.md.

Default false. Default-row seeding (so the first admin visit doesn’t 404) is the user’s responsibility — typically a one-liner in Plugin::on_ready that calls T::objects().create(T::default()).await if the count is zero. A future framework helper could automate that; for v1 the trait const is enough to let admin and any third-party tool know the model is singleton-shaped.

Source

const SOFT_DELETE: bool = false

Feature #72 — soft-delete marker. Set via #[umbral(soft_delete)] on the struct. When true, the framework treats this model as having a deleted_at: Option<DateTime<Utc>> column (which the user MUST declare — derive macros can’t add fields to the input struct), and:

  • Every QuerySet<T> terminal auto-injects WHERE deleted_at IS NULL so soft-deleted rows are invisible by default.
  • Manager::delete_instance(&row) and QuerySet::delete() issue UPDATE table SET deleted_at = NOW() WHERE ... instead of a hard DELETE FROM table WHERE ....
  • Callers who actually want the soft-deleted rows (admin trash views, audit dumps, undelete flows) opt back in per-query via .with_deleted() or .only_deleted().
  • Callers who need a hard DELETE (GDPR purge, etc.) use .hard_delete() to bypass the soft path on a per-call basis.

Default false so existing models compile unchanged.

Source

const UNIQUE_TOGETHER: &'static [&'static [&'static str]] = _

Composite-UNIQUE constraints. Each inner slice names a constraint over the listed column names. Set via #[umbral(unique_together = [["a", "b"]])]. Closes BUG-6 in bugs/tests/testBugs.md. Default empty; the migration engine emits one UNIQUE (col1, col2) clause per inner group on CREATE TABLE.

Source

const INDEXES: &'static [&'static [&'static str]] = _

Multi-column indexes. Each inner slice names an index over the listed columns. Set via #[umbral(indexes = [["tenant_id", "created_at"]])]. Closes BUG-7. Default empty; the migration engine emits CREATE INDEX IF NOT EXISTS idx_<table>_<col1>_<col2> after the CREATE TABLE. Single-column indexes stay on the field attribute (#[umbral(index)]).

Source

const ORDERING: &'static [(&'static str, bool)] = _

Default ORDER BY clause, applied when a QuerySet terminates without an explicit order_by. Each tuple is (column_name, is_descending). Set via #[umbral(ordering = ["-published_at", "id"])] (leading - flips to DESC). Closes BUG-8. Default empty.

Source

const M2M_RELATIONS: &'static [M2MRelationSpec] = _

Many-to-many relations declared on this model. Each entry names a field and its target model. The migration engine uses this to auto-generate junction tables; the admin uses it to render M2M pickers. Default empty.

Source

const REVERSE_FK_RELATIONS: &'static [ReverseFkRelationSpec] = _

Gap #44 — reverse-FK collections declared on this model via #[umbral(reverse_fk = "<fk_col>")] pub <name>: ReverseSet<C>. Each entry tells prefetch_related how to fetch the children: SELECT * FROM <target_table> WHERE <fk_column> IN (parent_pks) then group by <fk_column> value, populate each parent’s ReverseSet.resolved. Default empty; the macro emits one entry per declared ReverseSet<C> field.

Source

const ONE_TO_ONE_RELATIONS: &'static [OneToOneRelationSpec] = _

Reverse OneToOne accessors declared on this model via pub <name>: OneToOne<C> (no umbral attribute required). Unlike REVERSE_FK_RELATIONS, the FK column on the child is not named at macro time — prefetch_related looks it up at runtime by scanning the child’s FIELDS for the UNIQUE FK pointing back at this model’s table. Exactly one match required; 0 or 2+ matches surface a loud error naming the ambiguity.

Required Associated Types§

Source

type PrimaryKey: PrimaryKey

The primary-key type. M2 supports i64 only; UUID lands later.

Required Methods§

Source

fn primary_key(&self) -> Self::PrimaryKey

Return the primary key of this instance.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§

Source§

impl Model for Post

The hand-written Model impl. M3 will generate this exact impl from #[derive(Model)] on the struct above.

Post::TABLE and Post::FIELDS are reached via the trait (not as inherent consts); call sites use <Post as Model>::TABLE or just Post::TABLE when the trait is in scope.

Source§

const NAME: &'static str = "Post"

Source§

const TABLE: &'static str = "post"

Source§

const FIELDS: &'static [FieldSpec]

Source§

type PrimaryKey = i64