読者です 読者をやめる 読者になる 読者になる

ゆーすけべー日記

はてなBlogってどーなの!?

全裸で学ぶMVC事始め

Web Essay Perl Mojolicious zenrize

一般的なWeb Application Framework(WAF)ではMVCという設計及び実装における概念が取り入れられています。 MVCに従ってつくるのが全てではありませんが、 WAFを使うと共に、一度はMVCを用いたWebアプリの開発経験はしておいた方がよいと思います。 MVCはモデル(Model)、ビュー(View)、コントローラ(Controller)の3つの単語を組み合わせた言葉で、 この3つで概念が成り立っています。 クライアントがWebに対してリクエストをした時に、これら3つがそれぞれ連動して結果を返します。 一般的には以下のような処理経路をたどります。

  1. クライアントがWebサイトにリクエスト
  2. コントローラがリクエストの処理を行い、モデルとビューを動かす
  3. 必要に応じてモデルを呼び出す
  4. 結果のデータをビューに渡す
  5. ビューがHTML化などをしたものをクライアントに表示する

MVC

MVCという概念を学ぶにはこうした一連の流れを網羅する小さなサンプルをつくるのが一番取っ付きやすい気がします。 そこで今回は「全裸で学ぶMVC事始め」と題し、 zenrizeというテキスト処理を行うだけのWebサービスをつくってみたいと思います。

zenrize

zenrizeという聞き慣れない言葉が出てきましたが、これは単に僕がつくった言葉です。 zenrizeは「全裸bot」というsugyanがつくった 非常にくだらないTwitterボットの内部で行われている処理を指します。 全裸botではテキストの適切な位置に「全裸で」というフレーズを付け加えることにより、 Twitterのつぶやきを丸裸にします。 例えば、

これから歯磨きをするよ

という文章はおそらく以下のようにzenrize処理されます。

これから全裸で歯磨きをするよ

全裸botでは作者sugyanによる試行錯誤が行われているのですが、 今回はzenrizeを単純に「文章内の動詞の前に【全裸で】を必ずつける」と定義してみます。

Yahoo! 形態素解析APIを使う

日本語の文章内の品詞を特定するには形態素解析と呼ばれる処理をします。 お手軽に実装するには、Yahoo! APIで提供されている形態素解析APIを利用したいところです。

一からWeb APIにリクエストをし結果をパースするプログラムを書かずに今回は、 CPANモジュールから「WebService::YahooJapan::WebMA」を使ってみたいと思います。

簡単なスクリプトでzenrizeがされるかを試してみましょう。

use strict;
use warnings;
use WebService::YahooJapan::WebMA;
use utf8;

$WebService::YahooJapan::WebMA::APIBase =
  'http://jlp.yahooapis.jp/MAService/V1/parse';
my $api = WebService::YahooJapan::WebMA->new( appid => 'yourappid', );

my $result = $api->parse( sentence => '起きたのでご飯を食べます' )
  or die $api->error;
my $ma_result = $result->{ma_result};

my $text = '';
for my $word ( @{ $ma_result->{word_list} } ) {
    if ( $word->{pos} eq '動詞' ) {
        $text .= "全裸で$word->{surface}";
    }
    else {
        $text .= $word->{surface};
    }
}

print $text . "\n";

結果は以下のようになりました。

全裸で起きたのでご飯を全裸で食べます

いい感じです。 Yahoo! のWeb APIはデータリソースとし、それを扱うこうしたプログラム部分が以降モデルとなります。

Sinatraライクに実装する

MVCを理解するためにSinatraライクなPerlのWAF、Mojolicious::Liteを使います。 これだと1ファイルにMVCの全ての要素を詰め込めて俯瞰することができるのではないでしょうか。

まずはモデルを実装します。これはWebService::YahooJapan::WebMAのインスタンスを保持し、 zenrizeメソッドを上記のスクリプトと同じように実装した簡単なモジュールです。

package Zenra::Model;
use WebService::YahooJapan::WebMA;
use utf8;

$WebService::YahooJapan::WebMA::APIBase =
  'http://jlp.yahooapis.jp/MAService/V1/parse';

