モダンPerlの世界へようこそ

第39回 DBIx::Skinny:DBIx::Classに不満を感じたら

この記事を読むのに必要な時間:およそ 5.5 分

DBIx::ObjectMapper

2010年のYAPC::Asiaでも発表があった大石英介氏のDBIx::ObjectMapperは,いわゆるData Mapperパターンを実装したものです。Data Mapperパターンではアプリケーション側で利用するクラスとデータベースのテーブルをかならずしも1対1で対応させる必要がなく,アプリ側ではアプリ側の最適な実装に,データベース側ではデータベース側の最適な実装に,とわけられるので,変更や拡張が長期的に続いていく(アプリケーションのクラスとデータベースのスキーマが乖離しやすい)大規模アプリケーション向きといわれていますが,もう少し身近なところでは,オブジェクトとデータベースが密接に結びついていないためテストを書きやすくなる(いちいちデータベースにダミーデータを食わせてSQLを実行しなくてもオブジェクトをつくれる)のと,アプリケーション側のオブジェクトにはデータベースの接続情報などが入っていないため,デバッグのときなど気軽にオブジェクトの中身をダンプできるのが長所といえるでしょうか。単純な例ではいわゆるActive Recordパターンよりも記述が冗長になるデメリットが目立ってしまうのですが,このサンプルではメタデータとして格納されているテーブル情報を利用してテストデータを挿入したあと,メタデータに入っているテーブルと,アプリケーション側で使うクラス(MyUser, MyEmail)とをマッピングして,最後にリレーションを含むデータの取得と更新を試しています。詳しい使い方については,YAPC::Asiaでの発表のほか,2010年のAdvent Calendarの記事や,付属のテストが参考になります。

use strict;
use warnings;

# アプリで使うクラスを用意
package MyUser;
use base qw/Class::Accessor::Fast/;
__PACKAGE__->mk_accessors(qw/id name emails/);

# マッピングは外に出せるように別パッケージにまとめてあります
package MyMapper;
use DBIx::ObjectMapper;
use DBIx::ObjectMapper::Engine::DBI;

sub init {
  my $class = shift;
  my $_mapper = DBIx::ObjectMapper->new(
    engine => DBIx::ObjectMapper::Engine::DBI->new({
      dsn => 'dbi:SQLite::memory:',
      on_connect_do => [
        q/create table user (
            id integer primary key autoincrement, name text )/,
        q/create table email (
            id integer primary key autoincrement,
            user_id integer references user(id), email text )/,
      ],
    }),
  );
  $_mapper->metadata->autoload_all_tables;

  # テーブルのメタデータとアプリ用のクラスをマッピング
  $_mapper->maps(
    $_mapper->metadata->table('user') => 'MyUser',
    attributes => {
      properties => {
        emails => {
          isa => $_mapper->relation(has_many => 'MyEmail'),
        },
      },
    },
  );

  # 面倒なときはクラスの定義もおまかせにできます
  $_mapper->maps(
    $_mapper->metadata->table('email') => 'MyEmail',
    constructor => { auto => 1 },
    accessors   => { auto => 1 },
    attributes  => {
      properties => {
        user_id => {
          isa => $_mapper->relation(belongs_to => 'MyUser'),
        },
      },
    },
  );
  $_mapper;
}

# ここから動作確認
package main;
my $mapper = MyMapper->init;

# テーブルのメタデータから直接データを入れることもできます
$mapper->metadata->table('user')->insert->values(
  id   => 1,
  name => 'foo'
)->execute;

$mapper->metadata->table('email')->insert->values(
  user_id => 1,
  email   => 'foo@localhost'
)->execute;

# 本来の使い方はこちら
my $session = $mapper->begin_session;
my $user = $session->get(MyUser => 1);
print $user->name, "\n";
print map { $_->email, "\n" } @{ $user->emails };

# データの更新もセッションのなかで
$user->name('foo');
push @{ $user->emails }, MyEmail->new(email => 'bar@localhost');
$session->commit;

# あらためてセッションを作り直してデータを再取得
$session = $mapper->begin_session;
$user = $session->get(MyUser => 1);
print $user->name, "\n";
print map { $_->email, "\n" } @{ $user->emails };

ORLite

ここまで見てきたものはMySQLであろうとPostgreSQLやSQLiteであろうと基本的にはそれほど意識しなくても使えるようになっていましたが,どうせデータベースの乗り換えなどしないのだからと割り切るのであれば最初から特定のデータベースにすり寄ったO/Rマッパを書いてもよいはずです。アダム・ケネディ(Adam Kennedy)氏のORLiteは,SQLiteに特化したO/Rマッパとして,連載第17回で紹介したPadreや,CPANDBORDB::CPANUploadsORDB::CPANTSORDB::CPANRTなどのCPANまわりのツール群のなかで使われています(これらのツールは必要に応じて各サーバに用意されているデータベースのダウンロードや解凍をしたり,データベースのスキーマから動的にクラスを作成したりもしてくれます)⁠どのテーブルがどのクラスにマッピングされているかなどはいまのところ自分でデータベースハンドルを取りだしてクエリを発行しないとわからないので,事前にある程度概要をつかんでおく必要はありますが,CPANの統計をとったり検索サイトなどをつくるのであればワンライナーでも一覧を取り出せますので,使い方を覚えておくと便利なことがあるかもしれません。

use strict;
use warnings;
use ORDB::CPANUploads;
use Time::Piece;

ORDB::CPANUploads::Uploads->iterate(
  "where type = ? and author = ?",
  "cpan", "ISHIGAKI",
  sub {
    printf "%s: %s\n",
      $_->dist,
      Time::Piece->new($_->released)->ymd,
  }
);

ほかにもいろいろありますが

今回は比較的新しく,かつCPANにリリースされるくらいには成熟しているものという条件でいくつかのモジュールを取り上げてみましたが,もちろんCPANやgithubにはここで取り上げたほかにも新旧あわせてさまざまな選択肢が公開されています。それぞれに特徴(向き不向き)がありますので,ご自分の必要にあわせて適切なものを選んでください。

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/