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 theModel
traitModel::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:
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:
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.
Mixing data types
When using Model::create
, Rwf automatically converts values from Rust to database types. Due to how Rust works, it's not possible to build slices containing values of different types. If you try, you will get error[E0308]: mismatched types
. To get around this, you can call ToValue::to_value
on each column, for example:
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:
Optionally create records
If the record matching the INSERT
statement exists already, Rwf supports returning the existing row without performing an update:
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:
A transaction is started explicitly:
Afterwards, the ORM attempts to find a record matching the columns
in the INSERT
statement:
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: