Skip to content

Commit ae03d1f

Browse files
committed
Modify todo example to use postgres and diesel_async
1 parent 89a2af1 commit ae03d1f

File tree

6 files changed

+70
-62
lines changed

6 files changed

+70
-62
lines changed

examples/todo/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ publish = false
77

88
[dependencies]
99
rocket = { path = "../../core/lib" }
10-
diesel = { version = "2.0.0", features = ["sqlite", "r2d2"] }
10+
diesel = { version = "2.0.0", features = ["postgres", "r2d2"] }
1111
diesel_migrations = "2.0.0"
1212

1313
[dev-dependencies]
1414
parking_lot = "0.12"
1515
rand = "0.8"
1616

17-
[dependencies.rocket_sync_db_pools]
18-
path = "../../contrib/sync_db_pools/lib/"
19-
features = ["diesel_sqlite_pool"]
17+
[dependencies.rocket_db_pools]
18+
path = "../../contrib/db_pools/lib/"
19+
features = ["diesel_postgres"]
2020

2121
[dependencies.rocket_dyn_templates]
2222
path = "../../contrib/dyn_templates"

examples/todo/Rocket.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
[default]
22
template_dir = "static"
33

4-
[default.databases.sqlite_database]
5-
url = "db/db.sqlite"
4+
[default.databases.epic_todo_database]
5+
url = "postgresql://postgres@localhost:5432/epic_todo_database"
6+
max_connections = 1
7+
connect_timeout = 5

