Skip to main content

ツイートモデルを作成する

ツイート一覧画面のツイートをプログラムから指定できるように改良してみましょう。

モデルを作成する#

ツイートは

  • 投稿者名
  • メッセージ
  • 投稿日時

から成るため、これを構造体として定義します。

struct Tweet {    account_name: String,    message: String,    posted_at: DateTime<Local>}

Rustでは標準で日時を取り扱うことができないため、 Chrono ライブラリを使用します。

これにより、 DateTime 型、 Local 型などを取り扱うことができます。

Cargo.toml
...(省略)...
  [dependencies]  rocket = "0.4.10"  rocket_contrib = { version = "0.4.10", default-features = false, features = ["handlebars_templates"] }+ chrono = "0.4.19"

テンプレートにモデルデータを使う#

構造体を定義できたため、 この構造体のインスタンスデータを生成し、テンプレートに渡してみます。

src/main.rs
  #![feature(proc_macro_hygiene, decl_macro)]    #[macro_use] extern crate rocket;  extern crate rocket_contrib;  extern crate chrono;  - use std::collections::HashMap;  use rocket_contrib::templates::Template;+ use chrono::{DateTime, Local, TimeZone};+ + struct Tweet {+     account_name: String,+     message: String,+     posted_at: DateTime<Local>+ }    #[get("/")]  fn index() -> Template {-     let context: HashMap<&str, &str> = HashMap::new();+     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)  }    fn main() {      rocket::ignite()          .mount("/", routes![index])          .attach(Template::fairing())          .launch();  }

シリアライズできるようにする#

上記のコードはコンパイルエラーになります。

error[E0277]: the trait bound `Tweet: serde::ser::Serialize` is not satisfied   --> src/main.rs:30:31    |30  |     Template::render("index", &context)    |                               ^^^^^^^^ the trait `serde::ser::Serialize` is not implemented for `Tweet`    |

これは、テンプレートに渡すデータが serde::ser::Serialize トレイトを実装していること(=シリアライズできること)を要求していますが、 Tweet がそれを実装していないことによって起こっているエラーです。

info

シリアライズとは、複雑なデータ構造を文字列などの保存可能な形式に変換することです。

例えば、 Tweet のデータを

{  "account_name": "はなこ",  "message": "はじめてのツイート",  "posted_at": "2020-02-18T00:00:00Z"}

のようなJSON文字列に変換した場合は TweetをJSONにシリアライズした ことになります。

また、逆に文字列をデータ構造に変換することをデシリアライズといいます。

Tweet がこのトレイトを実装するために、まずは serde ライブラリをプロジェクトに追加しましょう。

後述する #[derive] アトリビュートでデフォルト実装を利用するために、 derive のfeatureも有効にしておきます。

Cargo.toml
  [package]  name = "rustwi"  version = "0.1.0"  edition = "2018"    [dependencies]  rocket = "0.4.10"  rocket_contrib = { version = "0.4.10", default-features = false, features = ["handlebars_templates"] }  chrono = "0.4.19"+ serde = { version = "1.0.126", features = ["derive"] }

Tweetserde::ser::Serialize トレイトを実装します。

自前でこのトレイトを実装しても良いのですが、ここでは、先ほど有効化したデフォルト実装をそのまま継承させます。

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_contrib::templates::Template;  use chrono::{DateTime, Local, TimeZone};  + #[derive(Serialize)]  struct Tweet {      account_name: String,      message: String,      posted_at: DateTime<Local>  }  ...(省略)...

これで Tweet はシリアライズできる状態になりました。しかし、コンパイルしようとすると再びエラーがでます。

error[E0277]: the trait bound `DateTime<Local>: Serialize` is not satisfied  --> src/main.rs:15:5   |15 |     posted_at: DateTime<Local>   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `DateTime<Local>`   |

DateTime 型もシリアライズに対応させる必要があります。 chrono ライブラリも serde に対応しているため、その設定をプロジェクト定義ファイルに追記します。

Cargo.toml
  [package]  name = "rustwi"  version = "0.1.0"  edition = "2018"    [dependencies]  rocket = "0.4.10"  rocket_contrib = { version = "0.4.10", default-features = false, features = ["handlebars_templates"] }- chrono = "0.4.19"+ chrono = { version = "0.4.19", features = ["serde"] }  serde = { version = "1.0.126", features = ["derive"] }

テンプレートで渡されたデータを使う#

最後にテンプレートファイルを、データを使うように書き換えます。

templates/index.html.hbs
...(省略)...
  <div class="form">    <form>      <input name="message" placeholder="ここに内容を書いてください" />      <button type="submit">ツイート</button>    </form>  </div>  <hr />- <div class="card">-   <span class="account-name">はなこ</span>-   2回目のツイート-   <span class="date">2021220</span>- </div>- <div class="card">-   <span class="account-name">はなこ</span>-   はじめてのツイート-   <span class="date">2021218</span>- </div>+ {{#each this}}+   {{#with this}}+     <div class="card">+       <span class="account-name">{{account_name}}</span>+       {{message}}+       <span class="date">{{posted_at}}</span>+     </div>+   {{/with}}+ {{/each}}  </body>  </html>

日付の表示を読みやすくするために、日時のヘルパー関数を定義してHandlebarsに渡します。

src/main.rs
...(省略)...
  #[macro_use] extern crate serde;    use rocket_contrib::templates::Template;+ use rocket_contrib::templates::handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext};  use chrono::{DateTime, Local, TimeZone};    #[derive(Serialize)]  ...(省略)...      Template::render("index", &context)  }  + 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(|f| f.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])-         .attach(Template::fairing())+         .attach(Template::custom(|e| {+             e.handlebars.register_helper("datetimeformat", Box::new(datetimeformat));+         }))          .launch();  }

次ページでは、ツイートを投稿できるようにしてみましょう。