Skip to content

Latest commit

 

History

History
760 lines (533 loc) · 31 KB

File metadata and controls

760 lines (533 loc) · 31 KB

Amon2入門 (第2部)

この記事では, Amon2入門 (第1部)に引き続き, Amon2を利用した簡単なWebアプリケーション(スケジュール管理サービス)の開発を通して, Amon2を利用したWebアプリケーション開発の流れを体験していきます.

アプリケーション概要

アプリケーションの概要は, 次のようにしましょう.

  • /にGETメソッドでアクセスすると, スケジュールを登録するためのフォームがある(スケジュールの登録)
    • フォームは, スケジュールのタイトルと, その日付を入力するテキストフォームがある
    • ここでは単純化の為, 日付はテキストフォームを利用して, 「2015/3/10」のように入力することとする
      • さらに単純化のため, 誤った日付(例えば, 「2015/30/10」など)は入力されないものとする
      • もちろん, 実際のアプリケーションではチェックして, エラーを出すべきです!
    • 更に更に, 単純化の為, フォームは空欄で入力されないものとする
      • しつこいですが, もちろん実際のアプリケーションではチェックして, エラーを出すべきです!
    • フォームを送信すると, /postにPOSTメソッドで送信される
      • ここでschedulesテーブルにスケジュールを登録する
      • 登録が終わったら, /にリダイレクトする
  • /にGETメソッドでアクセスすると, 新しいスケジュールが上になる形でスケジュールの一覧を見る事ができる(スケジュールの一覧表示)
    • スケジュールのタイトルと日付, そして「削除」ボタンを表示する
    • 削除ボタンをクリックすると, /scheudles/スケジュールID/deleteにPOSTメソッドでアクセスするようにする
  • /schedules/スケジュールID/deleteにPOSTメソッドでアクセスすると, そのスケジュールIDに該当するスケジュールをデータベースから削除する(スケジュールの削除)
    • スケジュールを削除した後は, /にリダイレクトする

Tips

アプリケーションの開発に入る前に, 少しTipsを.

$ carton exec -- plackup -Ilib -R ./lib --access-log /dev/null -p 5000 -a ./script/scheduler-server

アプリケーションの起動コマンドをこのように書き換えておくと, アプリケーションのファイルを変更した際に自動的にサーバを再起動(コードの読み込み直し)をしてくれます. コードを書き換える度にアプリケーションを停止して, 起動しなおすといった処理を回避出来るので, 非常に楽です.

データベーススキーマの編集

さて, まずはこのアプリケーションで利用するデータベースのスキーマを考えていきます. なお, Amon2ではデフォルトではデータベースにSQLiteを利用するようになっているので, 今回もそのままSQLiteを利用します. 実際のアプリケーションでは, MySQLを利用することが多いです(Amon2でも, 比較的簡単にMySQLを利用するように変更することができます).

SQLiteが使うデータベースのスキーマは, sql/sqlite.sqlに設置されています.

CREATE TABLE IF NOT EXISTS member (
    id           INTEGER NOT NULL PRIMARY KEY,
    name         VARCHAR(255)
);

今回は, このようなスキーマを使って開発を進めていくことにします.

CREATE TABLE IF NOT EXISTS schedules (
    id           INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    title        VARCHAR(255),
    date         INTEGER
);

スケジュールのID(id)の他に, スケジュールのタイトル(title)と日付(date)を持たせます. なお, 単純化の為, dateについてはUNIX時間(epoch秒)で持たせることにします.

書き換えが終わったら, 早速このスキーマを利用して, SQLite用のデータベースファイルを生成します. Amon2の場合, 開発環境(development)ではdb/development.dbというファイルを利用するので,

$ sqlite3 db/development.db < sql/sqlite.sql

このようにします.

...ちなみに余談ですが, データベースのスキーマを書き換えたい場合ですが, 面倒ですが一旦rm db/development.dbでデータベースファイルを削除してから, sqlite3 db/development.db < sql/sqlite.sqlで新しくデータベースファイルを生成しなおすのが何だかんだいって楽です. もちろん本番環境でこんな事をしてしまうとデータが全て消えてしまうので, 本番環境でMySQLを使っている場合は, ALTER TABLEなどを使ってよしなに既存のデータベースを変更するようにします.

Tengのスキーマの変更

次に, Tengのスキーマを変更します. Amon2がデフォルトで利用するORMのTengを利用する為には, 「Teng Schema」の設定をしなければなりません. 自動的に生成する方法もありますが(その一例), 今回は手動で書き換えることにします.

