モダンPerlの世界へようこそ

第9回 Jifty:一足早いクリスマスプレゼント

この記事を読むのに必要な時間:およそ 6 分

モデルを追加しよう

続いて,⁠ひとこと」を保存するモデルを用意しましょう。先ほどと同様に,シェルからヘルパースクリプトを実行します。

> jifty model --name Entry

Userモデルのほうはプラグインのほうで用意してくれた出来合いのスキーマを使いましたが,今度は自前でスキーマを用意します。MyApp::Recordのschemaブロックにこのようなカラムを定義してください。

use MyApp::Record schema {
  column body =>
    type is 'text';

  column user_id =>
    type is 'integer',
    refers_to MyApp::Model::User;

  column epoch =>
    type is 'integer',
    default is defer { time() };
};

一見裸のワードに見える単語がいくつも並んでいますが,この「is」はれっきとしたPerlの関数です(このような連結詞の存在がJifty的なDSLの顕著な特徴です⁠⁠。

refers_toはhas_aのような外部参照を指定するもの(ここでは「ひとこと」とユーザは一対一対応するのでMyApp::Model::Userという「レコード」を参照するようになっていますが,has_manyを実現したい場合は「refers_to MyApp::Model::UserCollection」のように「コレクション」を参照させます⁠⁠。deferは,Scalar::Deferというモジュールに由来する遅延評価のための関数です。

複雑なアクセスコントロールを実装する

このモデルは,だれもが好き勝手に更新できては困ります。データの読み取りは全員に認めますが,更新系の処理は自分の発言に対してのみできるようにしたいところです。

このような複雑なアクセスコントロールは,current_user_canというメソッドで行います。詳しくはJifty::Recordをご覧いただくとして,ここではread以外の処理はユーザIDが一致したときのみ実行可能にしています。

sub current_user_can {
  my ($self, $right, %args) = @_;

  return 1 if $right eq 'read';
  return 1 if $args{user_id} && $self->current_user->id == $args{user_id};
  return 1 if $self->user_id && $self->current_user->id == $self->user_id;

  return $self->SUPER::current_user_can($right, %args);
}

スキーマを更新しよう

モデルの処理としてはこれでひとまず完成ですが,このままではサーバを再起動してもデータベースの内容は更新されません。スキーマを更新するには,⁠1)新しいモデルに,どのバージョンのデータベースから有効になるか指定する,⁠2)設定ファイル内のデータベースのバージョンを更新する,⁠3)スキーマをセットアップする,という手順を踏む必要があります。

まずはlib/MyApp/Model/Entry.pmの最後にデータベースのバージョンを返すサブルーチンを用意します。

sub { '0.0.2' }

続いてetc/site_config.ymlの該当の場所に,新しいデータベースのバージョンを書き ます。

  Database:
    Version: 0.0.2

それが済んだら,ヘルパースクリプトを利用してスキーマを更新しましょう。

> jifty schema --setup

これで新しいモデルがデータベースに登録されました。もちろん生のSQLを書く必要はありません。

ディスパッチャもDSLで

モデルができたところで,今度はそれを表示するためのビューとディスパッチャの実装にかかりましょう。ビューやディスパッチャには便利なヘルパーは用意されていないので,自分で実装していきます。

エディタでこのようなlib/MyApp/Dispatcher.pmを用意してください。

package MyApp::Dispatcher;

use strict;
use warnings;
use Jifty::Dispatcher -base;

under 'user/*' => [
  run {
    my ($name) = ($1);
    my $user = MyApp::Model::User->load_by_cols(name => $name);
    set user => $user;
  },
  on qr/(\d+)/ => run {
    my ($epoch) = ($1);
    my $user = get('user');
    my $entry = MyApp::Model::Entry->load_by_cols(
      user_id  => $user->id,
      epoch    => $epoch,
    );
    set entry => $entry;
    show '/entry';
  },
  on '' => show '/list',
];

1;

ここではhttp://localhost:8888/user/<username>のようなアドレスにアクセスしたらそのユーザのひとこと一覧を,http://localhost:8888/user/<username>/<epoch>にアクセスしたら,そのときそのユーザがつぶやいたひとことを表示させようとしています。詳しくはJifty::Dispatcherをご覧いただくとして,ここではCatalystのチェーンドアクションと同じように,段階ごとに適切な処理を行うようになっています。

なお,ディスパッチャのなかでモデルにアクセスしているのは,将来ユーザやひとことが存在していなかった場合にエラー画面に飛ばすためです。getやsetは,Catalystでいうstashのデータを読み書きするためのもの。ビューではここでstashに登録したデータを使って必要な画面を作成していきます。

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/