Mojolicious::Liteは一つのファイルの中に全部突っ込んでWebアプリが作れるということなので、今自分が気になってるものを習作の意味を込めて全部詰め込んでCRUDアプリを作ってみました。jQueryも入れたかったけど書いていてカオスになったのでSNBinderの補助とページャ以外は無しにしました。
色々突っ込んでみたものは以下
- Teng
- ページャ(プラグイン)
- inflate/deflate
- triggerの代替 Class::Method::Modifiers
- Devel::KYTProf
- Xslate
- Mojolicious::Liteで継承
- SNBinder
- コンセプト
雑感
Teng
nekokakさん作の軽量ORMであるTeng。
- Skinnyの後継
- DBIの薄いラッパー
ということで、シンプルさを残しつつさらっと書きたいことはさらっと書けるように配慮されてて良い感じです。Skinnyと使い方はあまり変わらない事や、そもそもシンプルなのでコードを追いかけやすいということもあって理解はしやすそうです。ただドキュメントはまだまだ少ない感じで【実践Teng — Teng-Doc v0.01 documentation】という非公式のページも出来ているものの未掲載も残っていて、使用例などと合わせてドキュメントの充実が待たれているといった所でしょうか。
Xslate
なんだかとてつもなく速いテンプレートエンジン。自分はまだテンプレートエンジンの速度を気にしないといけないような状況には出会っていないので、速さというものの有り難さが分らないのですが速いそうです。また、テンプレートの書き方を選択出来るようでTTの文法で書く事も出来るようです。あと継承が扱えるのが良い感じです。テンプレートエンジンで継承は思いの外対応していないことが多いので。
SNBinder
データとテンプレートの組み合わせたものを一つの部品として見て、画面に貼付けて行く事で画面を構築して行くというコンセプトの、jsのテンプレートエンジン。この考え方は自分の大好きなJavaのWebアプリケーションフレームワーク、WicketのViewと似ていて非常に気に入っています。よくあるテンプレートエンジンのテンプレートの中にコードを埋め込んで画面を作る方法よりテンプレートがHTML的にプレーンでシンプルになるのが良いと思います。ただSNBinderはあまりにも機能がミニマムなのであえて使うほどでも無いかなとも思っています。後、データはサーバでViewがクライアントにあるので記述がシームレスに出来ないのもまた障壁のひとつかなと。Wicketだと全てJavaでサーバサイドで書けるので書いていて本当に幸せです。Java使いの方はWicketを使いましょう!
メモ
以下今回コードを書いていてのメモ
triggerの代替 Class::Method::Modifiers
Skinnyで言うtriggerがTengでは廃止されていて、代替としてClass::Method::Modifiersを使う事を提案されています。今回はTimestampとSoftDeleteの振る舞いを表現するために使ってみました。具体的にはcreated_at,updated_at,deleted_atの3カラムを用意してinsert,update,deleteの操作でそれぞれ自動的に日付が入力されるようにしました。また検索時にSoftDeleteされたものが検索の対象から排除されるようにしました。Class::Method::Modifiersの使い方を理解していないのでこれで良いのかは怪しいですが、それぞれsearchやinsertなどの動作を変更して*_atの値を操作しています。
package Message;
use parent 'Teng';
__PACKAGE__->load_plugin('Pager');
use Class::Method::Modifiers;
around delete => sub {
my ($orig, $self, $table_name, $delete_condition) = @_;
my $update_row_data = {
deleted_at => DateTime->now->set_time_zone('Asia/Tokyo'),
};
$self->update($table_name, $update_row_data, $delete_condition);
};
before update => sub {
my ($self, $table_name, $update_row_data, $update_condition) = @_;
if (!$update_row_data->{deleted_at}) {
$update_row_data->{updated_at} = DateTime->now->set_time_zone('Asia/Tokyo');
}
};
before insert => sub {
my ($self, $table_name, $row_data) = @_;
$row_data->{created_at} = DateTime->now->set_time_zone('Asia/Tokyo');
$row_data->{updated_at} = DateTime->now->set_time_zone('Asia/Tokyo');
};
before fast_insert => sub {
my ($self, $table_name, $row_data) = @_;
$row_data->{created_at} = DateTime->now->set_time_zone('Asia/Tokyo');
$row_data->{updated_at} = DateTime->now->set_time_zone('Asia/Tokyo');
};
before search => sub {
my ($self, $table_name, $search_condition, $search_attr) = @_;
$search_condition->{deleted_at} = \'IS NULL';
};
before search_with_pager => sub {
my ($self, $table_name, $where, $opts) = @_;
$where->{deleted_at} = \'IS NULL';
};
1;
my $result = $model->single('message', {
id => $self->param('id')
});
$model->fast_insert('message', {
message => $self->param('message'),
});
$model->update('message',
{
message => $self->param('message'),
},
{
id => $self->param('id'),
}
);
$model->delete('message', {
id => $self->param('id'),
});
my ($rows, $pager) = $model->search_with_pager(
'message',
{
},
{
order_by => 'id DESC',
page => $page,
rows => 3,
}
);
Mojolicious::LiteでのXslateの継承
Xslateで継承を使わないというのはありえないと思うのですが、Mojolicious::Liteでのサンプルが見つからなかったのでちょっと悩みました。実際はhogehoge.txで定義してincludeやcascadeなどでそれを指定するだけでした。
@@ base.tx
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">//<![CDATA[
google.load('jquery', '1.4.4');
//]]></script>
<script src="https://github.com/snakajima/SNBinder/raw/master/snbinder-0.5.3.js" type="text/javascript"></script>
: include main_js
<title>Mojolicious::Lite + Teng + SNBinder + Xslate (+ SQLite/OnMemory)</title>
</head>
<body>
: block content -> { }
</body>
</html>
@@ form.tx
<form method="post" action="<: $path :>">
: if ($id) {
<input type="hidden" name="id" value="<: $id :>">
: }
<p><textarea name="message" cols="50" rows="10" ><: $message :></textarea></p>
<p><input type="submit" /></p>
</form>
@@ index.html.tx
: cascade base
: override content -> {
: include form { path => "/create" }
<div id="main" style="display:none;">
<p>Accessing server ...</p>
</div>
: }
SQLite/OnMemory
実はSQLiteってオンメモリ(正しくはインメモリかな?)で動作するんですね。知りませんでした。これのおかげで必要なCPANモジュールが入っていれば本当に一つのファイルで動作するアプリになりました。ただしオンメモリなので切断されると当然データが消えます。また、複数プロセスから接続は出来ないようでStarmanなどのHTTPDと組み合わせて動かすと勝手に複数ワーカーが起動して動作するため上手く動きません。オンメモリでもDB名を付けることが出来たり、コネクションプールで接続を維持し続ける方法があったりするんじゃないかとは思うのですが、ちょっと良くわかりません。H2DBではそういうことが出来た覚えがあるのですがさて。
my $model = Message->new(
dbh => DBI->connect(
'dbi:SQLite:dbname=:memory:', '', '',
{
RaiseError => 1,
PrintError => 0,
AutoCommit => 1,
sqlite_unicode => 1,
}
)
);
コード
コードはGistに上げています
具体的にはgistに書いた。
完全分散モードが結構癖が強い感じなので気をつけたい。