Tengのスキーマファイルは, lib/Scheduler/DB/Schema.pmです.

package Scheduler::DB::Schema;
use strict;
use warnings;
use utf8;

use Teng::Schema::Declare;

base_row_class 'Scheduler::DB::Row';

table {
    name 'member';
    pk 'id';
    columns qw(id name);
};

1;

1つのテーブルに関する情報を, table { ... };の中に書いていきます(もし, データベースに複数個のテーブルがあれば, その数だけtable { ... };が並ぶことになります). columnsでそのテーブルが持つカラム(列)の名前を, そしてpkでprimary keyの設定をすることができます.

そのため, 先程のデータベーススキーマでは, 次のように書き換えることになります.

package Scheduler::DB::Schema;
use strict;
use warnings;
use utf8;

use Teng::Schema::Declare;

base_row_class 'Scheduler::DB::Row';

table {
    name 'schedules';
    pk 'id';
    columns qw(id title date);
};

1;

これで, データベースに関連する準備は完了です.

トップページの編集

まずは, スケジュールを登録出来るように, トップページのテンプレートを書き換えていきましょう.

: cascade "include/layout.tx"

: override content -> {

<h1 style="padding: 70px; text-align: center; font-size: 80px; line-height: 1; letter-spacing: -2px;">Scheduler</h1>

<hr />

<form class="form-inline" method="POST" action="<: uri_for('/post') :>">
    <div class="form-group">
        <label>タイトル</label>
        <input type="text" class="form-control" name="title" placeholder="例: Perlの勉強">
    </div>
    <div class="form-group">
        <label>日付</label>
        <input type="text" class="form-control" name="date" placeholder="例: 2015/03/10">
    </div>
    <button type="submit" class="btn btn-default">登録</button>
</form>

: }

この段階で, localhost:5000にアクセスしてみると, 次のように表示されるはずです.

ただ, フォームに適切な文字を入力して, 「登録」ボタンを押すと...

このように, 404 Not foundになってしまいます. これは, 言うまでもありませんが, 「POSTメソッドで, /postにアクセスした時の処理」を, まだ実装していないからです.

スケジュールの登録

それでは, Controllerに手を加えていきましょう. 「第1部」最後の練習問題の, 1. を解答した段階でのDispatcher.pmは, このようになっているはずです.

package Scheduler::Web::Dispatcher;
use strict;
use warnings;
use utf8;
use Amon2::Web::Dispatcher::RouterBoom;

get '/' => sub {
    my ($c) = @_;
    return $c->render('index.tx');
};

1;

今回は, ここからスタートしていきます. まず, 「POSTメソッドで, /postにアクセスした時の処理」を書けるように...

package Scheduler::Web::Dispatcher;
use strict;
use warnings;
use utf8;
use Amon2::Web::Dispatcher::RouterBoom;

get '/' => sub {
    my ($c) = @_;
    return $c->render('index.tx');
};

post '/post' => sub { ... };

1;

こうなりますね. そして具体的な処理を, サブルーチンリファレンスの中に書いていきます.

package Scheduler::Web::Dispatcher;
use strict;
use warnings;
use utf8;
use Amon2::Web::Dispatcher::RouterBoom;

use Time::Piece; # (1)

get '/' => sub {
    my ($c) = @_;
    return $c->render('index.tx');
};

post '/post' => sub {
    my ($c) = @_;

    my $title = $c->req->parameters->{title}; #
    my $date  = $c->req->parameters->{date};  # (2)

    my $date_epoch = Time::Piece->strptime($date, '%Y/%m/%d')->epoch; # (3)

    $c->db->insert(schedules => { #
        title => $title,          #
        date  => $date_epoch,     # (4)
    });                           #

    return $c->redirect('/');
};

1;

...今回の場合は, こうなるでしょうか. 少し複雑な(1)〜(4)の部分を, 1つずつ見ていきましょう.

(1) モジュールの呼び出し

(3)の部分で, 「2014/05/10」といった文字列をepoch秒に変換する為に, Time::Pieceというモジュールを読み込んでいます.

なお, Time::PieceはPerl 5.9.5からコアモジュール(Perlをインストールした際に, 最初から入っているモジュール)になっています.

$ corelist Time::Piece

Data for 2014-09-14
Time::Piece was first released with perl v5.9.5

(2) テキストフォームに入力した文字列の取得

    my $title = $c->req->parameters->{title};
    my $date  = $c->req->parameters->{date};

