DBIC のスキーマをモデルクラスとして使ってみる

ドキュメントに書いてないので今後も使えるかわかんないけど、メモメモΦ


普通 DBIC で新しいレコードを挿入するには、

use strict;
use warnings;

package My::Schema::Artist;
use base qw( DBIx::Class );
__PACKAGE__->load_components(qw( Core ));
__PACKAGE__->table('artist');
__PACKAGE__->add_columns(
    id => {
        data_type         => 'INTEGER',
        is_nullable       => 0,
        is_auto_increment => 1,
    },
    name => {
        data_type         => 'VARCHAR',
        is_nullable       => 0,
    },
);
__PACKAGE__->set_primary_key('id');

package My::Schema;
use base qw( DBIx::Class::Schema );
__PACKAGE__->register_class('artist', 'My::Schema::Artist');

my $schema = My::Schema->connect('dbi:SQLite:test.db');
$schema->deploy();

my $new_artist
    = $schema->resultset('artist')->create({
        name => 'dayflower',
    });

みたいに、resultset()create() メソッドを使う。


本題と関係ないけど、SQL::Translator をインストールしてあると、上記のように connect() して得られたスキーマインスタンスdeploy() を発行することでテーブルを生成することができる。ちょいと便利ですね。


さて。

My::Schema::Artist は一見普通のクラスなので、インスタンス化してみる。

my $schema = My::Schema->connect('dbi:SQLite:test.db');

my $new_artist = My::Schema::Artist->new();

$new_artist->name('dayflower');

うまく動く。

じゃあこれを INSERT できるかなと思ってやってみると、

$new_artist->insert();
# => DIED!
#    Can't call method "resolve" on an undefined value
#    at /usr/lib/perl5/site_perl/5.8.8/DBIx/Class/Row.pm line 1210.

できない。Storage とバインドされてないから(挿入先がわからないんで)当たり前ちゃあ当たり前。エラーメッセージだけからは原因がわかんないのはご愛嬌?


んで、レガシーコードでは、このモデルもどきインスタンスから resultset() をひっぱりだして create() するっていうユーティリティクラスを定義してたりした。

でも API やコードをみてみると result_source() に設定すればいけるような気がした。

my $schema = My::Schema->connect('dbi:SQLite:test.db');

my $new_artist = My::Schema::Artist->new();

$new_artist->name('dayflower');

$new_artist->result_source($schema->source('artist'));

$new_artist->insert();
# => OK!

お、うまくいった。

ユーティリティメソッドを定義して

sub My::Schema::Artist::store_to_schema {
    my ($self, $schema) = @_;

    $self->result_source($schema->source($self->table));

    if ($self->in_storage) {
        $self->update();
    }
    else {
        $self->insert();
    }
}

my $schema = My::Schema->connect('dbi:SQLite:test.db');

my $new_artist = My::Schema::Artist->new();

$new_artist->name('dayflower');

$new_artist->store_to_schema($schema);

$new_artist->name('foo');
$new_artist->update();

とかするとそれっぽい。


で、これは使えるか?


単体で使うならアリだけど、リレーション張るとうまく使えない。

use strict;
use warnings;

package My::Schema::Model;

sub store_to_schema {
    my ($self, $schema) = @_;

    $self->result_source($schema->source($self->table));

    if ($self->in_storage) {
        $self->update();
    }
    else {
        $self->insert();
    }
}

package My::Schema::Artist;
use base qw( DBIx::Class My::Schema::Model );
__PACKAGE__->load_components(qw( Core ));
__PACKAGE__->table('artist');
__PACKAGE__->add_columns(qw( id name ));
__PACKAGE__->set_primary_key('id');

package My::Schema::Album;
use base qw( DBIx::Class My::Schema::Model );
__PACKAGE__->load_components(qw( Core ));
__PACKAGE__->table('album');
__PACKAGE__->add_columns(qw( id title ));
__PACKAGE__->set_primary_key('id');

My::Schema::Album
    ->belongs_to('artist', 'My::Schema::Artist', 'artist_id');

package My::Schema;
use base qw( DBIx::Class::Schema );
__PACKAGE__->register_class('artist', 'My::Schema::Artist');
__PACKAGE__->register_class('album',  'My::Schema::Album' );


package main;

my $schema = My::Schema->connect('dbi:SQLite:test.db');

my $new_album = My::Schema::Album->new();

$new_album->title('daily dayflower');

$new_album->artist->name('dayflower');
# => DIED!
#    Can't call method "resolve" on an undefined value
#    at /usr/lib/perl5/site_perl/5.8.8/DBIx/Class/Row.pm line 1210.

みたく、relationship の先にアクセスしようとするとうまくいかない。

インスタンスを設定すればいいかなと思って

$new_album->artist( My::Schema::Artist->new() );

ってやってもダメ(この時点で)。

relationship に基づくプロパティは Storage とバインドされてないと使えないみたい。

面倒だけど

my $schema = My::Schema->connect('dbi:SQLite:test.db');

my $new_artist = My::Schema::Artist->new();
$new_artist->name('dayflower');
$new_artist->store_to_schema($schema);

my $new_album = My::Schema::Album->new();
$new_album->title('daily dayflower');
$new_album->artist_id($new_artist->id);
$new_album->store_to_schema($schema);

みたいに、ひとつのテーブルずつやらなきゃいけない。こうなるとうまみがまるでない。