Amon2+TengとMVC的Modelの実装

前回のTengのリファレンスに続き、今度はAmon2Tengを利用するケース。これは amon2-setup.pl を実行する際に flavor に Teng を指定するとすぐに利用できるようになります。以下、一応手順。

  1. Amon2, Teng, Amon2::Setup::Flavor::Teng が入ってない場合はインストール
    $ cpanm Amon2 Teng Amon2::Setup::Flavor::Teng
  2. 任意の場所で Amon2 のスケルトンを作成。この際 flavor に Teng を指定
    $ amon2-setup.pl --flavor=Basic,Teng MyApp

amon2-setup.pl を実行すると指定したプロジェクト名(上の例では MyApp)のディレクトリとともにスケルトンが作成されます。

use File::Spec;
use File::Basename qw(dirname);
my $basedir = File::Spec->rel2abs(File::Spec->catdir(dirname(__FILE__), '..'));
my $dbpath = File::Spec->catfile($basedir, 'db', 'development.db');
+{
    'DBI' => [
        'dbi:mysql:DB_NAME', 'DB_USER', 'DB_PASSWORD',
        +{
            'mysql_enable_utf8' => 1,
        }
    ],
};

で、MyApp/config/development.pl に接続したいDBの設定を追記すると、

package MyApp::Web::Dispatcher;
use strict;
use warnings;
use utf8;
use Amon2::Web::Dispatcher::Lite;

any '/' => sub {
    my ($c) = @_;

    my $row = $c->db->single({'id' => 1});

    return $c->render('index.tt', {
        'test' => $row->name,
    });
};

といった具合(ここで触ってるのは MyApp/lib/MyApp/Web/Dispatcher.pm)に Teng を利用してDB接続できるようになります。

ただこのままだとコントローラに全部実装しそうになる……というか、モデルってどうしたら良いの?ってのは

でも触れられてるんですが、この辺は自分も前々からどうすんのが良いのかよく分かんなくてちょっと悩んでました。が、MVCを意識したModelの具体的な実装とその考察の方で軽く紹介されてた Amon2::Plugin::Model が良かったので、そちらを利用した実装例も紹介します。

Amon2::Plugin::Model はCPANで公開されてるプラグインではないので、公開されてるgithubからダウンロードしてきます。git clone を使ってもいいんですけど、Amon2::Plugin::Model というファイル名のまま clone されちゃうのでちょっと悲しい。

ファイルの置き場はちょっと考えどころなんですが、今回はMyAPPディレクトリ直下に vendor/Amon2/Plugin を作成してそこに Model.pm として設置します。

で、lib/MyApp.pmで Amon2::Plugin::Model をロードさせます。このファイルの一番下など任意の場所に

use lib qw/vendor/;
use Amon2::Plugin::Model;
__PACKAGE__->load_plugin('Model');

と追記すると、MyApp/lib/MyApp/Web/Dispatcher.pm で

any '/' => sub {
    my ($c) = @_;

    my $user  = $c->model('User');

    return $c->render('index.tt', {
        'content' => $user->any_method,
    });
};

といった具合に利用できるようになります。プラグインで $c に追加されるmodelメソッドはlib/MyApp/Model から引数に指定したモデルを探しだし、それを $c を引数にしてコンストラクトしてくれます。返り値は指定したモデルのオブジェクトとなります。

Amon2::Plugin::Model のソースを読むと

my $model_class = sprintf '%s::%s', $self->__class_prefix, (ucfirst $name);
$self->{__models}{$name} = $model_class->new(
    c => $self,
);

という形でオブジェクトを作成するので、される側は

package MyApp::Model::User;

use strict;
use warnings;

sub new {
    my ($class, %obj) = @_;
    # 今回はTengオブジェクトだけ欲しいので。
    # この結果、このクラスメソッドでは$self->{'db'}経由でTeng利用できるように。
    bless {'db' => $obj{'c'}->db}, $class; 
}

sub get_user {
    my ($self, $id) = @_;

    return $self->{'db'}->single('user', {'id' => $id});
}
1;

みたいに受けておくと、$obj{‘c’} に Amon2 のコントローラ $c が入ってるので(結合度と相談しながら)、よきに計らって利用くださいませ、となります。