Chainedアクションのサンプル
例として、商品アイテムとそのアイテムに対して登録されたコメントを表示/登録/編集/削除するWebアプリケーションを考えます。これらのアクションを定義するためにFCSample::Controller::Itemコントローラを作成します。
$ ./FCSample/script/fcsample_create.pl controller Item
この商品アイテムアプリケーションで必要となるパスとして次のものを定義します。/itemの後にはItemIDが、/commentの後にはCommentIDが指定されるものとします。
| パス | 説明 |
| /item | 登録されているアイテムの一覧表示 |
| /item/* | 指定されたアイテムの情報を表示 |
| /item/*/comment | 指定されたアイテムのコメント一覧を表示 |
| /item/*/comment/add | 指定されたアイテムにコメント追加 |
| /item/*/comment/* | 指定されたアイテム、コメントを表示 |
| /item/*/comment/*/edit | 指定されたアイテム、コメントの編集 |
| /item/*/comment/*/delete | 指定されたアイテム、コメントの削除 |
これらのパスに対応させるアクションとして、次のものを定義しました。この中でパスと関連づけられていないアクションとしてcapture_itemとcapture_commentがあります。これらのアクションで、データの取得、Stashへの登録を行うことで、依存する他のアクションではそれぞれの目的に特化した処理を定義できるようになり、コードの再利用性が高まります。
| アクション名 | 対応するパス | :Chainedの引数 | :PathPartの引数 | 説明 |
| index | /item | × | × | Itemコントローラのindexアクションとしてアイテム一覧を表示 |
| capture_item | × | / | 'item' | 引数として受け取ったItemIDからアイテム情報を取得 |
| show_item | /item/* | capture_item | '' | 指定されたアイテム情報を表示 |
| show_comment_list | /item/*/comment | capture_item | 'comment' | 指定されたアイテムのコメント一覧を表示 |
| add_comment | /item/*/comment/add | capture_item | 'comment/add' | 指定されたアイテムにコメント追加 |
| capture_comment | × | capture_item | 'comment' | 引数として受け取ったItemIDとCommentIDから該当するコメント情報を取得 |
| show_comment | /item/*/comment/* | capture_comment | '' | 指定されたコメントを表示 |
| edit_comment | /item/*/comment/*/edit | capture_comment | 'edit' | 指定されたコメントの編集 |
| delete_comment | /item/*/comment/*/delete | capture_comment | 'delete' | 指定されたコメントの削除 |
それでは、上記のアクションのコードを見ていきましょう。
package FCSample::Controller::Item;
# 省略
# (1)モデルからアイテム一覧を取得し、表示する
sub index :Path :Args(0) {
my ( $self, $c ) = @_;
}
# (2)最上位のChainedアクションとして定義し、ItemIDを引数とする
sub capture_item :Chained('/') :PathPart('item') :CaptureArgs(1) {
my ( $self, $c, $item_id ) = @_;
$c->stash->{item_id} = $item_id;
}
# (3)Stashから取得するItemIDを使用してアイテム情報を表示する
sub show_item :Chained('capture_item') :PathPart('') :Args(0) {
my ( $self, $c ) = @_;
my $item_id = $c->stash->{item_id};
...
}
# (4)複数階層のパスをPathPartに指定
sub add_comment :Chained('capture_item') :PathPart('comment/add') :Args(0) {
my ( $self, $c ) = @_;
my $item_id = $c->stash->{item_id};
if ($c->request->method eq 'POST') {
# POSTの場合にはパラメータから受け取ったコメントを登録
$comment = $c->request->parameters->{comment};
...
} else {
# POST以外の場合にはコメント登録フォームを表示
...
}
...
}
# (5)エンドポイントではないアクションを連鎖
sub capture_comment
:Chained('capture_item') :PathPart('comment') :CaptureArgs(1) {
my ( $self, $c, $comment_id ) = @_;
$c->stash->{comment_id} = $comment_id;
}
(1)モデルからアイテム一覧を取得し、表示する
indexアクションとして定義し、モデルから取得したアイテム一覧を表示します。
(2)最上位のChainedアクションとして定義し、ItemIDを引数とする
最上位のChainedアクションとして定義するため、:Chained('/')としています。またこのアクションはエンドポイントではないためCaptureArgsアトリビュートで引数の数を指定しています。ここではItemIDだけを引数として受け取ります。
この例では、ItemIDをStashに登録していますが、通常はモデルから取得したアイテムデータそのものをStashに登録します。
(3)Stashから取得するItemIDを使用してアイテム情報を表示する
このアクションは、capture_itemアクションの後に呼び出される必要があるため、:Chained('capture_item')のように指定します。またエンドポイントになるためArgsアトリビュートで引数の数を指定しています。
必要なアイテム情報はcapture_itemアクションによってStashに登録されているので、ここではアイテムの表示に特化した処理だけを定義できます。
(4)複数階層のパスをPathPartに指定
PathPartには、ここで指定しているように複数階層のパスを指定することもできます。
また、Chainedアクションに限った方法ではありませんが、add_commentアクションでは同じURLを指定された場合でも、HTTPのメソッドによって処理を切り替えています。
(5)エンドポイントではないアクションを連鎖
capture_commentアクションはエンドポイントではありませんが、上位アクションとしてcapture_itemを指定しています。このようにChainedアクションではさまざまなURLに対応できるように、柔軟な定義が可能となっています。
まとめ
本記事では、アクションからURLを遷移することなく別アクションを呼び出す方法、そしてリダイレクトを行う方法を紹介しました。そして、第3回の記事で簡単に紹介したChainedアクションについてもサンプルを交えて説明しました。
次回は、Perlでのオブジェクト指向プログラミングについて簡単に説明し、Catalyst5.8から基盤として使用されているMoose、そしてMooseを使用したモダンなCatalystプログラミングなどについて説明していく予定です。
参考資料
- 『Catalyst-Manual-5.8000』
- 『Catalyst::DispatchType::Chained』
- 『モダンPerl入門』 牧大輔 著、翔泳社、2009年2月