examples/todo/db/DB_LIVES_HERE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
db does not live here :(

examples/todo/src/main.rs

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
#[macro_use] extern crate rocket;
2-
#[macro_use] extern crate rocket_sync_db_pools;
3-
#[macro_use] extern crate diesel;
42

53
#[cfg(test)]
64
mod tests;
@@ -13,13 +11,15 @@ use rocket::response::{Flash, Redirect};
1311
use rocket::serde::Serialize;
1412
use rocket::form::Form;
1513
use rocket::fs::{FileServer, relative};
14+
use rocket_db_pools::{Connection, Database};
1615

1716
use rocket_dyn_templates::Template;
1817

1918
use crate::task::{Task, Todo};
2019

21-
#[database("sqlite_database")]
22-
pub struct DbConn(diesel::SqliteConnection);
20+
#[derive(Database)]
21+
#[database("epic_todo_database")]
22+
pub struct Db(rocket_db_pools::diesel::PgPool);
2323

2424
#[derive(Debug, Serialize)]
2525
#[serde(crate = "rocket::serde")]
@@ -29,14 +29,14 @@ struct Context {
2929
}
3030

3131
impl Context {
32-
pub async fn err<M: std::fmt::Display>(conn: &DbConn, msg: M) -> Context {
32+
pub async fn err<M: std::fmt::Display>(conn: &mut Connection<Db>, msg: M) -> Context {
3333
Context {
3434
flash: Some(("error".into(), msg.to_string())),
3535
tasks: Task::all(conn).await.unwrap_or_default()
3636
}
3737
}
3838

39-
pub async fn raw(conn: &DbConn, flash: Option<(String, String)>) -> Context {
39+
pub async fn raw(conn: &mut Connection<Db>, flash: Option<(String, String)>) -> Context {
4040
match Task::all(conn).await {
4141
Ok(tasks) => Context { flash, tasks },
4242
Err(e) => {
@@ -51,11 +51,11 @@ impl Context {
5151
}
5252

5353
#[post("/", data = "<todo_form>")]
54-
async fn new(todo_form: Form<Todo>, conn: DbConn) -> Flash<Redirect> {
54+
async fn new(todo_form: Form<Todo>, mut conn: Connection<Db>) -> Flash<Redirect> {
5555
let todo = todo_form.into_inner();
5656
if todo.description.is_empty() {
5757
Flash::error(Redirect::to("/"), "Description cannot be empty.")
58-
} else if let Err(e) = Task::insert(todo, &conn).await {
58+
} else if let Err(e) = Task::insert(todo, &mut conn).await {
5959
error_!("DB insertion error: {}", e);
6060
Flash::error(Redirect::to("/"), "Todo could not be inserted due an internal error.")
6161
} else {
@@ -64,50 +64,55 @@ async fn new(todo_form: Form<Todo>, conn: DbConn) -> Flash<Redirect> {
6464
}
6565

6666
#[put("/<id>")]
67-
async fn toggle(id: i32, conn: DbConn) -> Result<Redirect, Template> {
68-
match Task::toggle_with_id(id, &conn).await {
67+
async fn toggle(id: i32, mut conn: Connection<Db>) -> Result<Redirect, Template> {
68+
match Task::toggle_with_id(id, &mut conn).await {
6969
Ok(_) => Ok(Redirect::to("/")),
7070
Err(e) => {
7171
error_!("DB toggle({}) error: {}", id, e);
72-
Err(Template::render("index", Context::err(&conn, "Failed to toggle task.").await))
72+
Err(Template::render("index", Context::err(&mut conn, "Failed to toggle task.").await))
7373
}
7474
}
7575
}
7676

7777
#[delete("/<id>")]
78-
async fn delete(id: i32, conn: DbConn) -> Result<Flash<Redirect>, Template> {
79-
match Task::delete_with_id(id, &conn).await {
78+
async fn delete(id: i32, mut conn: Connection<Db>) -> Result<Flash<Redirect>, Template> {
79+
match Task::delete_with_id(id, &mut conn).await {
8080
Ok(_) => Ok(Flash::success(Redirect::to("/"), "Todo was deleted.")),
8181
Err(e) => {
8282
error_!("DB deletion({}) error: {}", id, e);
83-
Err(Template::render("index", Context::err(&conn, "Failed to delete task.").await))
83+
Err(Template::render("index", Context::err(&mut conn, "Failed to delete task.").await))
8484
}
8585
}
8686
}
8787

8888
#[get("/")]
89-
async fn index(flash: Option<FlashMessage<'_>>, conn: DbConn) -> Template {
89+
async fn index(flash: Option<FlashMessage<'_>>, mut conn: Connection<Db>) -> Template {
9090
let flash = flash.map(FlashMessage::into_inner);
91-
Template::render("index", Context::raw(&conn, flash).await)
91+
Template::render("index", Context::raw(&mut conn, flash).await)
9292
}
9393

9494
async fn run_migrations(rocket: Rocket<Build>) -> Rocket<Build> {
95+
use diesel::Connection;
9596
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
9697

9798
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
99+
let config: rocket_db_pools::Config = dbg!(rocket.figment()).extract_inner("databases.epic_todo_database").expect("Db not configured");
98100

99-
DbConn::get_one(&rocket).await
100-
.expect("database connection")
101-
.run(|conn| { conn.run_pending_migrations(MIGRATIONS).expect("diesel migrations"); })
102-
.await;
101+
rocket::tokio::task::spawn_blocking(move || {
102+
diesel::PgConnection::establish(&config.url)
103+
.expect("No database")
104+
.run_pending_migrations(MIGRATIONS)
105+
.expect("Invalid migrations");
106+
})
107+
.await.expect("tokio doesn't work");
103108

104109
rocket
105110
}
106111

107112
#[launch]
108113
fn rocket() -> _ {
109114
rocket::build()
110-
.attach(DbConn::fairing())
115+
.attach(Db::init())
111116
.attach(Template::fairing())
112117
.attach(AdHoc::on_ignite("Run Migrations", run_migrations))
113118
.mount("/", FileServer::from(relative!("static")))

examples/todo/src/task.rs

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use rocket::serde::Serialize;
22
use diesel::{self, result::QueryResult, prelude::*};
3+
use rocket_db_pools::diesel::RunQueryDsl;
34

45
mod schema {
5-
table! {
6+
diesel::table! {
67
tasks {
78
id -> Nullable<Integer>,
89
description -> Text,
@@ -13,7 +14,7 @@ mod schema {
1314

1415
use self::schema::tasks;
1516

16-
use crate::DbConn;
17+
type DbConn = rocket_db_pools::diesel::AsyncPgConnection;
1718

1819
#[derive(Serialize, Queryable, Insertable, Debug, Clone)]
1920
#[serde(crate = "rocket::serde")]
@@ -31,41 +32,35 @@ pub struct Todo {
3132
}
3233

3334
impl Task {
34-
pub async fn all(conn: &DbConn) -> QueryResult<Vec<Task>> {
35-
conn.run(|c| {
36-
tasks::table.order(tasks::id.desc()).load::<Task>(c)
37-
}).await
35+
pub async fn all(conn: &mut DbConn) -> QueryResult<Vec<Task>> {
36+
tasks::table.order(tasks::id.desc()).load::<Task>(conn).await
3837
}
3938

4039
/// Returns the number of affected rows: 1.
41-
pub async fn insert(todo: Todo, conn: &DbConn) -> QueryResult<usize> {
42-
conn.run(|c| {
43-
let t = Task { id: None, description: todo.description, completed: false };
44-
diesel::insert_into(tasks::table).values(&t).execute(c)
45-
}).await
40+
pub async fn insert(todo: Todo, conn: &mut DbConn) -> QueryResult<usize> {
41+
let t = Task { id: None, description: todo.description, completed: false };
42+
diesel::insert_into(tasks::table).values(&t).execute(conn).await
4643
}
4744

4845
/// Returns the number of affected rows: 1.
49-
pub async fn toggle_with_id(id: i32, conn: &DbConn) -> QueryResult<usize> {
50-
conn.run(move |c| {
51-
let task = tasks::table.filter(tasks::id.eq(id)).get_result::<Task>(c)?;
52-
let new_status = !task.completed;
53-
let updated_task = diesel::update(tasks::table.filter(tasks::id.eq(id)));
54-
updated_task.set(tasks::completed.eq(new_status)).execute(c)
55-
}).await
46+
pub async fn toggle_with_id(id: i32, conn: &mut DbConn) -> QueryResult<usize> {
47+
let task = tasks::table.filter(tasks::id.eq(id)).get_result::<Task>(conn).await?;
48+
let new_status = !task.completed;
49+
let updated_task = diesel::update(tasks::table.filter(tasks::id.eq(id)));
50+
updated_task.set(tasks::completed.eq(new_status)).execute(conn).await
5651
}
5752

5853
/// Returns the number of affected rows: 1.
59-
pub async fn delete_with_id(id: i32, conn: &DbConn) -> QueryResult<usize> {
60-
conn.run(move |c| diesel::delete(tasks::table)
54+
pub async fn delete_with_id(id: i32, conn: &mut DbConn) -> QueryResult<usize> {
55+
diesel::delete(tasks::table)
6156
.filter(tasks::id.eq(id))
62-
.execute(c))
57+
.execute(conn)
6358
.await
6459
}
6560

6661
/// Returns the number of affected rows.
6762
#[cfg(test)]
68-
pub async fn delete_all(conn: &DbConn) -> QueryResult<usize> {
69-
conn.run(|c| diesel::delete(tasks::table).execute(c)).await
63+
pub async fn delete_all(conn: &mut DbConn) -> QueryResult<usize> {
64+
diesel::delete(tasks::table).execute(conn).await
7065
}
7166
}

examples/todo/src/tests.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use rand::{Rng, thread_rng, distributions::Alphanumeric};
44

55
use rocket::local::asynchronous::Client;
66
use rocket::http::{Status, ContentType};
7+
use rocket_db_pools::Database;
78

89
// We use a lock to synchronize between tests so DB operations don't collide.
910
// For now. In the future, we'll have a nice way to run each test in a DB
@@ -15,10 +16,14 @@ macro_rules! run_test {
1516
let _lock = DB_LOCK.lock();
1617

1718
rocket::async_test(async move {
18-
let $client = Client::tracked(super::rocket()).await.expect("Rocket client");
19-
let db = super::DbConn::get_one($client.rocket()).await;
20-
let $conn = db.expect("failed to get database connection for testing");
21-
Task::delete_all(&$conn).await.expect("failed to delete all tasks for testing");
19+
let rocket = super::rocket();
20+
let mut $conn = super::Db::fetch(&rocket)
21+
.expect("database")
22+
.get()
23+
.await
24+
.expect("database connection");
25+
let $client = Client::tracked(rocket).await.expect("Rocket client");
26+
Task::delete_all(&mut $conn).await.expect("failed to delete all tasks for testing");
2227

2328
$block
2429
})
@@ -39,7 +44,7 @@ fn test_index() {
3944
fn test_insertion_deletion() {
4045
run_test!(|client, conn| {
4146
// Get the tasks before making changes.
42-
let init_tasks = Task::all(&conn).await.unwrap();
47+
let init_tasks = Task::all(&mut conn).await.unwrap();
4348

4449
// Issue a request to insert a new task.
4550
client.post("/todo")
@@ -49,7 +54,7 @@ fn test_insertion_deletion() {
4954
.await;
5055

5156
// Ensure we have one more task in the database.
52-
let new_tasks = Task::all(&conn).await.unwrap();
57+
let new_tasks = Task::all(&mut conn).await.unwrap();
5358
assert_eq!(new_tasks.len(), init_tasks.len() + 1);
5459

5560
// Ensure the task is what we expect.
@@ -61,7 +66,7 @@ fn test_insertion_deletion() {
6166
client.delete(format!("/todo/{}", id)).dispatch().await;
6267

6368
// Ensure it's gone.
64-
let final_tasks = Task::all(&conn).await.unwrap();
69+
let final_tasks = Task::all(&mut conn).await.unwrap();
6570
assert_eq!(final_tasks.len(), init_tasks.len());
6671
if final_tasks.len() > 0 {
6772
assert_ne!(final_tasks[0].description, "My first task");
@@ -79,16 +84,16 @@ fn test_toggle() {
7984
.dispatch()
8085
.await;
8186

82-
let task = Task::all(&conn).await.unwrap()[0].clone();
87+
let task = Task::all(&mut conn).await.unwrap()[0].clone();
8388
assert_eq!(task.completed, false);
8489

8590
// Issue a request to toggle the task; ensure it is completed.
8691
client.put(format!("/todo/{}", task.id.unwrap())).dispatch().await;
87-
assert_eq!(Task::all(&conn).await.unwrap()[0].completed, true);
92+
assert_eq!(Task::all(&mut conn).await.unwrap()[0].completed, true);
8893

8994
// Issue a request to toggle the task; ensure it's not completed again.
9095
client.put(format!("/todo/{}", task.id.unwrap())).dispatch().await;
91-
assert_eq!(Task::all(&conn).await.unwrap()[0].completed, false);
96+
assert_eq!(Task::all(&mut conn).await.unwrap()[0].completed, false);
9297
})
9398
}
9499

@@ -98,7 +103,7 @@ fn test_many_insertions() {
98103

99104
run_test!(|client, conn| {
100105
// Get the number of tasks initially.
101-
let init_num = Task::all(&conn).await.unwrap().len();
106+
let init_num = Task::all(&mut conn).await.unwrap().len();
102107
let mut descs = Vec::new();
103108

104109
for i in 0..ITER {
@@ -119,7 +124,7 @@ fn test_many_insertions() {
119124
descs.insert(0, desc);
120125

121126
// Ensure the task was inserted properly and all other tasks remain.
122-
let tasks = Task::all(&conn).await.unwrap();
127+
let tasks = Task::all(&mut conn).await.unwrap();
123128
assert_eq!(tasks.len(), init_num + i + 1);
124129

125130
for j in 0..i {

0 commit comments

Comments
 (0)