Rust初心者が勉強したことを記録する備忘録。
github.com
今日やったこと
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");
}
}
次にやりたいこと
SQLに関する処理をModelに移す(もしくはRepositoryを作る)
- テストコードを書く
- jsonを返すように直す
- テンプレートエンジンを使ってhtmlを返す