Perl OR Mappers - DBIC / DOD / Fey

YAPC::Asia 2008 Tokyo

2008-05-16

始める前に

continued...

始める前に

continued...

始める前に

continued...

始める前に

continued...

始める前に

continued...

始める前に

continued...

始める前に

agenda

DBIx::Class

DBIx::Class

Data::ObjectDriver

Data::ObjectDriver

Fey::ORM

Fey::ORM

簡単な実例

DBICはみんな使ってるだろうから省略

Data::ObjectDriver

何も考えない使い方

DBに接続するDriverClassを作成:

package Proj::Driver;
use Data::ObjectDriver::Driver::DBI;
sub driver {
    Data::ObjectDriver::Driver::DBI->new(
        dsn      => 'dbi:mysql:proj',
        username => 'proj',
        password => 'passwd',
    )
}

テーブルにマッピングさせるClassを作成:

package Proj::Data::Todo;
use base 'Data::ObjectDriver::BaseObject';
use Proj::Driver;
__PACKAGE__->install_properties({
    columns     => [qw/id body delete_fg done_at created_on updated_on/],
    datasource  => 'todo',
    primary_key => 'id',
    driver      => Proj::Driver->driver,
});

let's play:

#! perl
use Proj::Data::Todo;
my $todo = Proj::Data::Todo->new;
$todo->body('learn DOD!');
$todo->save; # insert!

say $todo->body; # 'learn DOD!'

$todo->done_at(DateTime->now);
$todo->save # update!

Todo has many TodoTag. Tag has many TodoTag.

Tagテーブルの設定

package Proj::Data::Tag;
use base 'Data::ObjectDriver::BaseObject';
use Proj::Driver;
__PACKAGE__->install_properties({
    columns     => [qw/id name created_on updated_on/],
    datasource  => 'tag',
    primary_key => 'id',
    driver      => Proj::Driver->driver,
});

TodoTagテーブルの設定

package Proj::Data::TodoTag;
use base 'Data::ObjectDriver::BaseObject';
use Proj::Driver;
__PACKAGE__->install_properties({
    columns     => [qw/id todo_id tag_id created_on updated_on/],
    datasource  => 'todo_tag',
    primary_key => 'id',
    driver      => Proj::Driver->driver,
});

ここまでは一緒

リレーションの設定

package Proj::Data::TodoTag;
#..nop
__PACKAGE__->has_a({
    class  => 'Proj::Data::Todo',
    column => 'todo_id',
    method => 'todo',
    parent_method => 'tags',
});
__PACKAGE__->has_a({
    class  => 'Proj::Data::Tag',
    column => 'tag_id',
    method => 'tag',
    parent_method => 'todos',
});
continued...
continued...
#! perl
## primary key select
my $todo = Proj::Data::Todo->lookup(1);

## Todoに設定されているTagの取得
my @tags = map { $_->tag->name } $todo->tags;
continued...
continued...
#! perl
my $rs = Proj::Data::Todo->result(
    {},
    {
        sort      => 'id',
        direction => 'descend',
    }
);
$rs->add_constraint({done_on => \'IS NOT NULL'});
while ( not $rs->is_finished ) {
    my $todo = $rs->next;
}
continued...
   #! perl
   my $rs = Proj::Data::Todo->result(
       {},
       {
           sort      => 'id',
           direction => 'descend',
       }
   );
   $rs->add_constraint({done_on => \'IS NOT NULL'});
   while ( not $rs->is_finished ) {
       my $todo = $rs->next;
   }

add_constraintを呼び出してインクリメンタルに検索条件追加できる

Cacheをきかせる使い方

Driverクラスの書き方:

package Proj::Driver;
use Data::ObjectDriver::Driver::DBI;
use Data::ObjectDriver::Driver::Cache::Memcached; ## 追加
use Cache::Memcached;                             ## 追加
sub driver {
    Data::ObjectDriver::Driver::Cache::Memcached->new(
        ## Cacheの設定
        cache => Cache::Memcached->new({servers => ['127.0.0.1:11211']}),
        ## CacheにHitしなかった場合のDB接続設定
        fallback => Data::ObjectDriver::Driver::DBI->new(
            dsn      => 'dbi:mysql:proj',
            username => 'proj',
            password => 'passwd',
        ),
    );
}

Data::ObjectDriver::Driver::DBIの上に Data::ObjectDriver::Driver::Cache::Memcached がかぶさっただけ。 これだけで、自動的にキャッシュしてくれます。

簡単にパーティショニングさせる使い方

Driverクラスの書き方:

