Skip to content

Commit 4b90c80

Browse files
Merge branch 'master' into elijah/migrations-rollback
2 parents 921ef7b + 71ef9f0 commit 4b90c80

9 files changed

Lines changed: 1237 additions & 884 deletions

File tree

cot/src/test.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,44 @@ impl TestRequestBuilder {
649649
self
650650
}
651651

652+
/// Add a cache to the request builder.
653+
///
654+
/// # Examples
655+
///
656+
/// ```
657+
/// use cot::test::{TestCache, TestRequestBuilder};
658+
///
659+
/// let test_cache = TestCache::new_memory();
660+
/// let request = TestRequestBuilder::get("/")
661+
/// .cache(test_cache.cache())
662+
/// .build();
663+
/// ```
664+
#[cfg(feature = "cache")]
665+
pub fn cache(&mut self, cache: Cache) -> &mut Self {
666+
self.cache = Some(cache);
667+
self
668+
}
669+
670+
/// Add an email backend to the request builder.
671+
///
672+
/// # Examples
673+
///
674+
/// ```
675+
/// use cot::email::Email;
676+
/// use cot::email::transport::console::Console;
677+
/// use cot::test::TestRequestBuilder;
678+
///
679+
/// let test_email_backend = Email::new(Console::new());
680+
/// let request = TestRequestBuilder::get("/")
681+
/// .email(test_email_backend)
682+
/// .build();
683+
/// ```
684+
#[cfg(feature = "email")]
685+
pub fn email(&mut self, email: Email) -> &mut Self {
686+
self.email = Some(email);
687+
self
688+
}
689+
652690
/// Add form data to the request builder.
653691
///
654692
/// # Examples

