Skip to main content

Model

Trait Model 

Source
pub trait Model:
    FromRow
    + Sized
    + Send
    + Sync
    + Unpin
    + 'static {
    type Key: ModelKey;

    const TABLE: &'static str;
    const COLUMNS: &'static [&'static str];
    const SOFT_DELETES: bool = false;
    const SELECT_ALL: bool = false;
    const SEARCHABLE: &'static [&'static str] = _;
    const SEARCH_LANGUAGE: &'static str = "english";
Show 26 methods // Required method fn id(&self) -> Self::Key; // Provided methods fn replicate(&self) -> Self where Self: Clone { ... } fn default_scope(query: Query<Self>) -> Query<Self> { ... } fn saving(&mut self, _creating: bool) -> Result { ... } fn saved(&self, _created: bool) -> impl Future<Output = Result> + Send { ... } fn deleting(&self) -> Result { ... } fn deleted(&self) -> impl Future<Output = Result> + Send { ... } fn query() -> Query<Self> { ... } fn unscoped() -> Query<Self> { ... } fn refresh(&mut self, db: &Db) -> impl Future<Output = Result<()>> + Send { ... } fn search(words: &str) -> Query<Self> { ... } fn where_eq(column: &str, value: impl ToDbValue) -> Query<Self> { ... } fn all<'c, E: Executor<'c>>( db: E, ) -> impl Future<Output = Result<Vec<Self>>> + Send { ... } fn find<'c, E: Executor<'c>>( db: E, id: Self::Key, ) -> impl Future<Output = Result<Option<Self>>> + Send { ... } fn find_many<'c, E: Executor<'c>>( db: E, ids: impl IntoIterator<Item = Self::Key>, ) -> impl Future<Output = Result<Vec<Self>>> + Send { ... } fn insert_many<'c, E: Executor<'c>>( db: E, models: Vec<Self>, ) -> impl Future<Output = Result<u64>> + Send { ... } fn upsert<'c, E: Executor<'c>>( db: E, models: Vec<Self>, unique_by: &[&str], update: &[&str], ) -> impl Future<Output = Result<u64>> + Send { ... } fn find_or_404<'c, E: Executor<'c>>( db: E, id: Self::Key, ) -> impl Future<Output = Result<Self>> + Send { ... } fn create<'c, E: Executor<'c>>( db: E, model: Self, ) -> impl Future<Output = Result<Self>> + Send { ... } fn insert<'c, E: Executor<'c>>( &mut self, db: E, ) -> impl Future<Output = Result> + Send { ... } fn save<'c, E: Executor<'c>>( &mut self, db: E, ) -> impl Future<Output = Result> + Send { ... } fn save_only<'c, E: Executor<'c>>( &mut self, db: E, columns: &[&str], ) -> impl Future<Output = Result> + Send { ... } fn save_changes<'c, E: Executor<'c>>( &mut self, db: E, original: &Self, ) -> impl Future<Output = Result<bool>> + Send { ... } fn delete<'c, E: Executor<'c>>( &mut self, db: E, ) -> impl Future<Output = Result> + Send { ... } fn force_delete<'c, E: Executor<'c>>( &self, db: E, ) -> impl Future<Output = Result> + Send { ... } fn restore<'c, E: Executor<'c>>( &mut self, db: E, ) -> impl Future<Output = Result> + Send { ... }
}
Expand description

A struct stored as a row in a table. Derive it with #[derive(Model)].

#[derive(Model, Serialize, Default)]
#[model(table = "products", soft_deletes)]
struct Product {
    id: i64,
    name: String,
    price: i64,
    created_at: Option<DateTime>,
    updated_at: Option<DateTime>,
    deleted_at: Option<DateTime>,
}

let mut coffee = Product { name: "Coffee".into(), price: 18_000, ..Default::default() };
coffee.save(&db).await?;                     // INSERT, sets id and timestamps
let cheap = Product::query().where_op("price", "<", 20_000).get(&db).await?;

Implement it with #[derive(Model)]: the hidden items it writes may change in a minor release.

The primary key is the id column. Its type is the id field’s: i64 (numbered by the database; 0 means “not saved yet”), or a Ulid, a UUID or a String (see ModelKey).

