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

Perl日記

PerlとかRubyとかPHPとかPythonとか

MVCその4

続き。
WEB+DB PRESS Vol.44

Model

Modelとは何でしょうか。Modelは、アプリケーションの中枢です。今回のプログラムは何(What)をするプログラムでしょうか。HTMLを表示する? JSONを出力する? いえ、それはHowであってWhatではありません。このプログラムの"What"は「動画を検索する」です。この「動画を検索する処理」を担当するのがModelに相当します。
WEB+DB PRESS Vol.44 P170

僕がアレンジしたので置き換えると、「はてなタグ検索する処理」がModelに当たるってことか。

Controller

Controllerはユーザが入力した生データを受け取る部分(コンポーネント)です。生データを受け取って、Modelに渡します。するとModelが検索処理を行います。
WEB+DB PRESS Vol.44 P170

というわけで

Modelを分離してみる。

 一枚岩プログラムからViewを分離しました。そこから今度はModelを分離します。動画を検索するというドメインロジックを抽出してモジュール化するのです。インタフェースは何でもよいが、Modelは共通、ということを述べました。これを逆に考えると、Webアプリケーションと異なるインタフェース、たとえばCUIのコマンドラインスクリプトからも使えるようにモジュール化することができれば、そのモジュールはModelたり得るわけです。

なるほど。

lib/MyHatena/Tag.pm
package MyHatena::Tag;
use strict;
use warnings;
use base qw/Class::Accessor::Fast/;
our $VERSION = 0.01;
use LWP::UserAgent;
use Params::Validate qw/:all/;
use URI;
use XML::Simple;
use Encode qw/encode/;
use MyHatena::Tag::Result;

__PACKAGE__->mk_accessors(qw/title link/);

my $HATENA_TAG_API = "http://b.hatena.ne.jp/t/";

sub search {
  my $class = shift;
  my ($query) = validate_pos(@_, { type => SCALAR });

  my $uri = URI->new($HATENA_TAG_API.$query);
  $uri->query_form( sort => 'eid', mode => 'rss' );

  my $ua = LWP::UserAgent->new;
  $ua->agent(join('/', __PACKAGE__, __PACKAGE__->VERSION));

  my $res = $ua->get($uri);
  return $res->status_line if $res->is_error;

  my @entries = $class->_parse_xml(\$res->content);

  return MyHatena::Tag::Result->new(\@entries);
}

sub _parse_xml {
  my $class = shift;
  my ($xml_ref) = validate_pos(@_, { type => SCALARREF });
  my $parser = XML::Simple->new;

  my $xml;
  eval { $xml = $parser->XMLin($$xml_ref) };
  die $@ if $@;

  my @entries;
  for my $entry (@{$xml->{item}}) {
    push @entries, $class->new({
      title => encode('utf8', $entry->{title}),
      link  => $entry->{link},
    });
  }

  return @entries;
}

1;
__END__
lib/MyHatena/Tag/Result.pm
package MyHatena::Tag::Result;
use strict;
use warnings;

sub new {
  my $class = shift;
  my $self = shift || [];
  bless $self, $class;
}

sub entries {
  my $self = shift;
  return @$self;
}

1;
__END__
テスト

Modelを分離することができたので、プログラムのメインロジックである「検索処理」をテストすることができる。
もうなんていうか感動。
今まで関わってきたアプリケーション開発はこんなのなかったからなあ。
なのでまずは、検索処理だけするモジュールをロードしてちゃんと出力されるかチェック。

#!/usr/local/bin/perl
# tag_search.pl
use strict;
use warnings;
use feature qw/say/;
#use lib qw/lib/;
use FindBin::libs;
use MyHatena::Tag;

my $query = shift or die "usage: $0 <term>";

# MyHatena::Tag::Resultが返ってくる
my $result = MyHatena::Tag->search($query)
  or die "Can't search by [$query].";

for my $entry ($result->entries) {
  say $entry->title;
  say $entry->link;
}

__END__
$ ./tag_search.pl Perl
shuraba.com - Perl: 変数・定数
http://www.shuraba.com/?p=computing/perl/value
Perl - スカラー変数
http://www.booran.com/menu/perl/scalar.html
配列の基礎 - Perl入門〜サンプルコードによるPerl入門〜
http://tinyurl.com/yz7y2yb
………

ok。