DBIx::Simple ふたたび

tomi-ru
2011-12-08

タイガー&バニーの、原稿が遅れがちな方、トミールです!

今日は DBI へのシュガーシンタックスを追加するラッパー、DBIx::Simple の紹介です。

DBIx::Simple

実は DBI そのものって相当高機能である!、というのはこのアドベントカレンダーでもいろいろ紹介されると思います。が、やや不親切なメソッドの引数の感じを見て、ゴロゴロ DBI のラッパーが作られていたりします。そんななか DBIx::Simple がやはり良いと思うのはわかりやすくシンプルな API を提供する、という点で一定の成功を収めていると思う点です。

※ ただしわかりやすい API のトレードオフとして、結果のステートメントハンドルを bless するタイプです。もし blessする(結果オブジェクトを作成する)コストが問題になるようなハードコアな環境で開発されている方は普通に DBI を素で使えばよいと思います。

DBIx::Simple の使い方は、POD 自体がメソッド一覧に加えより具体的な例が書かれているので説明要らないくらいじゃないかとおもいます。

ハッシュリファレンスで受け取る hash()、複数の場合ハッシュリファレンスのリストとして受け取る hashes()、結果が1件の値だけの場合などに使う flat()into() が良く使われるメソッドだと思います。

そのへんずいぶん前に、日本語の説明書いたのですが、今日はそれ以外の点を紹介してみようと思います。

DBI のサブクラスとして使う

DBIx::Simple への不満点として、それが DBI のラッパーであり、たとえば DBIx::Connect のような別の DBI のラッパーと合わせて使いにくい、という点がありました。そこで DBI のサブクラスとして動く DBIx::Simple、DBIx::Simple::Inject を作りました。

    my $conn = DBIx::Connector->new(
        'dbi:mysql:foobar', 'user', 'pass',
        {
            RootClass  => 'DBIx::Simple::Inject',
            RaiseError => 1,
            ...
        },
    );

このように、DBI の RootClass で指定して使うことを想定したものです。

    $conn->txn(sub {
        # データベースハンドラで、
        my $dbh = shift;
        
        # DBI::db の提供するメソッドに加え、
        $dbh->do($sql);
        
        # DBIx::Simple のメソッドも使える
        my $res = $dbh->query($sql, @params);
    });

DBI の全機能に加え、query()select()など DBIx::Simple のメソッドも DBI に“注入”されてます。

Set your favorite abstract module

    my $dbis = DBIx::Simple->connect(...);
    my $user = $dbis->select(users => ['*'], { id => 123 })->hash;

select(), insert(), update(), delete() メソッドが使えるわけですが、SQL 文を作成する部分について、デフォルトでは SQL::Abstract モジュールが使われます。ただし、SQL::Abstract は Limit 句の指定ができなかったりとやや不足な気がします。

使う SQL 文構築モジュールは abstract() プロパティで変更できます。

    use SQL::Maker;
    
    # lvalue メソッドなので以下のようにして代入する
    $dbis->abstract = SQL::Maker->new($dbis->dbh->{Driver}{Name});
    
    # SQL::Maker の select() をつかって SQL文とバインドパラメータが作成される
    my $user = $dbis->select(users => {
        foo      => ['bar', 'baz'],
    }, {
        order_by => ['created'],
        limit    => 1,
    })->hash;

SQL::Makerを使う場合は上のように指定します。select(), insert(), update(), delete()の4メソッドを実装しているオブジェクトであれば何でも利用可能です。

    my $conn = DBIx::Connector->new(
        'dbi:mysql:foobar', 'user', 'pass',
        {
            RootClass  => 'DBIx::Simple::Inject',
            RaiseError => 1,
            ...,
            private_dbixsimple => {
                abstract => 'SQL::Maker',
            },
        },
    );

DBIx::Simple::Inject を使う場合、private_dbixsimpleという独自アトリビュートでabstractにクラス名を渡すことで設定できます。

オブジェクトとして受け取る

一部でORマッパーのような、メソッドを生やしたデータを作りたい場合、 object()というのが便利です。

    my $user = $dbis->select(users => [qw/id name/], { id => 123 })->object('MyApp::User');

`object()`は、結果のハッシュを指定したクラスの `new()` に渡した結果オブジェクトを返すメソッドです。上の場合、

    use MyApp::User;
    my $user = MyApp::User->new( id => 123, name => 'foobar' );

のようにしたものが返ります。

もし単純に new() をするのではなく何かオブジェクト作成時にしこみたい場合は、そのクラスが new_from_dbix_simple() メソッドを仕込んでいれば new() の代わりにそれが呼ばれます。new_from_dbix_simple() の場合結果のデータだけじゃなく DBIx::Simple::Result が渡されるので、任意の形式の結果からオブジェクトを作成できます。

以上です

"Simple" is not always simple, but also Life is not always simple.

↑ タイバニ風な!