Embed

Derive Macro Embed 

Source
#[derive(Embed)]
{
    // Attributes available to this derive:
    #[column]
    #[index]
    #[unique]
}
Expand description

Derive macro that turns a struct or enum into an embedded type stored inline in a parent model’s table.

Embedded types do not have their own tables or primary keys. Their fields are flattened into the parent model’s columns. Use Embed for value objects (addresses, coordinates, metadata) and enums (status codes, contact info variants).

§Structs

An embedded struct’s fields become columns in the parent table, prefixed with the field name. For example, an address: Address field with street and city produces columns address_street and address_city.

#[derive(toasty::Embed)]
struct Address {
    street: String,
    city: String,
}

#[derive(toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: i64,
    name: String,
    address: Address,
}

Applying #[derive(Embed)] to a struct generates:

  • An Embed trait implementation (which extends Register).
  • A Fields struct returned by <Type>::fields() for building filter expressions on individual fields.
  • An Update struct used by the parent model’s update builder for partial field updates.

§Nesting

Embedded structs can contain other embedded types. Columns are flattened with chained prefixes:

#[derive(toasty::Embed)]
struct Location {
    lat: i64,
    lon: i64,
}

#[derive(toasty::Embed)]
struct Address {
    street: String,
    city: Location,
}

When Address is embedded as address in a parent model, this produces columns address_street, address_city_lat, and address_city_lon.

§Enums

An embedded enum stores a discriminant value identifying the active variant. Each variant must have a #[column(variant = N)] attribute assigning a stable integer discriminant.

Unit-only enum:

#[derive(toasty::Embed)]
enum Status {
    #[column(variant = 1)]
    Pending,
    #[column(variant = 2)]
    Active,
    #[column(variant = 3)]
    Archived,
}

A unit-only enum occupies a single column in the parent table. The column stores the discriminant as an integer.

Data-carrying enum:

#[derive(toasty::Embed)]
enum ContactInfo {
    #[column(variant = 1)]
    Email { address: String },
    #[column(variant = 2)]
    Phone { number: String },
}

A data-carrying enum stores the discriminant column plus one nullable column per variant field. For example, a contact: ContactInfo field produces columns contact (discriminant), contact_address, and contact_number. Only the columns belonging to the active variant contain values; the rest are NULL.

Mixed enum (unit and data variants together):

#[derive(toasty::Embed)]
enum Status {
    #[column(variant = 1)]
    Pending,
    #[column(variant = 2)]
    Failed { reason: String },
    #[column(variant = 3)]
    Done,
}

Applying #[derive(Embed)] to an enum generates:

  • An Embed trait implementation (which extends Register).
  • A Fields struct with is_<variant>() methods and comparison methods (eq, ne, in_list).
  • For data-carrying variants, per-variant handle types with a matches(closure) method for pattern matching and field access.

§Field-level attributes

§#[column(...)] — customize the database column

On struct fields, overrides the column name and/or type:

#[derive(toasty::Embed)]
struct Address {
    #[column("addr_street")]
    street: String,

    #[column(type = varchar(255))]
    city: String,
}

See Model for the full list of supported column types.

On enum variants, #[column(variant = N)] is required and assigns the integer discriminant stored in the database:

#[column(variant = 1)]
Pending,

Discriminant values must be unique across all variants of the enum. They are stored as i64.

§#[index] — add a database index

Creates a non-unique index on the field’s flattened column.

#[derive(toasty::Embed)]
struct Contact {
    #[index]
    country: String,
}

§#[unique] — add a unique constraint

Creates a unique index on the field’s flattened column. The database enforces uniqueness.

#[derive(toasty::Embed)]
struct Contact {
    #[unique]
    email: String,
}

§Using embedded types in a model

Reference an embedded type as a field on a Model struct. The parent model’s create and update builders gain a setter for the embedded field. For embedded structs, a with_<field> method supports partial updates of individual sub-fields:

// Full replacement
user.update()
    .address(Address { street: "456 Oak Ave".into(), city: "Seattle".into() })
    .exec(&mut db).await?;

// Partial update (struct only) — updates city, leaves street unchanged
user.update()
    .with_address(|a| { a.city("Portland"); })
    .exec(&mut db).await?;

Embedded struct fields are queryable through the parent model’s fields() accessor:

let users = User::filter(User::fields().address().city().eq("Seattle"))
    .exec(&mut db).await?;

§Constraints

  • Embedded structs must have named fields (tuple structs are not supported).
  • Generic parameters are not supported.
  • Every enum variant must have a #[column(variant = N)] attribute with a unique discriminant value.
  • Enum variants may be unit variants or have named fields. Tuple variants are not supported.
  • Embedded types cannot have primary keys, relations, #[auto], #[default], #[update], or #[serialize] attributes.

§Full example

#[derive(Debug, PartialEq, toasty::Embed)]
enum Priority {
    #[column(variant = 1)]
    Low,
    #[column(variant = 2)]
    Normal,
    #[column(variant = 3)]
    High,
}

#[derive(Debug, toasty::Embed)]
struct Metadata {
    version: i64,
    status: String,
    priority: Priority,
}

#[derive(Debug, toasty::Model)]
struct Document {
    #[key]
    #[auto]
    id: i64,

    title: String,

    #[unique]
    slug: String,

    meta: Metadata,
}

// Create
let mut doc = Document::create()
    .title("Design doc")
    .slug("design-doc")
    .meta(Metadata {
        version: 1,
        status: "draft".to_string(),
        priority: Priority::Normal,
    })
    .exec(&mut db).await?;

// Query by embedded field
let drafts = Document::filter(
    Document::fields().meta().status().eq("draft")
).exec(&mut db).await?;

// Partial update
doc.update()
    .with_meta(|m| { m.version(2).status("published"); })
    .exec(&mut db).await?;