$c->reqからパラメータを取得して, それぞれ$title$dateという変数に格納しています.

(3) 日付文字列のepoch秒への変換

    my $date_epoch = Time::Piece->strptime($date, '%Y/%m/%d')->epoch;

この辺りは, 「こんな感じでやる」... と思って下さい.

Time::Piece->strptime($date, $format)で, $formatに従って$dateを解析してTime::Pieceのオブジェクトを生成し, そのオブジェクトから利用出来るepochメソッドを利用して, 元の文字列$dateからepoch秒を生成しています.

生成したepoch秒は, $date_epochという変数に代入して利用します.

(4) データベースへの書き込み

    $c->db->insert(schedules => {
        title => $title,
        date  => $date_epoch,
    });

$c->dbからTengのオブジェクトを呼び出し, insertメソッドで書き込んでいます. Tengのinsertメソッドは, 第1引数にデータを書き込むテーブル名を, 第2引数にハッシュリファレンスで書き込みたいパラメータを与えます.

なので, この場合はschedulesというテーブルに, titleの列に$titleの中身を, dateの列に$date_epochの中身を書き込む, という処理になります.

動作確認

それでは, 動作を確認してみましょう! テキストフォームに適切な文字列を入力し, 「登録」ボタンを押すと, 先程のようにエラー画面が表示されず, そのまま/へリダイレクトされると思います.

それでは引き続き, 登録したスケジュールを表示出来るようにしていきましょう.

スケジュール一覧の表示

まずは, Controller側から変更していきます.

package Scheduler::Web::Dispatcher;
use strict;
use warnings;
use utf8;
use Amon2::Web::Dispatcher::RouterBoom;

use Time::Piece;

get '/' => sub {
    my ($c) = @_;

    my @schedules = $c->db->search('schedules'); # (1)
    return $c->render('index.tx', { schedules => \@schedules });
};

post '/post' => sub {
    my ($c) = @_;

    my $title = $c->req->parameters->{title};
    my $date  = $c->req->parameters->{date};

    my $date_epoch = Time::Piece->strptime($date, '%Y/%m/%d')->epoch;

    $c->db->insert(schedules => {
        title => $title,
        date  => $date_epoch,
    });

    return $c->redirect('/');
};

1;

(1)の部分で, またもやTengを利用して, schedulesテーブルの一覧を取得しています. Tengのsearchメソッドは, 第1引数に検索対象となるテーブルの名前を, 第2引数に検索条件(ハッシュリファレンス)を, 第3引数に検索オプション(ハッシュリファレンス)を指定できますが, 第2引数を省略した場合はテーブルに格納されている全てのデータを取得してくれます.

これを, $crenderメソッドでテンプレート側に渡して, 表示してもらう訳です. それでは, テンプレート側を見てみましょう.

: cascade "include/layout.tx"

: override content -> {

<h1 style="padding: 70px; text-align: center; font-size: 80px; line-height: 1; letter-spacing: -2px;">Scheduler</h1>

<hr />

<form class="form-inline" method="POST" action="<: uri_for('/post') :>">
    <div class="form-group">
        <label>タイトル</label>
        <input type="text" class="form-control" name="title" placeholder="例: Perlの勉強">
    </div>
    <div class="form-group">
        <label>日付</label>
        <input type="text" class="form-control" name="date" placeholder="例: 2015/03/10">
    </div>
    <button type="submit" class="btn btn-default">登録</button>
</form>

<hr />
    : for $schedules -> $schedule {
        <: $schedule.title :><br>
    : }
: }

とりあえず, 手っ取り早くデータベースに書き込みが出来ているか確認するために, titleだけを表示するようにしてみました.

    : for $schedules -> $schedule {
        <: $schedule.title :><br>
    : }

この部分ですが, Perlで例えればこんな感じになるでしょうか.

    for my $scheudle (@{ $schedules }) {
        print $schedule->title;
    }

テンプレート内で, for $arrays -> $object { ... }のように書くと, $arraysに含まれる全要素について, 1つずつ$objectに代入した上で{ ... }内の処理を繰り返し実行します.

$scheduleについては, データベースに格納された1つの行がTengのRowオブジェクトとして格納されています. Rowオブジェクトからは, それぞれの列の名前のメソッドが使えるようになっているので, 例えばControllerにおいて,

