Perl Hackers Hub

第33回MojoliciousでかんたんWebアプリケーション開発(2)

前回の(1)こちらから。

MojoliciousとMVC

ここでは(1)で生成したmyapp.plファイルのコードをもとに、多くのWebアプリケーションフレームワークで採用されているMVCModel-View-ControllerアーキテクチャとMojoliciousの対応について解説します。

コントローラ

myapp.plにおいてコントローラに相当するのはリスト1のサブルーチン部分です。

リスト1 ルーティングとコントローラの定義
get '/' => sub {
  my $c = shift;
  $c->render(template => 'index');
};

リクエストとコントローラを紐付けるルーティングの定義の基本的な形式は次のようになります。

HTTP メソッド 'URL パターン' => sub { ... };

get '/'部分で処理すべきリクエストを定義し、リクエストがマッチした場合に実行するサブルーチン(コントローラ)を定義します。リスト1ではHTTPのGETメソッドで/というパターンにマッチするURLにリクエストがきたときに、定義したサブルーチンが実行されます。

Mojoliciousの内部において、ルーティングはMojolicious::Routesが担当し、コントローラはMojolicious::Controllerが担当します。

実行されるサブルーチンの第1引数にはMojolicious::Controllerのインスタンスが渡されます。このインスタンスを利用して、リクエストに含まれるデータの取得やビューの決定などを行います。

Mojolicious::Routesではプレースホルダやネストしたルーティングを活用した認証処理など、より複雑なルーティングも定義できます。詳しくはルーティングガイドを参考にしてください。

ビュー

ビューはMojolicious::Controllerrenderメソッドで呼び出します。myapp.plではリスト1の次の部分です。

$c->render(template => 'index');

Mojolicious 内部において、ビューはMojolicious::Rendererが担当し、デフォルトではMojo::Templateがレンダリングします。このコードではtemplate => 'index'によって、DATAセクションに定義してあるindex.html.epをテンプレートとしてレンダリングしますリスト2⁠。

リスト2 index.html.epテンプレート
@@ index.html.ep
% layout 'default';
% title 'Welcome';
Welcome to the Mojolicious real-time web framework!

テンプレートの命名規則はテンプレート名.フォーマット.ハンドラです。引数による指定がない場合、テンプレート名はコントローラ名/アクション名もしくはルート名フォーマットはhtml、ハンドラはepとなります。myapp.plではコントローラ名やアクション名、ルート名を定義していないので、templateによってテンプレート名を指定しています。

テンプレートではリスト3のようなEmbedded Perlと呼ばれる形式でPerlコードを埋め込むことができます。また、Mojolicious::Plugin::DefaultHelpersMojolicious::Plugin::TagHelpers開発者側で定義したヘルパーを使うことができます。リスト2のテンプレートではMojolicious::Plugin::DefaultHelperslayoutヘルパーとtitleヘルパーを使用しています。

リスト3 Embedded Perl
<% Perl コード %>
<%= Perl コードを評価しXML エスケープして置換 %>
<%== Perl コードを評価して置換 %>
<%# コメント %>
<%% "<%" に置換 %>
% 行単位の"<% Perl コード %>"
%= 行単位の"<%= Perl コード %>"
%== 行単位の"<%== Perl コード %>"
%# コメント行
%% "%" に置換

Mojolicious::RendererではこのほかにもJSONJavaScript Object Notationやバイナリデータのレンダリング、クライアントからのリクエストに応じたレンダリングの切り替え(コンテントネゴシエーション)などができます。詳しくはレンダリングガイドを参考にしてください。

モデル

Mojolicious はモデルをサポートしていません。myapp.plでもモデルに相当する部分はありません。モデルは開発者側で設計して実装する必要があります。

プロトタイプや小規模開発ではコントローラ内部にモデルのロジックを書いてもさほど問題はありませんが、ある程度の規模の開発や拡張性が必要な場合は、コントローラからモデルを分離して実装するのが効果的です。モデルの実装については後述します。

MojoliciousによるWebアプリケーション開発

ここでは簡単な掲示板の作成を通して、Mojoliciousアプリケーションの開発を体験してみましょう。

事前に次のコマンドを実行して、アプリケーションのひな型となるファイルを生成してください。

$ mojo generate lite_app mojo_bbs.pl

データベースを用意する

今回作成するアプリケーションではデータベースにSQLite3を利用します。各プラットフォームに合わせてインストールしてください。

掲示板のスキーマはリスト4になります。mojo_bbs.sqlファイルにSQLを保存して、次のコマンドでmojo_bbs.dbを作成してください。

