Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Data access

The panel loads rows on its own pages. This chapter covers querying Toasty from your code: a page, a record function, or a public page. The Toasty guide covers the query API in full.

Getting the database

app_context(db) registers the Db on the app’s router, where every panel mounted on it reads it; tablo_core::db::db(cx) returns it. Cloning a Db is cheap, and statements take it by &mut:

#![allow(unused)]
fn main() {
    let mut db = tablo_core::db::db(cx);
    let users = User::all().exec(&mut db).await?;
}

Inside a record function, run statements through the transaction you were handed (ex), never through a second handle: a single-connection pool such as sqlite::memory: would wait forever for the connection the transaction holds.

Querying

#![allow(unused)]
fn main() {
    User::filter(User::fields().email().eq("ada@example.com"));
    User::filter(User::fields().name().starts_with(prefix)).order_by(User::fields().name().asc());
}

Toasty binds values as parameters. If you build a LIKE pattern from user input, escape %, _ and your escape character first and pass it with like_with_escape, as the table search does.

Load a resource’s rows through scoped_query. See Tenancy.

A public page has no resource behind it, so it states its own filters, tenant included:

#![allow(unused)]
fn main() {
    let posts = Post::filter(Post::fields().status().eq("published".to_string()))
        .include(Post::fields().author())
        .exec(&mut db)
        .await?;
}

Relations

Toasty loads a relation only when the query includes it. Include every relation you read, in the same query:

#![allow(unused)]
fn main() {
    let posts = Post::all()
        .include(Post::fields().author())
        .exec(&mut db)
        .await?;
    for post in &posts {
        let _name = &post.author.get().name; // no extra query
    }
}

Reading a relation that was not included panics in get(); check is_unloaded() first where a missing include is possible. In a table column, declare the relation with ComputedColumn::include instead: see Tables.

A resource’s table on your own page

A page can render a resource’s list table over its own query — here, only featured posts — with the same columns, filters and row actions as the resource’s list:

#![allow(unused)]
fn main() {
    let table = tablo_core::panel::wired_table::<PostResource>(cx)?;
    let state = TableState::from_cx(cx);
    let query = scoped_query::<PostResource>(cx)?.filter(Post::fields().featured().eq(true));
    let page = TablePage::load(cx, &table, query, &state).await?;
    let body = table
        .render_with_state(cx, page, &state, "/admin/featured")
        .await?;
}

wired_table is the table the request’s panel mounted for the resource, with the row actions its policy allows; it returns an error when that panel does not mount the resource. The last argument of render_with_state is the URL the table’s search, sort and pager links point at: the page’s own.

Schema setup

db.push_schema().await? creates every registered table, which suits a prototype or a test. A production app manages its schema with toasty-cli migrations.

Rendering rules

Topcoat may render a page’s regions concurrently and re-render them in place, so code that renders follows three rules:

  • No side effects. Pages, layouts and components only read. Writes belong in record functions or app routes.
  • Deterministic output. No HashMap iteration order, current time or random values in a region that re-renders: a re-render must produce the same markup for the same data.
  • Untrusted signals. A value read on the server with a signal’s get() or read() comes from the client. Validate it like any request input.

Share a query between components of one request with Topcoat’s #[memoize], and add a Toasty #[index] to columns you filter on.

A table with live_search() refreshes through a Topcoat shard request. Page and layout guards do not run for shard requests, so the panel’s shard checks authentication, the tenant and ViewAny itself; a shard you write starts with auth::guard(cx)?, and checks can_list::<R>(cx) before it lists R’s rows.