get '/' => sub {
    my ($c) = @_;

    my @schedules = $c->db->search('schedules');
    for my $schedule (@schedules) {
        print STDERR $schedule->title . "\n";
    }

    return $c->render('index.tx', { schedules => \@schedules });
};

のように書くと, /にアクセスする度に, scheudler-serverを実行しているコンソール上に登録したスケジュールのタイトルがズラっと表示されるはずです(時間があれば, やってみてください).

というわけで, この時点で適当にスケジュールを登録してから, /にアクセスすると...

このように, タイトルが表示されるようになっているはずです.

日付の表示

続いて, スケジュールの表示部分をテーブルにしつつ, 日付も表示するようにしてみます.

: cascade "include/layout.tx"

: override content -> {

<h1 style="padding: 70px; text-align: center; font-size: 80px; line-height: 1; letter-spacing: -2px;">Scheduler</h1>

<hr />

<form class="form-inline" method="POST" action="<: uri_for('/post') :>">
    <div class="form-group">
        <label>タイトル</label>
        <input type="text" class="form-control" name="title" placeholder="例: Perlの勉強">
    </div>
    <div class="form-group">
        <label>日付</label>
        <input type="text" class="form-control" name="date" placeholder="例: 2015/03/10">
    </div>
    <button type="submit" class="btn btn-default">登録</button>
</form>

<hr />

<table class="table">
    <thead>
        <tr>
            <th>タイトル</th>
            <th>日時</th>
        </tr>
    </thead>
    <tbody>
    : for $schedules -> $schedule {
        <tr>
            <td><: $schedule.title :></td>
            <td><: $schedule.date :></td>
        </tr>
    : }
    </tbody>
</table>
: }

それでは, ブラウザで/にアクセスしてみましょう.

...そういえば, スケジュールの日付はepoch秒で保存するようにしていました. これでは非常に見栄えが悪いので, 「XXXX年XX月XX日」のように表示するようにしたいです.

inflateとdeflate

この解決策としては, いろいろな手段があります. Controllerレイヤーで変換してしまう, JavaScriptで変換してしまう... などなど.

今回は, Tengのinflate/deflateという仕組みを使って解決してみようと思います. この機能の詳細については, Perl Advent Calendar 2011 Teng Tracのinflate / deflateという記事が詳しいです.

一言で言えば, inflateは「Tengでデータベースからデータを引っ張ってくる時に, フィルタリングするモノ」, 逆にdeflateは「Tengでデータベースにデータを書き込む時に, フィルタリングするモノ」と思って下さい.

今回は, inflateの機能を使って, Tengを使ってschedulesテーブルからデータを引っ張ってくる際に, 「date」カラムのデータをepoch秒から(そのepoch秒を格納した)Time::Pieceのオブジェクトに変換します. そして, テンプレート側で, Time::Pieceのstrftimeメソッドを利用して, 好きなフォーマットで表示します.

Tengのスキーマの書き換え

package Scheduler::DB::Schema;
use strict;
use warnings;
use utf8;

use Teng::Schema::Declare;
use Time::Piece; # (1)

base_row_class 'Scheduler::DB::Row';

table {
    name 'schedules';
    pk 'id';
    columns qw(id title date);

    inflate 'date' => sub {                      #
        my $col_value = shift;                   # (2)
        Time::Piece->strptime($col_value, '%s'); #
    };                                           #
};

1;

まず, (2)で使っているTime::Pieceモジュールを(1)でuseしています. そして肝心の(2)の部分. inflateの書式は, inflate $column_name => sub { ... };のように書きます. $column_nameはinfalteの処理をしたいカラムの名前になるので, この場合, dateカラムのみsub { ... }で指定した処理(フィルター)が行われます.

サブルーチンリファレンスの第1引数には, その指定したカラムのデータが入ってきます. 例えば, schedulesテーブルからあるデータを引っ張ってきた時, そのデータのdateカラムの中身が1234567890であれば, (2)のコードの$col_valueには, その1234567890が入ります.

そして, そのサブルーチンリファレンスの返り値が, 新たに$column_nameのデータとして上書きされます(この場合は, dateカラムの中身が上書きされる).

ここでは, $col_valueにはepoch秒が入っているはずなので, それをTime::Pieceのstrptimeメソッドを利用してTime::Pieceのオブジェクトにして, それを返しています.

これによって, Controllerで

my $schedule = $c->db->single('schedules');  # `single`は, データベースから1つの要素を取得するメソッドです.
print $schedule->date->strftime("%Y/%m/%d"); # => 2015/03/05 (例)

