A freelance programmer, coding, debugging, and even bug fixing his life...

2008年11月9日

Catalystで一行掲示板 Add to Delicious Bookmark この記事をクリップ! Yahoo!ブックマークに登録

Fukuoka Perl Mongers第12回定例勉強会で発表させていただいた資料を公開します。アプリケーションとしては、かなり貧弱なものですが、Catalystの触りの部分だけでも理解していただければ幸いです。

Catalystで一行掲示板

目的

  1. とにかくCatalystで動作するものを作ってみる。
  2. 実装は簡単に。
    • ログイン/ログアウトは実装する。
    • DBのテーブルリレーションは一箇所くらいやってみる。
  3. DBは設置が簡単なSQLiteを使う。

今回実装した機能

  1. 一行掲示板
  2. ログイン機能
  3. 一覧表示機能
  4. コメント投稿機能
  5. バリデーションは実装していません。
  6. 画面デザインは全くやってません。

開発環境

  1. Mac OS X 10.5
    • ターミナル
    • テキストエディタ
    • Webブラウザ
  2. Perl 5.8.8
  3. Sqlite3
  4. Catalyst (5.7014)
  5. DBIx::Class (0.08010)
  6. DBIx::Class::Schema::Loader (0.04005)
  7. 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を使ったフォームの生成やバリデーション。

などです。

0 コメント:

コメントを投稿