Shibuya.pm #12連動企画
本日開催のShibuya Perl Mongersテクニカルトーク#12のテーマは "No Perl, NoSQL, NoKVS" または "Not only Perl, Not only SQL, Not only KVS" ということなので、
オブジェクトをまるごと保存する
牧大輔氏も
オブジェクトをまるごと保存といってもピンとこないかもしれませんので、
テキストを持たせるクラスは簡単です。
package Post;
use Moose;
has 'name' => (is => 'rw', isa => 'Str');
has 'body' => (is => 'rw', isa => 'Str');
has 'author' => (is => 'rw', isa => 'Person', weak_ref => 1);
__PACKAGE__->meta->make_immutable;
写真を持たせるクラスも、
package Photo;
use Moose;
has 'name' => (is => 'rw', isa => 'Str');
has 'image' => (is => 'rw');
has 'author' => (is => 'rw', isa => 'Person', weak_ref => 1);
__PACKAGE__->meta->make_immutable;
両者をとりまとめるPersonはこんな感じです。postsのなかにはテキスト記事も写真記事も入る予定です。単純にArrayRef型にしておいてもよいのですが、
package Person;
use Moose;
use KiokuDB::Util 'set';
has 'name' => (is => 'rw', isa => 'Str');
has 'posts' => (
is => 'rw',
does => 'KiokuDB::Set',
default => sub { set() },
);
__PACKAGE__->meta->make_immutable;
では、
use strict;
use warnings;
use KiokuDB;
use KiokuDB::Util 'set';
use Person;
use Post;
use Photo;
my $db = KiokuDB->connect( 'dbi:SQLite:db', create => 1 );
まずは必要なモジュールを読み込んで、
my $person_id;
{
my $scope = $db->new_scope;
my $person = Person->new(name => 'Foo Bar');
my $post = Post->new(
name => 'my post',
body => 'MyPost',
author => $person,
);
my $photo = Photo->new(
name => 'my photo',
image => 'MyPhoto',
author => $person,
);
$person->posts(set($post, $photo));
$person_id = $db->store($person);
}
保存するオブジェクトをつくって、
$person_id = $db->store(my_id => $person);
ただし、
if ($db->object_to_id($person)) {
$person_id = $db->store($person);
}
else {
$person_id = $db->store(my_id => $person);
}
いまつくった$person以下のオブジェクトはスコープを抜けたところでメモリから消えます。ここではまだnew_
{
my $other_scope = $db->new_scope;
my $person = $db->lookup($person_id);
foreach my $post ($person->posts->members) {
print $post->name, " by ", $post->author->name, "\n";
}
}
別のスコープで、
KiokuDBはアプリケーションの仕様が変わっても柔軟に対応できます。今度はムービー記事も投稿できるようにしたいという要望があがってきました。RDBMSならテーブルの変更やらなにやらが必要になるところですが、
package Movie;
use Moose;
has 'name' => (is => 'rw', isa => 'Str');
has 'movie' => (is => 'rw');
has 'author' => (is => 'rw', isa => 'Person', weak_ref => 1);
__PACKAGE__->meta->make_immutable;
Movieを追加するコードはこうなります。
{
use Movie;
my $yet_another_scope = $db->new_scope;
my $person = $db->lookup($person_id);
my $movie = Movie->new(
name => 'my movie',
movie => 'MyMovie',
author => $person,
);
$person->posts->insert($movie);
$db->store($person);
}
なお、
$db->txn_do(sub { ... });
オブジェクトの検索
KiokuDBを使うとオブジェクトの保存や取り出しは非常に簡単になりますが、
検索を有効にしたい場合は、
my $db = KiokuDB->connect(
'dbi:SQLite:db',
create => 1,
columns => [
name => { data_type => 'varchar', is_nullable => 1 },
],
);
このカラム指定のやり方はDBIx::Classのものと同じです。is_
検索用のカラムを用意したら、
{
my $stream = $db->search({ name => { 'like' => '%movie' }});
while (my $block = $stream->next) {
foreach my $object (@$block) {
print $object->name, "\n" ;
}
}
}
実際にはどのように保存されているのか
こうして永続化したオブジェクトは、
@dbi:SQLite:db> select * from entries; id,data,class,root,tied,name 'B3250019-4096-1018-85CF-79025712037B','{"__CLASS__":"KiokuDB::Set::Stored","data":["B3848A9D-4096-1018-85CF-79025712037B","D5DEA4B1-4096-1018-85CF-79025712037B","B34F3D07-4096-1018-85CF-79025712037B"],"id":"B3250019-4096-1018-85CF-79025712037B"}','KiokuDB::Set::Stored',0,undef,undef 'B34F3D07-4096-1018-85CF-79025712037B','{"__CLASS__":"Post","data":{"author":{"$ref":"B3005845-4096-1018-85CF-79025712037B.data"},"body":"MyPost","name":"my post"},"id":"B34F3D07-4096-1018-85CF-79025712037B"}','Post',0,undef,'my post' 'B3005845-4096-1018-85CF-79025712037B','{"__CLASS__":"Person","data":{"name":"Foo Bar","posts":{"$ref":"B3250019-4096-1018-85CF-79025712037B.data"}},"id":"B3005845-4096-1018-85CF-79025712037B","root":true}','Person',1,undef,'Foo Bar' 'B3848A9D-4096-1018-85CF-79025712037B','{"__CLASS__":"Photo","data":{"author":{"$ref":"B3005845-4096-1018-85CF-79025712037B.data"},"image":"MyPhoto","name":"my photo"},"id":"B3848A9D-4096-1018-85CF-79025712037B"}','Photo',0,undef,'my photo' 'D5DEA4B1-4096-1018-85CF-79025712037B','{"__CLASS__":"Movie","data":{"author":{"$ref":"B3005845-4096-1018-85CF-79025712037B.data"},"movie":"MyMovie","name":"my movie"},"id":"D5DEA4B1-4096-1018-85CF-79025712037B"}','Movie',0,undef,'my movie' [5 rows of 6 fields returned]
また、
$ kioku dump --dsn dbi:SQLite:db
--- !!perl/hash:KiokuDB::Entry
class: KiokuDB::Set::Stored
data:
- B3848A9D-4096-1018-85CF-79025712037B
- D5DEA4B1-4096-1018-85CF-79025712037B
- B34F3D07-4096-1018-85CF-79025712037B
id: B3250019-4096-1018-85CF-79025712037B
root: 0
--- !!perl/hash:KiokuDB::Entry
class: Post
data:
author: !!perl/hash:KiokuDB::Reference
id: B3005845-4096-1018-85CF-79025712037B
body: MyPost
name: my post
id: B34F3D07-4096-1018-85CF-79025712037B
root: 0
--- !!perl/hash:KiokuDB::Entry
class: Person
data:
name: Foo Bar
posts: !!perl/hash:KiokuDB::Reference
id: B3250019-4096-1018-85CF-79025712037B
id: B3005845-4096-1018-85CF-79025712037B
root: 1
(後略)
そのままではうまく保存できない場合
KiokuDBはほとんどのオブジェクトをそのまま保存できますが、
O/Rマッパを置き換えるものではなく
KiokuDBはうまく使えば便利なツールですが、
Catalystを使っている場合はKiokuDBをモデルとして使うためのコンポーネントが用意されているほか、
KiokuDBは2008年5月に始まった若いプロジェクトですから