• このエントリーをはてなブックマークに追加

はじめに

Amon2 アプリケーションで「モデル」的なクラスをメンドくなく利用するのに,Amon2::Plugin::Model というものを書いて 使っていたりします.

以前, hirobanex さんのエントリにて次のような言及がありました:

とりあえず、issmさんに紹介していただいたコードを、Modelの名前空間いじれるようにして汎用化するのが一番いいのではないでしょうか?w
【Amon2のオレオレTips】MVCを意識したModelの具体的な実装とその考察 | hirobanex.net

このあたりやその他いくつか修正を加え,そういえば GitHub にリポジトリ作ってもいなかったので,これを機に公開してみます.

SYNOPSIS

Amon2 アプリケーション「MyApp」として簡単に説明してみます.

プラグインを読み込む

アプリケーションクラスにて load_plugin します:

package MyApp;
use parent 'Amon2';
__PACKAGE__->load_plugin('Model');
...

モデルクラスを定義する

「Foo」というモデルのためのクラス MyApp::Model::Foo を次のように準備します:

package MyApp::Model::Foo;
use Class::Accessor::Lite (
    new => 0,
    ro  => [qw/c/],
);
 
sub new {
    # context object is passed as parameter "c"
    my ($class, %params) = @_;
    return bless \%params, $class;
}
 
sub hello {
    return 'hello';
}
 
sub search {
    my $self = shift;
    my $dbh = $self->c->dbh;
    my $sth = $dbh->prepare_cached(...);
    $sth->execute(...);
    ...
}
 
...

コンストラクタ new へのパラメータとして,コンテキストオブジェクトを参照する c というものが渡るので,モデルクラス内では,これを参照できる手段を確保しておくとよさげです.

上記の例では,Class::Accessor::Lite を使って, read only なアクセサ c を定義しているので,各メソッドからは,次のように参照できたりします:

sub my_method {
    my $self = shift;
    my $c = $self->c;  # コンテキストオブジェクト!
    ...
}

モデルを呼び出す

Model プラグインを読み込むと,コンテキストオブジェクトにメソッド model が生えます.これにモデルクラス名を渡すことで,対応するモデルのオブジェクトが返ってきます:

my $c = MyApp->bootstrap();
my $foo = $c->model('Foo');
print $foo->hello();  # 'hello'

また,複数のモデルを一度に指定することも可能です.

my ($foo, $bar, $baz) = $c->model(qw/Foo Bar Baz/);

基本は以上です.

最近の修正でできるようになったこと

冒頭で「このあたりやその他いくつか修正を加え」と書きましたが,これによって,次のことができるようになりました:

  • 任意のクラス指定
  • パラメータ渡し
  • 初期化メソッドの定義

任意のクラス指定

修正前は,指定したモデル名は,必ず,自身のアプリケーション下のモデルクラスである必要がありました.先の MyApp というアプリケーションの場合,Foo というモデルは MyApp::Model::Foo というクラス名でなければならない,といった具合です.

この制限をなくし,Plack::Builder をつかったミドルウェアの有効化みたいな感じで,モデル名の頭に + をつけた場合,それ自体を名前に持つクラスが対象となる ようにしました:

my $c = MyApp->bootstrap();
my $any_model = $c->model('+MyModel::Foo::Bar');  # MyModel::Foo::Bar というクラスが対象

パラメータ渡し

メソッド model の引数として ($scalar => \%hashref) のような渡し方をすることで,そのモデルのコンストラクタのパラメータにすることができます.

例えば,次のようなモデル:

package MyModel::User;
use Class::Accessor::Lite (
    new => 0,
    ro  => [qw/c/],
    rw  => [qw/nickname/],
);
 
sub new {
    my ($class, %params) = @_;
    return bless \%params, $class;
}

を定義したとすると,次のように呼び出すことができます:

my @users = $c->model(
    '+MyModel::User' => { nickname => 'issm' },
    '+MyModel::User' => { nickname => 'clairvy' },
);
print $users[0]->nickname;  # 'issm'
print $users[1]->nickname;  # 'clairvy'

初期化メソッドの定義

モデルクラス内で,メソッド init を定義しておけば,モデル呼び出し時に自動的に実行されます:

package MyModel::User;
use Class::Accessor::Lite (
    new => 0,
    ro  => [qw/c/],
    rw  => [qw/nickname/],
);
 
sub new {
    my ($class, %params) = @_;
    return bless \%params, $class;
}
 
sub init {
    my ($self, %params) = @_;  # %params には,model メソッドで渡したハッシュの内容がそのまま入ります
    $self->{initialized} = 1;
}
my $user = $c->model('+MyModel::User' => { nickname => 'issm' });
print $user->{initialized};  # 1

おわりに

以上,Amon2 アプリケーションにおいて,定義したモデルクラスを手軽に呼び出せるようにするためのプラグイン Amon2::Plugin::Model というものを以前から書いて使っていたけど公開するのを忘れていたので,このたび公開してみた,というお話でした.

お仕事的にてんやわんやではありますが,そんな中から俺得なコードが湧き出ていることを実感しているという意味では感謝する部分もありますかねw でもまぁ workaholic にはなりたくないですが!