Rust初心者が勉強したことを記録する備忘録。
今日やったこと
main.rs に処理を全部書いて微妙だったので、MVCっぽい感じでディレクトリ構造を整理した。
独学なので、この構成で良いのかは全く自信がない。
ディレクトリ構成
ルーティング、モデル、コントローラーが別ファイルになるようにしている。
src ├── controllers │ ├── mod.rs │ ├── posts.rs │ └── top.rs ├── lib.rs ├── main.rs ├── models │ ├── mod.rs │ ├── post.rs │ └── util.rs ├── routes.rs └── schema.rs
ルーティング
ルーティングを縦に列挙するのは辛いので、リソース単位で関数化した。
fn main() { HttpServer::new(|| App::new().configure(routes::top).configure(routes::posts)) .bind("127.0.0.1:8088") .unwrap() .run() .unwrap(); }
routes.rs は以下のような感じで実装してある。
use crate::controllers::{posts, top}; use actix_web::web; pub fn top(cfg: &mut web::ServiceConfig) { cfg.route("/", web::get().to(top::index)); } pub fn posts(cfg: &mut web::ServiceConfig) { cfg.route("/posts", web::get().to(posts::index)) .route("/posts/{id}", web::get().to(posts::show)) .route("/posts", web::post().to(posts::create)) .route("/posts/{id}", web::put().to(posts::update)) .route("/posts/{id}", web::patch().to(posts::update)) .route("/posts/{id}", web::delete().to(posts::destroy)); }
コントローラー
Rails のコントローラーのような感じでメソッドを作っている。
use crate::models::Post; use actix_web::{HttpRequest, HttpResponse, Responder}; pub fn index() -> impl Responder { let results = Post::all(); let res = format!("Displaying {} posts", results.len()); HttpResponse::Ok().body(res) } pub fn show(req: HttpRequest) -> impl Responder { let post = find_post(req); let res = format!("Show {}", post.id); HttpResponse::Ok().body(res) } pub fn create() -> impl Responder { Post::create("title", "body"); HttpResponse::Created().body("Inserting") } pub fn update(req: HttpRequest) -> impl Responder { let mut post = find_post(req); post.publish(); HttpResponse::Ok().body("Published") } pub fn destroy(req: HttpRequest) -> impl Responder { let post = find_post(req); post.destroy(); HttpResponse::NoContent() } fn find_post(req: HttpRequest) -> Post { let id: i32 = req.match_info().get("id").unwrap().parse().unwrap(); Post::find(id) }
モデル
main.rsにあった処理をActiveRecordっぽいAPIで使えるように実装した。
ただ、細かいところは適当。
use super::util::establish_connection; use crate::schema::posts; use crate::schema::posts::dsl; use diesel::prelude::*; #[derive(Queryable)] pub struct Post { pub id: i32, pub title: String, pub body: String, pub published: bool, } #[derive(Insertable)] #[table_name = "posts"] struct NewPost<'a> { pub title: &'a str, pub body: &'a str, } impl Post { pub fn all() -> Vec<Post> { let connection = establish_connection(); dsl::posts .filter(dsl::published.eq(true)) .limit(5) .load::<Post>(&connection) .expect("Error loading posts") } pub fn find(id: i32) -> Post { let connection = establish_connection(); dsl::posts .find(id) .first::<Post>(&connection) .expect("Error finding posts") } pub fn create(title: &str, body: &str) { let new_post = NewPost { title: title, body: body, }; let connection = establish_connection(); diesel::insert_into(posts::table) .values(&new_post) .execute(&connection) .expect("Error saving new post"); } pub fn publish(&mut self) { let connection = establish_connection(); let num_updated = diesel::update(dsl::posts.find(self.id)) .set(dsl::published.eq(true)) .execute(&connection) .expect(&format!("Unable to find post {}", self.id)); if num_updated > 0 { self.published = true; } } pub fn destroy(&self) { let connection = establish_connection(); diesel::delete(dsl::posts.find(self.id)) .execute(&connection) .expect("Error deleting posts"); } }