Akihiro's Programmer Blog

Technology Notes for Personal

Amon2で簡易Modelを作る

 Amon2Modelを実装するにはどうすればいいか試行錯誤してみました。

 flavorBasicを想定しています。


内容


まずはググる

 Amon2 Modelでググって以下のサイトを見つけました。


 すでにPluginとして公開されている方がいらっしゃいました。

 ならこれを使えばすぐにModelを実装できるのでは?

 ということで試してみました。


とりあえず簡易でいい

 「 Amon2::Plugin::Model というのを書いて使っているので公開してみた」を見てもらえればなのですが、試してみて何となくModelクラスが冗長な感じがしてしまいました。

 もっと簡単な記述でささっと使えないものかなーと思ったので、上記の2サイトを参考にしながら実装してみました。


実装

 まずはどんな感じで使いたいかを考えてみます。

 実装要件は以下。

  • Modelクラスは最低限の記述で
  • Web側からの呼び出しは参考サイトみたいに
  • O/Rマッパー「Teng」を使用
  • 今回はDBとしてmysqlを想定


 まずはconfigファイルを編集。  この情報を元にDBにアクセスします。

return {
    DB   => [
            'DBI:mysql:[hostname]:[servername]:[portnumber]',
            '[username]',
            '[password]',
    ],
}


 次にlib/MyApp.pmを編集していきます。

 まずはdbメソッドを編集してDBへの接続とTengを利用できるようにします。

sub db {
    my $c = shift;
    if (!exists $c->{db}) {
        my $conf = $c->config->{DB}
            or die "Missing configuration about DB";
        $c->{db} = Teng->new(
            connect_info => $conf,
            schema_class => "MyApp::DB::Schema",
        );
    }
    $c->{db};
}


 続いてmodelメソッドを追加。  これで$c->model('Foo')とかやるとFooモデルを呼び出せます。

sub model {
    my ($c, $model_name) = @_;

    my $module_name = 'MyApp::Model::' . $model_name;
    eval "require $module_name";

    return $module_name->new;
}


 次はModel部分を作っていきます。

 lib/MyApp/Model.pmを作って、以下のようにします。

package MyApp::Model;
use strict;
use warnings;
use utf8;
use parent qw/MyApp/;

sub new {
    shift
}

sub c {
    MyApp->context
}

1;


 後はlib/MyApp/Model/以下にModelクラスを必要な分だけドンドン作っていって、MyApp::Modelを継承していけば、OKです。

 まずは1テーブルに対して1モデル作っていけば良いのではないかと思います。


利用方法

 例えばUserモデルとしてlib/MyApp/Model/User.pmは以下のように実装します。

package MyApp::Model::User;
use strict;
use warnings;
use utf8;
use parent qw/MyApp::Model/;

sub lookup_by_id {
    my ($class, $id) = @_;

    return $class->c->db->single('user', +{ id => $id });
}

1;


 Web側では以下のように呼び出して使います。

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

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

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

    return $c->render('index.tx', { name => $user->name });
};

1;


最後に

 これで簡易ではあるものの、Model実装ができました。

 validationやらmodelメソッドの中身とか突っ込みどころ満載かとは思いますが、分かりやすさ(自分に対して)を重視しながらだとこんな感じに落ち着きました。

 これはマズいってところとかあれば突っ込みお願い致します。


追記 (2014/07/13)

 @pchatsuさんからご指摘をいただきました。

 ModelがMyAppを継承するのはオブジェクト指向的にどうなのか。ということで、継承を止めてAmon2::Declareを使用することにしました。

package MyApp::Model;
use strict;
use warnings;
use utf8;
use Amon2::Declare;

sub new {
    shift;
}

sub db {
    c->db;
}

1;


 そして、Userモデルの実装は以下のようになります。

package MyApp::Model::User;
use strict;
use warnings;
use utf8;
use parent qw/MyApp::Model/;

sub lookup_by_id {
    my ($class, $id) = @_;

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

1;


 一つ気をつけないと行けないのは、local context modeが有効になっている場合はうまく動作しません。amon2-setupしたままだと有効になっているので、lib/MyApp.pmの以下を消しておく必要があります。

...
# Enable project local mode.
__PACKAGE__->make_local_context();
...


参考

Amon2 に local context mode をつけた