のように, $schedule->dateの中身が, epoch秒ではなくそのepoch秒のTime::Pieceオブジェクトになる, という訳です.

テンプレートでの描画

    : for $schedules -> $schedule {
        <tr>
            <td><: $schedule.title :></td>
            <td><: $schedule.date.strftime("%Y/%m/%d") :></td>
        </tr>
    : }

テンプレートは, このように書き換えます. テンプレートにおいて, メソッド呼び出しの->.で表現するので, 先程のTime::Pieceオブジェクトから任意のフォーマットの日付の文字列を生成するには, このように書けば良いです.

これらの変更を実装すると, /にアクセスした時の表示は, 次のようになるはずです.

これで, 冒頭で定めたアプリケーション概要のうち, 「スケジュールの登録」と「スケジュールの一覧表示」を達成することが出来ました. 続いて, 「スケジュールの削除」に挑戦していきましょう!

スケジュールの削除

それでは最後に, スケジュールを削除する機能を実装していきます.

テンプレートの編集

まず, 削除ボタンを表示するようにテンプレートを書き換えましょう.

    <thead>
        <tr>
            <th>タイトル</th>
            <th>日時</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
    : for $schedules -> $schedule {
        <tr>
            <td><: $schedule.title :></td>
            <td><: $schedule.date.strftime("%Y/%m/%d") :></td>
            <td>
                <form method="POST" action="<: uri_for('/schedules/'~$schedule.id~'/delete') :>">
                    <button type="submit" class="btn btn-danger">削除</button>
                </form>
            </td>
        </tr>
    : }
    </tbody>

すると, このような形で削除ボタンが表示されます.

コラム: $schedule.idの正体

ここで唐突に出てきた, $schedule.id($schedule->id)について解説しておきます.

このidですが, 最初にデータベーススキーマを設定した際には, schedulesテーブルの中のカラムとして存在しました.

CREATE TABLE IF NOT EXISTS schedules (
    id           INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    title        VARCHAR(255),
    date         INTEGER
);

しかし, Controllerでschedulesテーブルにデータを挿入する際には, idを指定していませんでした.

    $c->db->insert(schedules => {
        title => $title,
        date  => $date_epoch,
    });

しかし実際には, schedulesテーブルにデータを挿入するタイミングで, 適切な値がidとして格納されています. これは, SQLiteのPRIMARY KEYによるものです.

PRIMARY KEY, 主キーは1テーブルにつき1カラムのみ設定することができ, データ型は大抵INTEGERなどを数値を格納出来る型にします. なお, PRIMARY KEYを設定した場合, 自動的にNOT NULL制約を与えた場合と同じように, 重複した値が許されなくなります. SQLiteの場合, データ型がINTEGERのカラムに対してPRIMARY KEYを設定すると, データ挿入時, そのカラムに格納する値が空だった場合のみ自動的に重複しない値が挿入されるようになっています. なお, 今回はSQLiteを利用していますが, MySQLを利用している場合は, このような挙動をさせたい場合はPRIMARY KEYに加えてAUTOINCREMENTを指定しなければなりません.

さて, もしPRIMARY KEYがない場合, 削除をする際にはtitledateで条件をかける必要がありますし, そもそもtitledateが同じカラムがあった場合, どちらを消していいのかわからなくなる, という場合も出てくるでしょう. しかし, このようにPRIMARY KEYとしてidを生成しておけば, 全てのスケジュールにユニークなIDを割り振ることが出来ます. そのため, ここまで見てきたようにスケジュールの削除を(idの指定のみで)簡単に実装することができるようになります.

...この辺りの詳細については割愛しますが, idというカラムの有無で実装の難しさが変わってくる, という所は感じて頂けたと思います. データベーススキーマの設計と, Webアプリケーションの実装は密接に関連していて, データベーススキーマをうまく設計出来ないと, Webアプリケーションの実装も難しくなる, という所は覚えておいて下さい.

Controllerの書き換え

続いて, Controllerを書き換えていきます.

ここで悩むのは, パスをどのように指定すればよいか? という所でしょう. ここまで, 例えばpost '/post' => sub { ... };のようにControllerに書いてきましたが, 今回のパス(左の例の場合, '/post')は可変です. つまり, それぞれのスケジュールが持っているIDに応じて, /schedules/1/deleteとか/schedules/5/deleteのように, 複数のパスが存在する訳です.

こういう場合は, パスをこのように書けば解決できます.

