Fukuoka Perl Mongersの第12回定例勉強会で発表させていただいた資料を公開します。アプリケーションとしては、かなり貧弱なものですが、Catalystの触りの部分だけでも理解していただければ幸いです。
Catalystで一行掲示板
目的
- とにかくCatalystで動作するものを作ってみる。
- 実装は簡単に。
- ログイン/ログアウトは実装する。
- DBのテーブルリレーションは一箇所くらいやってみる。
- DBは設置が簡単なSQLiteを使う。
今回実装した機能
- 一行掲示板
- ログイン機能
- 一覧表示機能
- コメント投稿機能
- バリデーションは実装していません。
- 画面デザインは全くやってません。
開発環境
- Mac OS X 10.5
- Perl 5.8.8
- Sqlite3
- Catalyst (5.7014)
- DBIx::Class (0.08010)
- DBIx::Class::Schema::Loader (0.04005)
- Template Toolkit (2.20)
インストール
PerlとSQLiteは既にインストールしてあるものとして解説します。
Catalystのインストール
cpan -i Task::Catalyst
もしテストが失敗して、インストールできないようだったら、
cpan -if Task::Catalyst
アプリケーションのスケルトンを作成
適当なディレクトリへ移動してから、以下のコマンドを実行
catalyst.pl MyBBS
アプリケーションのルートディレクトリへ入る
cd MyBBS/
以下、ファイル名やスクリプトの実行などは、このディレクトリを基点に解説しています。
開発用サーバー起動
perl script/mybbs_server.pl -r
プラウザでhttp://localhost:3000/
にアクセスしてみる。
停止させる場合は、
キーボードの Ctrl+c
初期設定
とりあえず使いそうなモジュールを追記しておきます。
lib/MyBBS.pm
use Catalyst qw/
-Debug
ConfigLoader
Static::Simple
StackTrace
/;
URIマッピングを決める
- ログイン画面
-
/login
- ログアウト画面
-
/logout
- コメント入力画面
-
/comment
- コメント一覧画面
-
/comment/list
コントローラー名決定
Root.pm (既にある)
Login.pm
Logout.pm
Comment.pm
コントローラー(Controller)生成
以下のコマンドを実行すると上記のControllerが
lib/MyBBS/Controller
ディレクトリ内に作られます。
perl script/mybbs_create.pl controller Login
perl script/mybbs_create.pl controller Logout
perl script/mybbs_create.pl controller Comment
Template Toolkit(TT)の初期設定
テンプレートクラス作成
下記のコマンドを実行します。
perl script/mybbs_create.pl view TT TTSite;
テンプレートファイルの拡張子を設定
下の一行を加えます。
lib/MyBBS/View/TT.pm
TEMPLATE_EXTENSION => '.tt2',
テスト
以下のコマンドを実行します。
CATALYST_DEBUG=0 prove --lib lib -v t
データベース
DBスキーマーの作成
mybbs.sql というファイルを以下の内容で作成します。
-- First drop everything
DROP INDEX if exists "user_id_pkey";
DROP INDEX if exists "comment_id_pkey";
DROP INDEX if exists "login_name_unique_index";
DROP TABLE if exists user;
DROP TABLE if exists comment;
-- Create tables
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT
, login_name TEXT NOT NULL
, password TEXT NOT NULL
, email TEXT NOT NULL
, name TEXT NOT NULL
, delete_flag BOOLEAN NOT NULL default 'false'
, created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP
, updated_on TIMESTAMP
);
CREATE TABLE comment (
id INTEGER PRIMARY KEY AUTOINCREMENT
, statement TEXT NOT NULL
, user_id INTEGER NOT NULL
, delete_flag BOOLEAN NOT NULL default 'false'
, created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP
, updated_on TIMESTAMP
, FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
);
-- Create indexes
CREATE INDEX "user_id_pkey" ON "user" ("id");
CREATE INDEX "comment_id_pkey" ON "comment" ("id");
CREATE UNIQUE INDEX "login_name_unique_index" ON "user" ("login_name");
-- Insert test users
INSERT INTO user
(
login_name
, password
, email
, name
)
VALUES
(
'foo'
, 'foo'
, 'foo@example.com'
, 'Mr. Foo'
);
INSERT INTO user
(
login_name
, password
, email
, name
)
VALUES
(
'bar'
, 'bar'
, 'bar@example.com'
, 'Mr. Bar'
);
データベース作成
上で作成した mybbs.sql をsqlite3に読み込んでデータベースを作成します。
以下のコマンドを実行します。
sqlite3 mybbs.db < mybbs.sql
mybbs.db というファイルが生成されているのを確認します。
DBIx::Class
Perlの代表的なO/Rマッパーを使用しますが、今回のアプリケーションでは、ほとんどその存在を意識することはないと思います。
スキーマークラスを生成する
以下のコマンドを実行します。
rm lib/MyBBS/Schema.pm;
perl script/mybbs_create.pl model DB DBIC::Schema MyBBS::Schema create=static dbi:SQLite:mybbs.db;
データベース( mybbs.sql )に変更があったときには、再度上のコマンドを実行してください。
リレーションシップの設定
上で作成したSQLが正しければ、すでに自動で設定されています。
テンプレート(View)作成
最初にエラーメッセージとステータスメッセージを表示する領域を追記しておきます。
root/lib/site/layout
<div id="content">
<span class="message">[% status_msg %]</span>
<span class="error">[% error_msg %]</span>
[% content %]
</div>
コメント一覧画面作成
Controllerを実装
lib/MyBBS/Controller/Comment.pm
=head2 list
コメント一覧を表示
=cut
sub list : Local {
my ( $self, $c ) = @_;
# コメントの配列
$c->stash->{comments} = [
$c->model('DB::Comment')->search(
undef,
{
# 取得するカラム名を指定します。
# ここでは関連テーブルからログイン名(login_name)も取得しています。
columns => [qw/statement user_id.login_name/],
# リレーション名を指定
join => 'user_id',
}
)
];
$c->stash->{template} = 'comment/list.tt2';
}
テンプレート
root/src/comment/list.tt2
[% META title = 'コメント一覧' -%]
<table>
[% FOREACH comment IN comments -%]
<tr>
<td>[% comment.statement %]</td>
<td>[% comment.user_id.login_name %]</td>
</tr>
[% END -%]
</table>
<p>
<a href="[% c.uri_for('form_create') %]">コメントする</a>
<a href="[% c.uri_for('/logout') %]">ログアウト</a>
</p>
[%# USE Dumper(Indent=1) -%]
[%# Dumper.dump(comments) %]
コメント投稿フォームを表示するメソッドを実装
コントローラー
Comment.pm
=head2 form_create
入力フォームを表示するメソッド
=cut
sub form_create : Local {
my ( $self, $c ) = @_;
# テンプレートをセット
$c->stash->{template} = 'comment/form_create.tt2';
}
テンプレート
root/src/comment/form_create.tt2
[% META title = 'コメント入力' -%]
<form method="post" action="[% c.uri_for('form_create_do') %]">
<table>
<tr><td>Comment:</td><td><textarea name="statement"></textarea></td></tr>
</table>
<input type="hidden" name="user_id" value="[% c.user.id %]">
<input type="submit" name="Submit" value="Submit">
</form>
<p>
<a href="[% c.uri_for('/comment/list') %]">コメント一覧へ</a>
<a href="[% c.uri_for('/logout') %]">ログアウト</a>
</p>
入力されたコメントをDBに保存する処理を実装
コントローラー
lib/MyBBS/Controller/Comment.pm
=head2 form_create_do
入力されたコメントをDBに保存する処理を実装します。
=cut
sub form_create_do : Local {
my ( $self, $c ) = @_;
# フォームで入力されたコメントと入力したユーザーのID(hidden)を取得します。
my $statement = $c->request->params->{statement} || 'N/A';
my $user_id = $c->request->params->{user_id} || 'N/A';
# DBに保存します。
my $comment = $c->model('DB::Comment')->create(
{ statement => $statement,
user_id => $user_id,
}
);
# コメント一覧画面ヘリダイレクトします。
$c->response->redirect($c->uri_for('list'));
}
ログイン機能
必要なモジュールを追加
ログイン機能の実装に必要なモジュールを追記します。
lib/MyBBS.pm
Authentication
Session
Session::Store::FastMmap
Session::State::Cookie
設定ファイル
mybbs.conf
<authentication>
default_realm dbic
<realms>
<dbic>
<credential>
class Password
password_field password
password_type clear
</credential>
<store>
class DBIx::Class
user_class DB::User
id_field login_name
</store>
</dbic>
</realms>
</authentication>
コントローラー
lib/MyBBS/Controller/Login.pm
=head2 INDEX
ログイン機能を実装します。
=cut
sub index : Path : Args(0) {
my ( $self, $c ) = @_;
# フォームに入力されたログイン名とパスワードを取得します。
my $login_name = $c->request->params->{login_name} || "";
my $password = $c->request->params->{password} || "";
# もしログインIDとパスワードがどちらも取得できたら、
if ( $login_name && $password ) {
# ログインを試みます。
if ($c->authenticate(
{ login_name => $login_name,
password => $password,
}
)
)
{
# ログインに成功したら、コメント一覧へリダイレクトします。
$c->response->redirect( $c->uri_for('/comment/list') );
return;
}
else {
# ログインに失敗したら、エラーメッセージをテンプレートにセットします。
$c->stash->{error_msg} = "Bad username or password.";
}
}
# ログインページを表示します。
$c->stash->{template} = 'login.tt2';
}
ログアウト
lib/MyBBS/Controller/Logout.pm
=head2 index
ログアウト処理を実装します。
=cut
sub index : Path : Args(0) {
my ( $self, $c ) = @_;
# ログアウトします。
$c->logout;
# トップページへリダイレクトします。
$c->response->redirect( $c->uri_for('/') );
}
ログインフォーム用テンプレート
root/src/login.tt2
[% META title = 'Login' %]
<!-- Login form -->
<form method="post" action="[% c.uri_for('/login') %]">
<table>
<tr>
<td>Login Name:</td>
<td><input type="text" name="login_name" size="40" /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" size="40" /></td>
</tr>
<tr>
<td colspan="2"><input type="submit" name="submit" value="Submit" /></td>
</tr>
</table>
</form>
<p>
[% IF c.user_exists %]
既に '[% c.user.login_name %]' としてログインしています。<br />
こちらから<a href="[% c.uri_for('/logout') %]">ログアウト</a>できます。
[% ELSE %]
ログインしてください
[% END %]
</p>
ログインチェック機能
ここでは、ログインしていないユーザーが、認証が必要なページに直接アクセスした場合にできないようにチェックする機能を実装します。
コントローラー
lib/MyBBS/Controller/Root.pm
=head2 auto
ユーザーがログイン認証済かどうか検査します、認証されていないユーザーはログインページヘリダイレクトします。
=cut
sub auto : Private {
my ( $self, $c ) = @_;
# とりあえず認証していなくてもログインページだけはアクセスできるようにします。
if ( $c->controller eq $c->controller('Login') ) {
return 1;
}
# 認証されていなければ、ログインページへリダイレクトします。
if ( !$c->user_exists ) {
$c->response->redirect( $c->uri_for('/login') );
return 0;
}
# 認証済ならば1を返します。
return 1;
}
Catalyst v5.66
動作確認
以上で、ひととおりの実装が終了しました。
下記の項目を確認してみます。
- ログインできること。
- ログイン名とパスワードは、foo/foo もしくは bar/bar
- ログインしていない状態では、ログイン画面以外のURLには行けない(ログイン画面に飛ばされる))こと。
- ログアウトできること。
- コメント一覧が表示できること。
- コメント投稿できること。
足りない機能
- フォーム・バリデーション
- コメントの削除
- ユーザー追加
- ユーザー情報の編集・削除
Reference
- Catalyst::Manual::Tutorial (English)
-
もう少し複雑なアプリケーション(書籍データーベース)を作る過程が、このチュートリアルで解説されています。
具体的には、
- ユーザーの役割(role)による権限(Authentication)の管理
- DBに保存されるパスワードをSHA-1でハッシュ化する方法。
- 揮発性メッージ(Flash)の使い方。
- FormFuを使ったフォームの生成やバリデーション。
などです。