Rustでwebアプリを実装して勉強 その2 - SQLの処理をモデルに書く

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を返す