Skip to main content

ツイートを受け付ける

ここでは、フォームから投稿されたデータを表示してみましょう。

リクエストデータを定義する#

フォームデータを受け取るために、まずはリクエストデータの構造体を定義します。 今後拡張していきますが、まずはメッセージだけが含まれていれば良いでしょう。

struct NewTweet {    message: String}

続いて、これを受け取るためのエンドポイントを定義します。 今回は Tweet エンティティを作成する処理なので、 POST /tweets にしたいと思います。

ところで、ここまで言及してきませんでしたが、Rocketでは、下記のコードのようにエンドポイントに使用する関数に 属性マクロ を適用します。

#[get("/")] // --(1)fn foo() -> Template {    ...}

(1) により、 foo に自動的にHTTPのGETメソッドに必要な処理が追加されます。GETの他にPOST, PUT, DELETE, HEAD, PATCH, OPTIONのHTTPメソッド用のマクロが用意されています。

フォームデータを受け取るPOSTメソッドの場合は下記のとおりです。

#[post("/"), data = "<form>"] // --(1)fn foo(form: Form<FooStruct>) -> Template { // --(2)    ...}
caution

(1)data に指定する名前(ここでは form ) と、 (2) の引数名を合わせる必要があります。

では、ここまでの内容をメインプログラムに実装してみましょう。

src/main.rs
  #![feature(proc_macro_hygiene, decl_macro)]    #[macro_use] extern crate rocket;  extern crate rocket_contrib;  extern crate chrono;  #[macro_use] extern crate serde;  + use rocket::request::Form;  use rocket_contrib::templates::Template;  use rocket_contrib::templates::handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext};  use chrono::{DateTime, Local, TimeZone};    #[derive(Serialize)]  struct Tweet {      account_name: String,      message: String,      posted_at: DateTime<Local>  }+ + struct NewTweet {+     message: String+ }    #[get("/")]  fn index() -> Template {      let context = vec![          Tweet {              account_name: "はなこ".into(),              message: "2回目のツイート".into(),              posted_at: Local.ymd(2020, 2, 20).and_hms(0, 0, 0)          },          Tweet {              account_name: "はなこ".into(),              message: "はじめてのツイート".into(),              posted_at: Local.ymd(2021, 2, 18).and_hms(0, 0, 0)          }      ];      Template::render("index", &context)  }+ + #[post("/", data = "<form>")]+ fn post_tweet(form: Form<NewTweet>) -> String {+     form.message.clone()+ }    fn datetimeformat(      h: &Helper,      _: &Handlebars,      _: &Context,      _: &mut RenderContext,      out: &mut dyn Output,  ) -> HelperResult {      let datetime = h.param(0).and_then(|dt| {          dt.value().as_str().and_then(|dt| DateTime::parse_from_rfc3339(dt).ok())      });      let format = h.param(1).and_then(|x| x.value().as_str());      if let (Some(datetime), Some(format)) = (datetime, format) {          let _ = out.write(datetime.format(format).to_string().as_str());      }      Ok(())  }    fn main() {      rocket::ignite()          .mount("/", routes![index])+         .mount("/tweets", routes![post_tweet])          .attach(Template::custom(|e| {              e.handlebars.register_helper("datetimeformat", Box::new(datetimeformat));          }))          .launch();  }

リクエストデータの構造体をフォームデータに対応させる#

さて、このままではエラーになってしまいます。

error[E0277]: the trait bound `Form<NewTweet>: FromData<'_>` is not satisfied  --> src/main.rs:42:15   |42 | fn post_tweet(form: Form<NewTweet>) -> String {   |               ^^^^^^^^^^^^^^^^^^^^ the trait `FromData<'_>` is not implemented for `Form<NewTweet>`   |

これは、リクエストで送られてくるデータを NewTweet のどのフィールドと対応させるかを指示するための実装 FormDataNewTweet に存在しないために起こっているエラーです。

Rocketでは FormData のデフォルト実装を提供しているため、 #[derive] マクロで継承することができます。リクエストで送られてくるデータの名前と構造体の名前が一致している場合にはこれだけで対応できます。

src/main.rs
...(省略)...
      posted_at: DateTime<Local>  }  + #[derive(FromForm)]  struct NewTweet {      message: String  }    #[get("/")]  fn index() -> Template {
...(省略)...
info

名前が一致しない場合でも #[form(field = "<field name from request>")] 属性マクロを使って対応付けさせることができます。

これでコンパイルが通ります。フォームに何か入力して「ツイート」ボタンを押すと、入力した内容が表示されるはずです。

リクエストを受け取ってツイートとして表示するようにする#

このページの最後の内容として、受け取ったデータをツイートとして表示してみましょう。

src/main.rs
...(省略)...
- fn post_tweet(form: Form<NewTweet>) -> String {-     form.message.clone()+ fn post_tweet(form: Form<NewTweet>) -> Template {+     let context = vec![+         Tweet {+             account_name: "はなこ".into(),+             message: form.message.clone(),+             posted_at: Local.ymd(2020, 2, 20).and_hms(0, 0, 0)+         }+     ];+     Template::render("index", &context)+ }
...(省略)...

名前が「はなこ」になってしまっていますが、ツイートに書いた内容が表示されるようになりました。テンプレートを使うことで、データを変更しつつ、ページの表示を再利用できています。

次の章では、ユーザーが送信したツイートをデータベースから読み取って表示したり、データベースに保存したりします。その前に、巨大化してきた src/main.rs をリファクタしておきましょう。