sub new {
    bless { yahoo_ma =>
          WebService::YahooJapan::WebMA->new( appid => 'yourappid, ), },
      shift;
}

sub zenrize {
    my ( $self, $sentence ) = @_;
    return unless $sentence;
    my $api       = $self->{yahoo_ma};
    my $result    = $api->parse( sentence => $sentence ) or return;
    my $ma_result = $result->{ma_result};

    my $result_text = '';
    for my $word ( @{ $ma_result->{word_list} } ) {
        if ( $word->{pos} eq '動詞' ) {
            $result_text .= "全裸で$word->{surface}";
        }
        else {
            $result_text .= $word->{surface};
        }
    }
    return $result_text;
}

次にコントローラ部分を実装します。と、その前にアプリケーションの設定でモデルを登録しているコードも含まれています。 「get」「post」メソッドでそれぞれのメソッドに応じたルーティングを設定し、 リクエストを処理、ビューを用いてHTMLをレンダリングしています。

package main;
use Mojolicious::Lite;
use Encode;

app->helper(
    model => sub {
        Zenra::Model->new;
    }
);

get '/' => sub {
    my $self = shift;
    $self->render('index');
};

post '/result' => sub {
    my $self        = shift;
    my $text        = $self->req->param('text');
    my $result_text = $self->app->model->zenrize( decode_utf8($text) )
      or return $self->redirect_to('/');
    $self->stash->{result} = $result_text;
    $self->render('result');
};

app->start;

最後にビューの実装です。これはMojo::Templateと呼ばれるMojoliciousで使われているテンプレートの記述です。

@@ index.html.ep
<form action="/result" method="post">
<textarea rows="3" cols="60" name="text"></textarea>
<br />
<input type="submit" value="zenrize" />
</form>

@@ result.html.ep
<p>
<b><%= $result %></b>
</p>
<a href="/">戻る</a>

Mojolicious:Liteではこうしたビューをデータセクションに書くことができます。

では繋ぎ合わせた全てのコードをお見せいたします。

#!/usr/bin/env perl
package Zenra::Model;
use WebService::YahooJapan::WebMA;
use utf8;

$WebService::YahooJapan::WebMA::APIBase =
  'http://jlp.yahooapis.jp/MAService/V1/parse';

sub new {
    bless { yahoo_ma =>
          WebService::YahooJapan::WebMA->new( appid => 'yourappid', ), },
      shift;
}

sub zenrize {
    my ( $self, $sentence ) = @_;
    return unless $sentence;
    my $api       = $self->{yahoo_ma};
    my $result    = $api->parse( sentence => $sentence ) or return;
    my $ma_result = $result->{ma_result};

    my $result_text = '';
    for my $word ( @{ $ma_result->{word_list} } ) {
        if ( $word->{pos} eq '動詞' ) {
            $result_text .= "全裸で$word->{surface}";
        }
        else {
            $result_text .= $word->{surface};
        }
    }
    return $result_text;
}

package main;
use Mojolicious::Lite;
use Encode;

app->helper(
    model => sub {
        Zenra::Model->new;
    }
);

get '/' => sub {
    my $self = shift;
    $self->render('index');
};

post '/result' => sub {
    my $self        = shift;
    my $text        = $self->req->param('text');
    my $result_text = $self->app->model->zenrize( decode_utf8($text) )
      or return $self->redirect_to('/');
    $self->stash->{result} = $result_text;
    $self->render('result');
};

app->start;

__DATA__

@@ index.html.ep
% layout 'default';
<form action="/result" method="post">
<textarea rows="3" cols="60" name="text"></textarea>
<br />
<input type="submit" value="zenrize" />
</form>

@@ result.html.ep
% layout 'default';
<p>
<b><%= $result %></b>
</p>
<a href="/">戻る</a>

@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
<head><title><%= title %></title></head>
<body style="width:500px;margin:0 auto;"><h1>zenrize</h1><%= content %></body>
</html>

これを「zenra.pl」などと保存し実行したりplackupで指定したりするとWebブラウザからzenrizeを試すことができます。

こちらがトップの入力画面です。

zenrize1

結果です。

zenrize2

見事にzenrizeされたテキストがWebに表示されているではないですか!

まとめ

MVCを理解するために駆け足でzenrizeという題材の小さなWebアプリをつくってみました。 公開するにはちょっと不安な要素がいくつかありますが手元で確認するにはよい例だと思います。 そろそろ暖かくなる季節なので、「全裸で」Webアプリをつくるのもいいと思います!

お知らせ

メルマガ「ゆーすけべーラジオ」でもWebサービスにまつわるエピソードを連載中です。 よろしければお試しを!