post '/schedules/:id/delete' => sub {
    my ($c, $args) = @_;

    my $id = $args->{id};

    ...
};

パスの中に, :idのように:で始まる文字列を入れると, その部分は「全てのパターンにマッチ」するようになります. そして, そのマッチした文字列は, サブルーチンリファレンスの第2引数にハッシュリファレンスの形で渡ってきます.

なので, 上記のコードは, 例えば/schedules/1/deleteにアクセスした場合は$idの中身が1に, /schedules/5/deleteにアクセスした場合は$idの中身が5になります.

後は, Tengのdeleteメソッドを利用して該当するIDのスケジュールを消してから, '/'にリダイレクトすればOKです.

post '/schedules/:id/delete' => sub {
    my ($c, $args) = @_;
    my $id = $args->{id};

    $c->db->delete('schedules' => { id => $id });
    return $c->redirect('/');
};

動作確認

それでは, 動作確認をしてみましょう.

ここから, 「テスト2」の削除ボタンを押すと...

「テスト2」が正しく消えましたね!

表示順序の変更

さて, これでアプリケーション概要で定めた全ての機能を実装することが出来ました. ...が, 複数のスケジュールを登録してみた時, 違和感を感じませんか?

日時の順番がバラバラになっていますね. これを, 「最新のスケジュールが上になる」ように変更して, Amon2チュートリアルを終わりにしたいと思います.

Tengのsearchメソッドのオプション

Tengのsearchメソッドは, 第3引数にオプションを指定できると説明しました. このオプションで, 取得するデータを並び替えるORDER BYを, dateカラムについて適用するようにします.

get '/' => sub {
    my ($c) = @_;

    my @schedules = $c->db->search('schedules', {}, { order_by => 'date DESC'});
    return $c->render('index.tx', { schedules => \@schedules });
};

第2引数, 検索条件はなし(全て取得する)なので, {}のように空のハッシュリファレンスを渡しています. そして第3引数で, { order_by => 'date DESC' }を指定しています. Tengは, これらの条件やオプションなどを元に, 自動的にSQLのクエリを組み立て, SQLiteやMySQLに対して実行してくれるのです.

さて, このように変更すると, '/'にアクセスした際の結果はどうなるでしょうか?

...新しいスケジュールが上に表示されるようになりました!

練習問題

1-1. スケジュールの逆順表示

GETメソッドで'/'にアクセスした時はこれまで通り「新しいスケジュールが上」になるように表示しつつ, GETメソッドでクエリパラメータ(クエリ文字列)を使って'/?order=reverse'でアクセスした時は, 「新しいスケジュールが下」になるように書き換えてみよう.

ヒント

Controllerにおいて, GETメソッドで'/?order=reverse'にアクセスした際のorder=reverseのようなパラメータは, 次のようにして取得することが出来ます.

get '/' => sub {
    my ($c) = @_;

    my $order = $c->req->parameters->{order};

    ...
};

1-2. 「今日」のスケジュールの強調

スケジュールの日付が今日だった場合, そのスケジュールのタイトルを赤色に変更するようにしてみよう. 今日の日付については, Time::Pieceをuseしている場合, localtimeというサブルーチンで今現在の時間のTime::Pieceオブジェクトを取得することができるので, ここから導くと良いでしょう(strftimeメソッドで, 適切なフォーマットで文字列を生成しましょう).

1-3. deflate

スケジュールを投稿する際, ControllerでTime::Pieceのオブジェクトからepoch秒を生成しています. これを, Tengのdeflateの機能を使って実装するように書き換えてみましょう.

うまく実装できれば, Controllerのスケジュール投稿に関するコードは, 次のように書き換えても正しく動くはずです.

post '/post' => sub {
    my ($c) = @_;

    $c->db->insert(schedules => {
        title => $c->req->parameters->{title},
        date  => $c->req->parameters->{date},
    });

    return $c->redirect('/');
};

2. チャレンジ課題

チャレンジ課題は必須ではありません. 少し難易度が高いので「チャレンジ問題」にしていますが, 時間があれば是非挑戦していただきたいです!

チャレンジ課題は, トップページに表示されている各スケジュールに「編集」ボタンを設置して, 任意のスケジュールのタイトルや日付を変更できる機能の追加です.

スケジュール編集をするためのテンプレートを作成するなど, 作業量は少なくありませんが, この問題をクリア出来れば「Amon2で作成されたWebアプリケーションに, 自力で機能を1つ追加できた」という経験が出来ます!