オレオレモジュールの進捗報告なんかしてどうする
「SQLを書くプロジェクトのためのDBIx::Sunnyってのを書いている」で紹介した DBIx::Sunny はしばらく放置中だったが、最近思い出していじってる。
github: https://github.com/kazeburo/DBIx-Sunny
まず、DBIx::Sunny は DBIx::Sunny::Schema へ rename。基本的には機能変わってない。
package NoNoPaste::Data;
use strict;
use warnings;
use utf8;
use parent qw/DBIx::Sunny::Schema/;
use Mouse::Util::TypeConstraints;
subtype 'Uint'
=> as 'Int'
=> where { $_ >= 0 };
no Mouse::Util::TypeConstraints;
__PACKAGE__->query(
'add_entry',
id => 'Str',
nick => { isa => 'Str', default => 'anonymouse' },
body => 'Str',
q{INSERT INTO entries ( id, nick, body, ctime ) values ( ?, ?, ?, DATETIME('now') )},
);
__PACKAGE__->select_all(
'entry_list',
'offset' => { isa => 'Uint', default => 0 },
q{SELECT id,nick,body,ctime FROM entries ORDER BY ctime DESC LIMIT ?,11}
);
__PACKAGE__->select_row(
'retrieve_entry',
'id' => 'Str',
q{SELECT id,nick,body,ctime FROM entries WHERE id = ?}
);
1;
Schemaを継承したクラスを作り、SQLとbind値を定義する。そして
my $dbh = DBI->connect(...);
my $data = NoNoPaste::Data->new( dbh => $dbh );
# エントリ追加
my $row = $data->add_entry(
id => $id,
nick => $nick,
body => $body,
);
# 最近のエントリ
my $rows = $data->entry_list( offset => $offset );
# エントリ取得
my $entry = $data->retrieve_entry( id => $id );
このようにSQLを隠蔽して使える。ちなみにインスタンスを作る際にreadonlyオプションを渡すと、queryメソッドが使えなくなる。slaveサーバなどの場合に使えるのかも。Schemaクラスは継承するだけの普通のモジュールなので好きにメソッドを足しても問題ない
package NoNoPaste::Data;
use parent qw/DBIx::Sunny::Schema/;
...
sub recent_entries {
state $rule = Data::Validator->new(
'limit' => { isa => 'Natural', default => '10' },
'offset' => { isa => 'Uint', default => 0 },
)->with('Method');
my($self, $args) = $rule->validate(@_);
$self->select_row(
q{SELECT id,nick,body,ctime FROM entries ORDER BY ctime DESC LIMIT ?,?},
$args->{offset},
$args->{limit}+1,
);
my $next;
$next = pop @$rows if @$rows > $args->{limit};
return $rows, $next;
}
package main;
my ($rows, $has_next) = $data->recent_entries( ... );
selectone, selectrow, selectall, queryはオブジェクトメソッドでも使えるようになっている(多分わかりにくい)。オブジェクトメソッド時はmethod(query,bindvalue)で使える。このようにSchemaクラスはシンプルなので、SQL::Makerや他のSQL genaratorと組み合わせても使えると思う。
んで、renameしたあとの DBIx::Sunny は Amon2::DBIインスパイアの DBI wrapperになっている。Scope::Containerと組み合わせるのに Amon2::DBI を試していたんだけどオレオレにするために fork させてもらった。
使い方は
my $dbh = DBIx::Sunny->connect(...);
# or
use DBI;
my $dbh = DBI->connect(.., {
RootClass => 'DBIx::Sunny',
PrintError => 0,
RaiseError => 1,
});
上のDBIx::Sunnyを直接呼び出す場合は、PrintError,RaiseErrorが自動で設定されるのでオプションの設定は基本的にいらない。RootClass を使う場合は、connectionが確立するまでは DBIx::Sunny が呼ばれないので Print、RaiseError等は接続前に指定しておく必要がある。BKですね。
DBIx::Sunny の主な機能ですが、まず設定まわりで
- AutoInactiveDestroy を有効に
- sqliteunicode と mysqlenable_utf8 を有効に
- mysqlautoreconnect は無効
- RaiseError => 1, PrintError => 0、ShowErrorStatement =>1 に設定
このあたりが強制的に変更されます。そして追加機能ですが、
- DBIx::TransactionManager 組み込み
- SQL にコメントでstatementを読んだファイルの名前と行数を埋め込み
- Schemaと同じく、selectone,selectrow,select_all,query のメソッド追加
が追加されています。
Scope::Container::DBI、DBIx::Sunny、DBIx::Sunny::Schema を組み合わせて使うと多分便利。Scope::Container::DBIのv0.03で、$Scope::Container::DBI::DBI_CLASS を変更することで利用するDBIのクラスを変更できるようになっている。
local $Scope::Container::DBI::DBI_CLASS = 'DBIx::Sunny';
my $dbh = Scope::Container::DBI->connect(
"dbi:SQLite:dbname=$db_path", '', '', {...});
my $data = NoNoPaste::Data->new( dbh => $dbh );
# 最近のエントリ
my $rows = $data->entry_list( offset => $offset );
# エントリ取得
my $entry = $data->retrieve_entry( id => $id );
この組み合わせのサンプルとして、nopasteのコードがありますので参考になれば〜。