$ cat mojo_bbs.sql | sqlite3 mojo_bbs.db
リスト4 掲示板のスキーマ
CREATE TABLE IF NOT EXISTS "comment" (
  "id" INTEGER PRIMARY KEY,
  "body" TEXT NOT NULL,
  "created_at" DATETIME NOT NULL
);

データベースを設定する

続いて、アプリケーションでデータベースを扱えるように設定します。

接続するデータベースの情報は外部ファイルで管理できたほうが取り回しが良いため、mojo_bbs.jsonファイルにJSONフォーマットでリスト5の内容を記述します。

リスト5 mojo_bbs.json
{
  "connect_info": [
    "dbi:SQLite:dbname=mojo_bbs.db", "", "",
    {
      "AutoCommit": 1,
      "PrintError": 0,
      "RaiseError": 1,
      "sqlite_unicode": 1
    }
  ]
}

mojo_bbs.plにはリスト6の内容を追加します。JSONConfigプラグインを利用することで、先ほどのmojo_bbs.jsonファイルの内容を参照できます。また、アプリケーションのインスタンスにdbhという属性を用意し、データベースハンドラを利用できるようにコールバックを定義します。このコールバックは毎回実行されるわけではなく、属性の値が設定されていない場合にのみ実行され、結果が属性に設定されます。

リスト6 データベース設定の追加
use DBI;

plugin 'JSONConfig';

app->attr(
  dbh => sub {
    my $self = shift;
    my $dbh = DBI->connect(
      @{$self->config->{connect_info}}
    ) or die $DBI::errstr;
    return $dbh;
  }
);

コントローラを実装する

ルーティングとコントローラの実装はリスト7のようになります。コントローラ内部では、先ほど用意した$c->app->dbhからデータベースハンドラを取得して操作を行います。

リスト7 ルーティングとコントローラの実装
use Time::Piece;

get '/' => sub {
  my $c = shift;
  $c->redirect_to('list');
} => 'index';

get '/comments' => sub {
  my $c = shift;
  my $comments = $c->app->dbh->selectall_arrayref(
    q{SELECT id, body, created_at FROM comment},
    {Slice => {}},
  );
  $c->stash(comments => $comments);
  $c->render;
} => 'list';

post '/comments' => sub {
  my $c = shift;
  my $url = $c->url_for('list');
  if (my $body = $c->param('body')) {
    my $sth = $c->app->dbh->prepare(
      q{INSERT INTO comment (body, created_at) VALUES (?, ?)});
    $sth->execute($body, localtime->strftime('%F %T'));
    my $id = $c->app->dbh->last_insert_id('', '', '', '');
    $url->fragment($id);
  }
  else {
    $url->fragment('post');
  }
  $c->redirect_to($url);
} => 'post';

get '/comments'ではテンプレートでも利用できるように、データベースから取得した結果をstashに渡します。post '/comments'は少々長いコードですが、コメントの入力があった場合にデータベースに登録し、/comments#idにリダイレクトしています。

テンプレートを追加する

最後の仕上げにコメントの一覧と投稿フォームを描画するためのテンプレートをDATAセクションに追加しますリスト8⁠。

リスト8 一覧画面のテンプレート
@@ list.html.ep
% layout 'default';
% title 'MojoBBS';
<dl>
  % for my $comment (@$comments) {
  <dt id="<%= $comment->{id} %>">
    <a href="#<%= $comment->{id} %>"><%= $comment->{id} %></a>
    <%= $comment->{created_at} %>
  </dt>
  <dd><pre><%= $comment->{body} %></pre></dd>
  % }
<dl>
<hr>
<form id="post" action="<%= url_for('post') %>" method="POST">
  <div><textarea name="body" rows="5"></textarea></div>
  <div><input type="submit" value="Post"></div>
</form>

一覧表示のためのget '/comments'にはlistというルート名を設定してあるので、テンプレート名はlist.html.epとなります。テンプレートではstashを通して渡されたデータをもとにコメント一覧をレンダリングします。

動作を確認する

それではアプリケーションを実行してみましょう。前節と同様にmorboを使います。今回は実行時に「設定ファイルをロードした」というログが画面に表示されます図4⁠。

図4 アプリケーションの実行
$ morbo mojo_bbs.pl
Server available at http://127.0.0.1:3000
[Tue Apr 7 20:02:19 2015] [debug] Reading configuration file "/path/to/mojo_bbs.json"
Ctrl-Cで終了

ブラウザでhttp://127.0.0.1:3000/にアクセスして、コメントの投稿や一覧の表示などの一通りの動作を確認してみてください。

<続きの(3)こちら。>

おすすめ記事

記事・ニュース一覧