ORM basics
Introduction
Rwf comes with its own ORM (object-relational mapping). The Rwf ORM is very flexible, supporting anything from basic fetch by primary key queries, to multi-table joins and complex custom queries.
What's an ORM?
The ORM is the M in MVC design: the model. It allows to easily retrieve data stored in your database tables and display it in the application, without having to write complex SQL queries by hand.
It works by attaching itself to Rust structs and mapping data from table columns to struct fields (and vice versa), converting them from database types to Rust data types automatically in the process.
Getting started
Using the ORM is simple and only requires defining a struct for each model (or database table). For example, most web apps will have a User
model,
which stores its data in a "users"
table:
Column | Database data type | Rust data type |
---|---|---|
id |
BIGINT |
i64 |
email |
VARCHAR |
String |
created_at |
TIMESTAMPTZ |
time::OffsetDateTime |
Defining the Rust struct for the model can be done as follows:
use rwf::prelude::*;
#[derive(Clone, macros::Model)]
struct User {
id: Option<i64>,
email: String,
created_at: OffsetDateTime,
}
The same table in the database can be created with this query1:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Note
The id
column is using an optional Rust i64
integer. This is because the struct will be used
for both inserting and selecting data from the table. When inserting, the id
column should be None
and will be automatically
assigned by the database. This ensures that all rows in your tables have a unique primary key.
Naming conventions
The struct fields have the same name as the database columns, and the data types match their respective Rust types. The table name in the database corresponds to the name of the struct, lowercase and pluralized. For example, User
model will refer to the "users"
table in the database.
A row in a database table which contains model data is called a record. The macros::Model
macro automatically implements the database to Rust and vice versa types conversion
and maps the column values to the struct fields.
Query data
With the model defined in Rust, writing SQL queries is automatically implemented by the ORM. For example, to fetch a record by primary key, you can do the following:
The find
method is implemented by the Model
trait for the User
struct automatically. It accepts a Rust integer and produces the following query:
The fetch
method assembles the query, sends it to the database, and returns one row. The row is converted to an instance of the User
struct:
Fetch multiple rows
Querying multiple rows can be done by using fetch_all
instead of fetch
, for example:
This will fetch 25 user records from the "users"
table, ordering them by the primary key. The result will be a Vec<User>
, in the order
returned by the database:
The ORM can be used to write easy and complex queries alike, without having to learn SQL. Rwf currently supports PostgreSQL, but other databases like SQLite and MySQL are on the roadmap.
-
See migrations to learn how to create tables in your database reliably. ↩