Required Associated Constants§

Source

const TABLE: &'static str

The table name (the snake_case struct name unless #[model(table = …)]).

Source

const COLUMNS: &'static [&'static str]

Every column, including id.

Provided Associated Constants§

Source

const SOFT_DELETES: bool = false

delete() sets deleted_at instead of removing the row, and queries skip deleted rows unless asked with with_trashed() / only_trashed().

Source

const SELECT_ALL: bool = false

Select every column (*) instead of COLUMNS, so from_row also sees columns the struct doesn’t list, and queries may filter on them. The built-in User does this to keep the app’s own columns.

Source

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

The text columns full-text search looks in, most important first (they weigh more in the ranking); empty: the model can’t be searched. Set it with #[model(search = "title, body")]; see renox::db::search.

Source

const SEARCH_LANGUAGE: &'static str = "english"

The language full-text search stems words for: english (the default) or a PostgreSQL text search configuration such as simple (no stemming) or spanish. Set it with #[model(search_language = "simple")].

Required Associated Types§

Source

type Key: ModelKey

The type of the id field.

Required Methods§

Source

fn id(&self) -> Self::Key

The primary key.

Provided Methods§

Source

fn replicate(&self) -> Self
where Self: Clone,

A copy of the model that isn’t saved yet (Laravel’s replicate): the same values, with an unsaved id, no deleted_at and, when they are Options, no timestamps, so save inserts a new row. Change what must differ (a unique SKU, a name) before saving it, or show it in the “new” form for someone to finish (“Duplicate”).

let original = Product::find_or_404(&db, 1).await?;
let mut copy = original.replicate();
copy.sku = format!("{}-COPY", original.sku);
copy.save(&db).await?; // a new row, with its own id
Source

fn default_scope(query: Query<Self>) -> Query<Self>

Conditions every query of this model starts with, e.g. the current tenant, read from renox::context. query(), find, all, where_eq and the relation loaders apply it; Model::unscoped doesn’t. Saving, deleting and restoring a loaded model work by its id. Set it with #[model(default_scope = "…")]:

#[derive(Clone)]
struct CurrentTeam(i64);

#[derive(Model, serde::Serialize, Default)]
#[model(table = "projects", default_scope = "team_only")]
struct Project { id: i64, team_id: i64, name: String }

fn team_only(query: renox::db::Query<Project>) -> renox::db::Query<Project> {
    match renox::context::get::<CurrentTeam>() {
        Some(team) => query.where_eq("team_id", team.0),
        None => query.none(), // no team, no rows: fail closed
    }
}
Source

fn saving(&mut self, _creating: bool) -> Result

Runs before the row is written; an error stops the save. Implement ModelHooks and add #[model(hooks)] rather than overriding it.

Source

fn saved(&self, _created: bool) -> impl Future<Output = Result> + Send

Runs after the row is written (inside the caller’s transaction, if any); an error is returned by save. See ModelHooks.

Source

fn deleting(&self) -> Result

Runs before delete/force_delete; an error stops it. See ModelHooks.

Source

fn deleted(&self) -> impl Future<Output = Result> + Send

Runs after delete/force_delete. See ModelHooks.

Source

fn query() -> Query<Self>

A query with the default scope applied (see Model::default_scope).

Source

fn unscoped() -> Query<Self>

A query without the default scope, e.g. for an admin who sees every tenant. Soft-deleted rows stay hidden unless asked for.

Source

fn refresh(&mut self, db: &Db) -> impl Future<Output = Result<()>> + Send

Reloads the model’s row (e.g. after an increment or another request changed it); a deleted row is a 404.

Source

fn search(words: &str) -> Query<Self>

The rows matching a full-text search, best matches first: shorthand for query().search(words). The model needs #[model(search = "…")] and its index (see renox::db::search).

#[derive(Model, serde::Serialize, Default)]
#[model(table = "posts", search = "title, body")]
struct Post { id: i64, title: String, body: String }

let posts = Post::search(&q).limit(20).get(&db).await?;
Source

fn where_eq(column: &str, value: impl ToDbValue) -> Query<Self>

Shorthand for query().where_eq(column, value).

Source

fn all<'c, E: Executor<'c>>( db: E, ) -> impl Future<Output = Result<Vec<Self>>> + Send

Every row, in id order.

Source

fn find<'c, E: Executor<'c>>( db: E, id: Self::Key, ) -> impl Future<Output = Result<Option<Self>>> + Send

The row with this id, or None.

Source

fn find_many<'c, E: Executor<'c>>( db: E, ids: impl IntoIterator<Item = Self::Key>, ) -> impl Future<Output = Result<Vec<Self>>> + Send

The rows with these ids, in id order (missing ids are skipped).

Source

fn insert_many<'c, E: Executor<'c>>( db: E, models: Vec<Self>, ) -> impl Future<Output = Result<u64>> + Send

Inserts many new models with a few statements (ids aren’t returned; use create when you need them). Timestamps are set; unsaved ULID and UUID keys are made, String keys must be set, i64 keys come from the database. Returns the number of rows inserted.

Source

fn upsert<'c, E: Executor<'c>>( db: E, models: Vec<Self>, unique_by: &[&str], update: &[&str], ) -> impl Future<Output = Result<u64>> + Send

Inserts models, or updates the rows they clash with on the unique_by columns (which need a unique index), setting update columns (and updated_at when the model has it). Returns the rows written.

let feed = vec![Stock { sku: "COFFEE-1".into(), qty: 12, ..Default::default() }];
Stock::upsert(&db, feed, &["sku"], &["qty"]).await?;
Source

fn find_or_404<'c, E: Executor<'c>>( db: E, id: Self::Key, ) -> impl Future<Output = Result<Self>> + Send

Like find, but a missing row becomes a 404 response.

Source

fn create<'c, E: Executor<'c>>( db: E, model: Self, ) -> impl Future<Output = Result<Self>> + Send

Inserts a new model and returns it with its id and timestamps.

Source

fn insert<'c, E: Executor<'c>>( &mut self, db: E, ) -> impl Future<Output = Result> + Send

Inserts the model as a new row, whatever its id: an unsaved id gets a new key (from the database for i64, a new ULID or UUID v7), a set one is written as it is (a String key must be set).

Source

fn save<'c, E: Executor<'c>>( &mut self, db: E, ) -> impl Future<Output = Result> + Send

Inserts the model if its id is unsaved (0, an empty ULID…), otherwise updates its row.

Source

fn save_only<'c, E: Executor<'c>>( &mut self, db: E, columns: &[&str], ) -> impl Future<Output = Result> + Send

Updates only columns (and updated_at, if the model has it) of a saved model, so a concurrent change to another column isn’t overwritten. A column the saving hook changes is saved only if it’s listed.

post.title = "New title".into();
post.save_only(&db, &["title"]).await?; // leaves `views` alone
Source

fn save_changes<'c, E: Executor<'c>>( &mut self, db: E, original: &Self, ) -> impl Future<Output = Result<bool>> + Send

Saves the columns that differ from original (the model as it was loaded), including those the saving hook changes, and returns whether anything was written. When nothing changed there’s no query and no saved hook.

let original = Post::find_or_404(&db, 1).await?;
let mut post = original.clone();
post.title = "New title".into();
post.save_changes(&db, &original).await?; // UPDATE posts SET title = ?
Source

fn delete<'c, E: Executor<'c>>( &mut self, db: E, ) -> impl Future<Output = Result> + Send

Deletes the row, or marks it deleted for models with soft deletes.

Source

fn force_delete<'c, E: Executor<'c>>( &self, db: E, ) -> impl Future<Output = Result> + Send

Removes the row, even for models with soft deletes.

Source

fn restore<'c, E: Executor<'c>>( &mut self, db: E, ) -> impl Future<Output = Result> + Send

Brings back a soft-deleted row.

Dyn Compatibility§

This trait is not dyn compatible.

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

Implementors§

Source§

impl Model for User

Source§

const TABLE: &'static str = "users"

Source§

const SELECT_ALL: bool = true

Source§

const COLUMNS: &'static [&'static str]

Source§

type Key = i64