DBICのRの拡張

WEB+DBが発売されてます。
id:naoyaさんの記事はDBICです!
買うべしべし。

WEB+DB PRESS Vol.36
WEB+DB PRESS Vol.36
posted with amazlet on 06.12.27
WEB+DB PRESS編集部
技術評論社
売り上げランキング: 690


DBICでRを拡張する時の話ですが、WEB+DBにも載っているとおり、
CDBIとは異なりDBを操作する時にResultSetというレイヤを経由して
もりもりDBを触るのですが、その為、CDBIで簡単にできていたことができなくなっています。
それはRの拡張です。


例えば弊社の場合、idの代わりにridがユニークなキーになってるので、
ridでレコードを検索する場合があり、
CDBIの場合
retrieve_ridというメソッドをCDBIに生やして、

Proj::Data::Blog->retrieve_rid('et5qZkSihU');

こんな感じでやってました、これをDBICで同じことを実現しようとした場合、
結構厄介だったりします。

例えば(MoFedge::Data::DBIC::Schemaをつかってるので生のDBICとはちょっと違うかも)

package TestDBIC::Schema::Blog;
use strict;
use warnings;
use base 'TestDBIC::Schema';

__PACKAGE__->table('blog');
__PACKAGE__->add_columns(
    qw/
        id
        rid
        member_id
        title
        created_on
        timestamp
    /
);

sub retrieve_rid {
    my $self = shift;
    my $rid  = shift;

    return $self->search({rid => $rid})->first;
}

...

my $member = $self->model('Blog')->retrieve_rid('et5qZkSihU');
warn $member->id;

こんな感じでやったとしても

Can't locate object method "retrieve_rid" via package "DBIx::Class::ResultSet"

と、エラーになります。
これは当然で、$self->model('Blog')から取れるのはResultSetのオブジェクトなのですが

retrieve_ridを定義しているのはスキーマ側のオブジェクトだからです。
まあ、ぜんぜん違うところにメソッドを生やしてるのですね。
なので普通にはでけません。
じゃあRの拡張はできないのかというとそんなことはないです。でけます。


案1:ResultSetManagerを使う方法

package TestDBIC::Schema::Blog;
...
__PACKAGE__->load_components(qw/ResultSetManager/);
__PACKAGE__->table('blog');
__PACKAGE__->add_columns(
    qw/
        id
        rid
        member_id
        title
        created_on
        timestamp
    /
);

sub retrieve_rid : ResultSet {
    my $self = shift;
    my $rid  = shift;

    return $self->search({rid => $rid})->first;
}

1;
...

my $member = $self->model('Blog')->retrieve_rid('et5qZkSihU');
warn $member->id;

このように

ResultSetManagerをload_componentsすることで

sub retrieve_rid : ResultSet {
    my $self = shift;
    my $rid  = shift;

    return $self->search({rid => $rid})->first;
}

こんな感じにCatalystぽく書けます。
メソッドのアトリビュートにResultSetを付けることによって
自動でResultSetで使えるようにしてくれます。


案2:自分でResultSetのクラスを作成する。

まず自分のResultSetを作成します。

package TestDBIC::ResultSet;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub retrieve_rid {
    my $self = shift;
    my $rid  = shift;

    return $self->search({rid => $rid})->first;
}

1;

テストなので微妙な名前空間においてますが気にせず。
スキーマのほうでは以下のようにします。

package TestDBIC::Schema::Blog;
use strict;
use warnings;
use base 'TestDBIC::Schema';

__PACKAGE__->table('blog');
__PACKAGE__->resultset_class('TestDBIC::ResultSet');
__PACKAGE__->add_columns(
    qw/
        id
        rid
        member_id
        title
        created_on
        timestamp
    /
);

1;

このように

__PACKAGE__->resultset_class('TestDBIC::ResultSet');

と、このスキーマで使うresultset_classを指定することができます。


案3:MoFedge::Data::DBIC::ResultSetRegisterを使う(very EXPERIMENTAL!)

オイラが作りました。EXPERIMENTAL!!
http://code.mfac.jp/trac/file/MoFedge-Data-DBIC-Schema/lib/MoFedge/Data/DBIC/ResultSetRegister.pm

package TestDBIC::Schema::Blog;
use strict;
use warnings;
use base 'TestDBIC::Schema';

__PACKAGE__->load_components(qw/+MoFedge::Data::DBIC::ResultSetRegister/);
__PACKAGE__->table('blog');
__PACKAGE__->add_columns(
    qw/
        id
        rid
        member_id
        title
        created_on
        timestamp
    /
);

__PACKAGE__->add_resultset_method(
    'retrieve_rid' =>
    sub {
        my $self = shift;
        my $rid  = shift;
        return $self->search({rid => $rid})->first;
    }
);

1;

MoFedge::Data::DBIC::ResultSetRegisterを使うと
add_resultset_methodというメソッドが追加されるので
そいつで無理やりResultSetにメソッドを生やします。

ちなみにDBIx::Class以外の名前空間に作ったプラグインとかは

__PACKAGE__->load_components(qw/+MoFedge::Data::DBIC::ResultSetRegister/);

こんな感じでモジュール名の前に+を書いてあげるといいです。

MoFedge::Data::DBIC::ResultSetRegisterをなんで作ったかというと
ResultSetManagerがキョドル時があった為、嫌いになったからです(半分本当)

ちなみにMoFedge::Data::DBIC::ResultSetRegisterは
MoFedgeとか関係なく使えるので、使いたい人は普通に使えるはずです。

大体こんな感じかな。
分かりにくいかもしれませんがフォースを使って読み解いて下さいw