docs/databases/overview.md

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
title: Overview
3+
---
4+
5+
Cot comes with its own ORM (Object-Relational Mapping) system, which is a layer of abstraction that allows you to interact with your database using objects instead of raw SQL queries. This makes it easier to work with your database and allows you to write more maintainable code. It abstracts over the specific database engine that you are using, so you can switch between different databases without changing your code. The Cot ORM is also capable of automatically creating migrations for you, so you can easily update your database schema as your application evolves, just by modifying the corresponding Rust structures.
6+
7+
## Defining models
8+
To define a model in Cot, create a Rust struct and annotate it with the [`model`](attr@cot::db::model) attribute macro. Cot will automatically implement the [`Model`](trait@cot::db::Model) trait for the model, making the struct recognizable to the ORM and mapping it to a database table. Here's an example of a simple model that represents a link in a link shortener service:
9+
10+
```rust
11+
use cot::db::{model, Auto, LimitedString};
12+
use cot::common_types::Url;
13+
14+
#[model]
15+
pub struct Link {
16+
#[model(primary_key)]
17+
id: Auto<i64>,
18+
#[model(unique)]
19+
slug: LimitedString<32>,
20+
url: Url,
21+
}
22+
```
23+
24+
There's some invaluable stuff going on here, so let's break it down:
25+
26+
* The [`#[model]`](attr@cot::db::model) attribute macro is used to automatically implement the [`Model`](trait@cot::db::Model) trait for the structure. This is required for the Cot ORM to recognize it as a database model.
27+
* The `id` field is a typical database primary key, which means that it uniquely identifies each row in the table. It's of type `i64`, which is a 64-bit signed integer. [`Auto`](enum@cot::db::Auto) wrapper is used to automatically generate a new value for this field when a new row is inserted into the table (`AUTOINCREMENT` or `SERIAL` value in the database nomenclature).
28+
* The `slug` field is marked as [`unique`](attr@cot::db::model), which means that each value in this field must be unique across all rows in the table. It's of type [`LimitedString<32>`](struct@cot::db::LimitedString), which is a string with a maximum length of `32` characters. This is a custom type provided by Cot that ensures that the string is not longer than the specified length at the time of constructing an instance of the structure.
29+
* The `url` field is of type [`Url`](struct@cot::common_types::Url), which is a custom type provided by Cot that represents a URL.
30+
31+
After putting this structure in your project, you can use it to interact with the database. Before you do that though, it's necessary to create the table in the database that corresponds to this model. Cot CLI has got you covered and can automatically create migrations for you – just run the following command:
32+
33+
```bash
34+
cot migration make
35+
```
36+
37+
This will create a new file in your `migrations` directory in the crate's src directory. We will come back to the contents of this file later in this guide, but for now, let's focus on how to use the model to interact with the database.
38+
39+
## Model Fields Options
40+
Cot provides specific field-level attributes that provide special meaning to fields in a model. The most common ones are listed below:
41+
42+
### `primary_key`
43+
This is used to mark a field as the primary key of the table. This is a required field for every model.
44+
45+
```rust
46+
#[model]
47+
pub struct Post {
48+
#[model(primary_key)]
49+
id: Auto<i64>,
50+
title: String,
51+
content: String,
52+
}
53+
```
54+
55+
### `unique`
56+
This is used to mark a field as unique, which means that each value in this field must be unique across all rows in the table. For more information see the [model field reference](attr@cot::db::model).
57+
58+
```rust
59+
#[model]
60+
pub struct User {
61+
#[model(primary_key)]
62+
id: Auto<i64>,
63+
#[model(unique)]
64+
username: String,
65+
}
66+
```
67+
68+
### `field_name`
69+
This is used to specify a custom column name for a field in the database table.
70+
71+
```rust
72+
#[model]
73+
pub struct Post {
74+
#[model(primary_key)]
75+
id: Auto<i64>,
76+
#[model(field_name = "post_title")]
77+
title: String,
78+
content: String,
79+
}
80+
```
81+
82+
## Field Types
83+
To use a type in a model, it **must** implement the [`ToDbValue`](trait@cot::db::ToDbValue) and [`FromDbValue`](trait@cot::db::FromDbValue) traits. The [`ToDbValue`](trait@cot::db::ToDbValue) trait tells Cot how to serialize the field value into a format that can be stored in the database (e.g. a string, a number, a boolean, etc.) while the [`FromDbValue`](trait@cot::db::FromDbValue) trait tells Cot how to deserialize the field value from the database format back into the Rust type.
84+
Cot provides implementations of these traits for many common types on a best-effort basis. Refer to the [implementations](trait@cot::db::FromDbValue#foreign-impls) and [implementors](trait@cot::db::FromDbValue#implementors) section of the docs for a complete list of the supported types.
85+
86+
In the example below, we show how to use a custom type as a field in a model:
87+
88+
```rust
89+
use cot::db::{Auto, ColumnType, DatabaseField, DbFieldValue, FromDbValue, model, SqlxValueRef, ToDbFieldValue};
90+
use cot::db::impl_mysql::MySqlValueRef;
91+
use cot::db::impl_postgres::PostgresValueRef;
92+
use cot::db::impl_sqlite::SqliteValueRef;
93+
94+
#[derive(Debug, Clone)]
95+
struct NewType(i32);
96+
97+
impl FromDbValue for NewType {
98+
fn from_sqlite(value: SqliteValueRef<'_>) -> cot::db::Result<Self>
99+
where
100+
Self: Sized
101+
{
102+
Ok(NewType(value.get::<i32>()?))
103+
}
104+
105+
fn from_postgres(value: PostgresValueRef<'_>) -> cot::db::Result<Self>
106+
where
107+
Self: Sized
108+
{
109+
Ok(NewType(value.get::<i32>()?))
110+
}
111+
112+
fn from_mysql(value: MySqlValueRef<'_>) -> cot::db::Result<Self>
113+
where
114+
Self: Sized
115+
{
116+
Ok(NewType(value.get::<i32>()?))
117+
}
118+
}
119+
120+
impl ToDbFieldValue for NewType {
121+
fn to_db_field_value(&self) -> DbFieldValue {
122+
self.0.clone().into()
123+
}
124+
}
125+
126+
#[model]
127+
#[derive(Debug, Clone)]
128+
pub struct Post {
129+
#[model(primary_key)]
130+
id: Auto<i64>,
131+
new_type: NewType,
132+
}
133+
```
134+
135+
## Relationships
136+
Relational databases are all about relationships between tables, and Cot provides a convenient way to define database relationships between models.
137+
138+
### Foreign keys
139+
To define a foreign key relationship between two models, you can use the [`ForeignKey`](https://docs.rs/cot/latest/cot/db/enum.ForeignKey.html) type. Here's an example of how you can define a foreign key relationship between a `Link` model and some other `User` model:
140+
141+
```rust
142+
use cot::db::ForeignKey;
143+
144+
#[model]
145+
pub struct Link {
146+
#[model(primary_key)]
147+
id: Auto<i64>,
148+
#[model(unique)]
149+
slug: LimitedString<32>,
150+
url: String,
151+
user: ForeignKey<User>,
152+
}
153+
154+
#[model]
155+
pub struct User {
156+
#[model(primary_key)]
157+
id: Auto<i64>,
158+
name: String,
159+
}
160+
```
161+
162+
When you define a foreign key relationship, Cot will automatically create a foreign key constraint in the database. This constraint will ensure that the value in the `user_id` field of the `Link` model corresponds to a valid primary key in the `User` model.
163+
164+
When you retrieve a model that has a foreign key relationship, Cot will not automatically fetch the related model and populate the foreign key field with the corresponding value. Instead, you need to explicitly fetch the related model using the `get` method of the `ForeignKey` object. Here's an example of how you can fetch the related user for a link:
165+
166+
```rust
167+
let mut link = query!(Link, $slug == LimitedString::new("cot").unwrap())
168+
.get(db)
169+
.await?
170+
.expect("Link not found");
171+
172+
let user = link.user.get(db).await?;
173+
```
174+
175+
## Database Configuration
176+
Configure your database connection in the configuration files inside your `config` directory:
177+
178+
```toml
179+
[database]
180+
# SQLite
181+
url = "sqlite://db.sqlite3?mode=rwc"
182+
183+
# Or PostgreSQL
184+
url = "postgresql://user:password@localhost/dbname"
185+
186+
# Or MySQL
187+
url = "mysql://user:password@localhost/dbname"
188+
```
189+
190+
Cot tries to be as consistent as possible when it comes to the database engine you are using. This means that you can use SQLite for development and testing, and then switch to PostgreSQL or MySQL for production without changing your code. The only thing you need to do is to change the [`url`](struct@cot::config::DatabaseConfig#structfield.url) value in the configuration file!
191+
192+
Currently, Cot supports the following database engines:
193+
* SQLite
194+
* PostgreSQL
195+
* MySQL
196+
197+
As an alternative to setting the database configuration in the `TOML` file, you can also set it programmatically in the [`config`](trait@cot::project::Project#method.config) method of your project.
198+
Note that when you do this, the config values from the `TOML` file will be entirely ignored. Here's an example of how you can do that:
199+
200+
```rust
201+
use cot::config::DatabaseConfig;
202+
203+
struct MyProject;
204+
205+
impl Project for MyProject {
206+
fn config(&self) -> cot::Result<ProjectConfig> {
207+
Ok(
208+
ProjectConfig::builder()
209+
.database(
210+
DatabaseConfig::builder()
211+
.url("sqlite://db.sqlite3?mode=rwc")
212+
.build(),
213+
)
214+
.build()
215+
)
216+
}
217+
}
218+
```
219+
220+
## Summary
221+
222+
In this chapter you learned about the Cot ORM and how to define models, fields, and relationships between models. You also learned how to configure your database connection and how to use the models to interact with the database. In the next chapter, we will dive deeper into how to perform various database operations using the Cot ORM.

0 commit comments

Comments
 (0)