package Pro::Driver;
use Cache::Memcached;
use Data::ObjectDriver::Driver::Cache::Memcached;
use Data::ObjectDriver::Driver::Partition;
use Data::ObjectDriver::Driver::DBI;
sub driver {
    Data::ObjectDriver::Driver::Cache::Memcached->new(
        cache => Cache::Memcached->new({servers => ['127.0.0.1:11211']}),
        fallback => Data::ObjectDriver::Driver::Partition->new(
            get_driver => \&get_driver,
        ),
    ),
};

続き...:

package Proj::Driver;
# ...nop
sub get_driver {
    my($terms) = @_;
    ## some partitionning rule
    Data::ObjectDriver::Driver::DBI->new(
        dsn => 'dbi:SQLite:./dat/todo_' . $rule .'.dat',
        pk_generator => \&generate_pk,
    );
}
use Data::YUID::Generator;
sub generate_pk {
    my $obj = shift;
    $obj->id(Data::YUID::Generator->new->get_id);
}

DOD++

DOD--

Fey::ORM

簡単な使い方

Schemaクラス:

package Proj::Schema;
use Fey::ORM::Schema;
use Fey::DBIManager::Source;
use Fey::Loader;
my $source = Fey::DBIManager::Source->new(
    dsn      => 'dbi:mysql:diary',
    username => 'diary',
    password => 'passwd'
);
my $schema = Fey::Loader->new( dbh => $source->dbh() )->make_schema();
has_schema $schema;
__PACKAGE__->DBIManager()->add_source($source);

Diaryテーブルのマッピング

package Proj::Schema::Diary;
use Proj::Schema;
use Fey::ORM::Table;
my $schema = Proj::Schema->new();
has_table( $schema->Schema()->table('diary') );
continued...
continued...
#! perl
my $diary = Proj::Schema::Diary->insert(
    body       => 'nice day!',
    created_on => DateTime->now
);
say $diary->body; # nice day!

Diary has many DiaryTag. Tag has many DiaryTag.

package Proj::Schema::Tag;
use Proj::Schema;
use Fey::ORM::Table;
my $schema = Proj::Schema->new();
has_table( $schema->Schema()->table('tag') );

package Proj::Schema::DiaryTag;
use Proj::Schema;
use Fey::ORM::Table;
my $schema = Proj::Schema->new();
has_table $schema->Schema()->table('diary_tag');

リレーションの設定:

package Proj::Schema::Diary;
has_many diary_tags => (
    table => $schema->Schema()->table('diary_tag')
);

package Proj::Schema::DiaryTag;
has_one $schema->Schema()->table('diary');

let's play:

my $diary = Proj::Schema::Diary->new(id => 1);
say $diary->id;   # 1
say $diary->body; # nice day!

my $diary_tags = $diary->diary_tags;
while (my $row = $diary_tags->next) {
    say $row->tag->name; # perl, life, some more tags...
}

SQLをうまうまなかたちでうはうは

package Proj::Schema::Diary;
use Fey::Object::Iterator;
sub perl_entries {
    my $class = shift;
    my $schema = $class->SchemaClass()->Schema();
    my $select = $class->SchemaClass()->SQLFactoryClass()->new_select();
    my ( $diary_t, $diary_tag_t, $tag_t ) =
        $schema->tables( 'diary', 'diary_tag', 'tag' );
    $select->select( $diary_t )
       ->from( $diary_t )->from( $diary_tag_t )->from( $tag_t )
       ->where(
           $diary_t->column('id'), '=', $diary_tag_t->column('diary_id')
       )->and(
           $diary_tag_t->column('tag_id'), '=', $tag_t->column('id')
       )->and(
           $tag_t->column('name'), '=', 'perl'
       );

続き:

    my $dbh = $class->_dbh($select);
    my $sth = $dbh->prepare( $select->sql($dbh) );
    return Fey::Object::Iterator->new(
        classes     => [
            $class->meta()->ClassForTable(
                $diary_t, $diary_tag_t, $tag_t
            )
        ],
        handle      => $sth,
        bind_params => [ $select->bind_params() ],
    );
}

let's play:

my $it = Prj::Schema::Diary->perl_entries;
while (my $row = $it->next) {
    say $row->body;
}

複雑なSQLを生成してイテレータオブジェクトにして利用可能

まとめ

Fey::ORMまとめ

DODまとめ

DBICまとめ

結論

continued...

結論

continued...

結論

continued...

結論

continued...

結論

continued...

結論

continued...

結論

continued...

結論

continued...

結論

continued...

結論

continued...

結論

ご清聴有り難うございました