2011年1月13日木曜日

DBIx::Class(DBIC)で既存のメソッドを上書きしたい。けどできない。

自分の開発しているWebアプリケーションは、DBIx::Class(DBIC)を使っているのですが、少しハマったので、調査したことを書きます。

そもそもやりたかったことは、、、
テーブルに対して、DBIC経由でUPDATEなどのDML文が発行された時に、
自動的にmemcachedに保存されているキャッシュを削除させたかったのです。
そうすることで、キャッシュの削除を透過的に行うことができて、嬉しいかなと思いました。

そこで、該当テーブルのupdateメソッドを上書きすることにしました。

package DBIC::DB1::Result::Table;

use strict;
use warnings;
use base 'DBIx::Class';

(中略)

sub update {
    my $self = shift;
    $self->next::method( @_ );
    $self->delete_cache();
}

1;


こんな感じでupdateメソッドを上書きしておけば、UPDATEのタイミングでdelete_cache()も実行されるハズと思っていたのですが、上手くいかない場合がありました。

#!/usr/bin/perl

$ENV{'DBIC_TRACE'} = 1;

use strict;
use warnings;
use lib '/MyAPP/lib/
use YAML::Syck;
use DBIC::DB1;
use utf8;

$YAML::Syck::ImplicitUnicode = 1;
my $configfile = '/MyAPP/MyAPP.yml';
my $c = YAML::Syck::LoadFile($configfile);

my $row = DBIC::DB1->connect(
    @{$c->{'Model::DB1'}->{connect_info}}
)->resultset('Table')->find(1);
$row->update({ col1 => '' });        #上書きしたメソッドが実行される

my $rows = DBIC::DB1->connect(
    @{$c->{'Model::DB1'}->{connect_info}}
)->resultset('Table')->search({ item_id => { 'in' => ['1','2'] } });
$rows->update({ col1 => '' });       #上書きしたメソッドが実行されない


上記のテストスクリプトを実行すると、findメソッドなどで取得した単一レコードに対してupdateを実行すると、上書きしたupdateメソッドが実行されるのですが、searchメソッドなどで取得した複数行のレコードに対してupdateメソッドを発行した場合、上書きしたupdateメソッド(DBIC::DB1::Result::Table内のupdateメソッド)が呼ばれていないことがわかりました。

そこで、findおよびsearchメソッドが返すオブジェクトを調べてみることにしました。
その結果、findが返すのは、DBIC::DB1::Result::Tableオブジェクト、一方、searchが返すのは、DBIx::Class::ResultSetでした。

そこで、DBIx::Class::ResultSetをGoogle検索したところ、DBIx::Class::Manual::Cookbook - 様々なレシピというページが見つかったので、ここを参考にして次のようなパッケージを新しく作成しました。

package DBIC::DB1::ResultSet::Table;

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

sub update {
    my $self = shift;
    $self->next::method( @_ );
    $self->delete_cache();
}

1;


そして先ほどのテストスクリプトを実行すると。。。出来た!うまくいきました。
つまり、単一レコード(Result)に対するupdateメソッドと複数レコード(ResultSet)に対するupdateメソッドは、それぞれ別に定義してあげないといけなかったのでした。

なお、先ほどのDBIx::Class::Manual::Cookbook - 様々なレシピというページには、
パッケージ DBIC::DB1::Result::Table に対して、以下のコードを追加するよう書かれていますが、これはなくてもOKでした。

__PACKAGE__->resultset_class('DBIC::DB1::ResultSet::Table');

最初は理由がわからなかったのですが、色々調べてみると自分の環境では、パッケージ DBIC::DB1において、

__PACKAGE__->load_namespaces;

しているからでした。
これをしておくと、DB1/Result、DB1/ResultSet 以下に配置したパッケージが自動的に認識されるみたいです。こりゃ便利。

以下参考にしたURLです。
http://www.mail-archive.com/dbix-class@lists.scsys.co.uk/msg04797.html
http://search.cpan.org/~arcanez/DBIx-Class-0.08126/lib/DBIx/Class/Schema.pm#load_namespaces
http://gihyo.jp/dev/serial/01/perl-hackers-hub/000303

0 件のコメント:

コメントを投稿