■2011-06-08
* [Perl] ORM的なものでLEFT JOINしたカラムの値をRowオブエジュクトにまとめる
タイトルが意味不明ですが。DBIx::Skinnyなどを使っててhas-manyな関係のデータをLEFT JOINしたりします。するとまあJOINで掛け合わせた分、Rowオブジェクトが複数個返ります。
# 著者authorと著作bookがhas-manyな関係だとして
# SELECT a.id, b.id AS book_id, b.title AS title
# FROM author a JOIN book b ON a.id = b.author_id
# WHERE ...
... なんか色々あって
my $itr = $skinny->search_by_sql(...);
# この$itrを回すとbook_idとtitle以外
# (つまりid)は同じデータ
while ( my $author = $itr->next ) {
# $author->id, $author->book_id, $author->title
# 同じ 別 別
}
で、has-manyな部分はリストにまとめて、Rowオブジェクトはプライマリキー分だけ欲しいと思うことがあります。
# こんな感じになると嬉しい
$author = $itr->first;
my $books = $author->{ books };
for my $book ( @$books ) {
# $book->{ id }, $book->{ title }
}
そういうモジュールがCPANにないかと探したのですが見つからなかったので自作して使ってました。で、この部分の機能をDBIx::JoinedColumnsAggregatorという名前で切り出してみました。aggregate_joined_columnsという関数がエクスポートされますのでイテレータ(リストリファレンスでもよいよ)とオプションを渡します。
use DBIx::JoinedColumnsAggregator;
# ...
my $itr = $skinny->search_by_sql(...);
my $authors = aggregate_joined_columns( $itr, {
pk => ['id'],
tags => {
books => ['book_id','title'],
},
} );
my $author = $authors->[0];
my $books = $author->{ books };
# ...
プライマリキー毎に代表されたRowオブジェクトだけを返します。そして各Rowオブジェクトに、まとめあげたリストをつっこみます(デフォルトでtagsで指定したキーのハッシュエントリに保存されますが、setterオプションにsubrefを渡せば任意に変更可)。DBIx::SkinnyやTengで使うのを念頭に置いてますが、普通にDBI等でも使えます。
use DBI;
use DBIx::JoinedColumnsAggregator;
my $dbh = DBI->connect( 'dbi:SQLite:your_data', '', '' );
my $sth = $dbh->prepare( ... );
$sth->execute();
my $list = aggregate_joined_columns( $sth, {
pk => ['id'],
tags => {
books => ['book_id','title'],
},
access_style => 'hash',
next_method => 'fetchrow_hashref',
} );
[]

