Skip to content

Create records

Rwf can create model records in one of two ways:

  • Model::save method which is called on an instance of a struct implementing the Model trait
  • Model::create method which accepts the column names and their respective values as input

Saving models

Using our User model from our previous example, we can create a new record by instantiating a new instance of the User struct and calling save:

let user = User {
    id: None,
    email: "admin@example.com".to_string(),
    created_at: OffsetDateTime::now_utc(),
};

let user = user
    .save()
    .fetch(&mut conn)
    .await?;

Note

The id field is set to None. This ensures that the database assigns it a value automatically, and that this value is unique.

Calling save on a model struct with the id set to None produces the following query:

INSERT INTO "users" ("email", "created_at") VALUES ($1, $2) RETURNING *

Using table defaults

If you don't want to specify some columns when creating records and your database schema has configured defaults, you can use the Model::create method instead:

let user = User::create(&[
    ("email", "admin@example.com"),
])
.fetch(&mut conn)
.await?
INSERT INTO "users" ("email") VALUES ($1) RETURNING *

Any columns not specified in the INSERT statement will be automatically filled in with column defaults. For example, the created_at column specified in our previous example has a default value NOW(), the current database time.

Unique constraints

It's very common to place unique constraints on certain columns in a table to avoid duplicate records. For example, the "users" table would typically have a unique constraint on the email column, ensuring that no two users have the same email address.

To handle unique constraints, Rwf can update a record in-place if one exists already matching the constraint:

let user = User::create(&[
  ("email", "admin@example.com")
])
.unique_by(&["email"])
.fetch(&mut conn)
.await?;
INSERT INTO "users" ("email") VALUES ($1)
ON CONFLICT ("email") DO UPDATE
SET "email" = EXCLUDED."email"
RETURNING *

Optionally create records

If the record matching the INSERT statement exists already, Rwf supports returning the existing row without performing an update:

let user = User::find_or_create_by(&[
  ("email", "user1@example.com")
])
.fetch(&mut conn)
.await?;
SELECT * FROM "users" WHERE "email" = $1;
INSERT INTO "users" ("email") VALUES ($1) RETURNING *;

This executes up to two queries, starting with a SELECT to see if a row already exists, and if it doesn't, an INSERT to create it.

Combining with a unique constraint

In busy web apps which execute thousands of queries per second, it's entirely possible for a record to be created between the time the SELECT query returns no rows and an INSERT query is sent to the database. In this case, a unique constraint violation error will be returned. To avoid this, it's possible to combine unque_by with find_or_create_by executed inside a single transaction:

// Start a transaction explicitely.
let transaction = Pool::transaction().await?;

let user = User::find_or_create_by(&[
  ("email", "user1@example.com")
])
.unique_by(&["email"])
.fetch(&mut transaction)
.await?;

// Commit the transaction.
transaction.commit().await?;

A transaction is started explicitly:

BEGIN

Afterwards, the ORM attempts to find a record matching the columns in the INSERT statement:

SELECT * FROM "users" WHERE "email" = $1

If this query returns a row, no more queries are executed. Otherwise, an INSERT query with ON CONFLICT clause is sent to the database:

INSERT INTO "users" ("email") VALUES ($1)
ON CONFLICT ("email") DO UPDATE
SET "email" = EXCLUDED."email"
RETURNING *

Finally, the transaction is committed to the database:

COMMIT