[Perl] DBICでTwitterのユーザー関係を表してみる
追記:
勘違いしてたので別エントリーで補足
Twitterで言うところのfavorites、followers、friends。
もごもごで言うところの片想い、想われ、両想いの関係をDBICで表してみました。
まず関係の定義から。

favorite(片想い):
AがBをお気に入りに入れるとAから見てBはfavoriteな関係(片方向)
follower(想われ):
Bから見てAはfollowerな関係(片方向)
friend(両想い):
Aのお気に入りに入ってるBがAをお気に入りに入れると両方から見てfriendな関係(双方向)
で、テーブル設計ですが、それぞれをテーブルとして持ってもいいのですが、関係を追加するごとにあちこちのテーブルを見なきゃならんので、一個のテーブルで済ましてみました。

要は
user_linksテーブルを使ってusersを自己結合なmany-to-manyの関係にする。
user_links.user_idにはお気に入りに入れるusers.id
user_links.link_user_idにはお気に入りに入れられるusers.id
で、これをDBICのリレーションで表すと
MyApp/Schema.pm
package MyApp::Schema;
use strict;
use warnings;
use base qw/DBIx::Class::Schema::Loader/;
__PACKAGE__->loader_options(
relationships => 1,
debug => 1,
);
1;
MyApp/Schema/UserLinks.pm
package MyApp::Schema::UserLinks;
use strict;
__PACKAGE__->belongs_to(
'user' => 'MyApp::Schema::Users',
'user_id'
);
__PACKAGE__->belongs_to(
'link_user' => 'MyApp::Schema::Users',
'link_user_id'
);
1;
MyApp/Schema/Users.pm
package MyApp::Schema::Users;
use strict;
__PACKAGE__->has_many(
'user_link_users' => 'MyApp::Schema::UserLinks',
'user_id'
);
__PACKAGE__->many_to_many(
'link_users' => 'user_link_users',
'link_user'
);
__PACKAGE__->has_many(
'user_linked_users' => 'MyApp::Schema::UserLinks',
'link_user_id'
);
__PACKAGE__->many_to_many(
'linked_users' => 'user_linked_users',
'user'
);
sub favorite_users {
my ( $self, $cond, $attrs ) = @_;
my $id = $self->id;
my $sub_sql
= "NOT IN (SELECT user_id FROM user_links WHERE link_user_id = $id)";
return $self->link_users(
{ 'link_user_id' => \$sub_sql,
%{ $cond || {} },
},
$attrs
);
}
sub follower_users {
my ( $self, $cond, $attrs ) = @_;
my $id = $self->id;
my $sub_sql
= "NOT IN (SELECT link_user_id FROM user_links WHERE user_id = $id)";
return $self->linked_users(
{ 'user_id' => \$sub_sql,
%{ $cond || {} },
},
$attrs
);
}
sub friend_users {
my ( $self, $cond, $attrs ) = @_;
my $id = $self->id;
my $sub_sql
= "IN (SELECT user_id FROM user_links WHERE link_user_id = $id)";
return $self->link_users(
{ 'link_user_id' => \$sub_sql,
%{ $cond || {} },
},
$attrs
);
}
1;
重要なのはUsersクラスでメソッドは
$user->link_users
自分がお気に入りに入れているユーザー全てを取得
$user->linked_users
自分をお気に入りに入れているユーザー全て取得
これだけだとfriendな関係のユーザーも入ってしまうので別にメソッドを追加します。
$user->favorite_users
link_usersで自分をお気に入りに入れていないユーザー
$user->follower_users
linked_usersで自分がお気に入りに入れていないユーザー
$user->friend_users
link_usersで自分をお気に入りに入れているユーザー
many-to-manyなリレーションを作ると
$user->add_to_link_users($user)
$user->add_to_linked_users($user)
のメソッドが自動的に作成されるので関係の追加にはこれを使うといいかも。
で、実際に使うにはこんな感じ
use strict;
use warnings;
use MyApp::Schema;
# userを3人作成
my $schema = MyApp::Schema->connect('dbi:SQLite:db/myapp.db');
for ( 0 .. 2 ) {
$schema->resultset('Users')->create( { username => "user$_", } );
}
my @users = $schema->resultset('Users')->search( {} );
# user0とuser1はfriend
$users[0]->add_to_link_users( $users[1] );
$users[1]->add_to_link_users( $users[0] );
# user2はuser0のfavorite
# user0はuser2のfollower
$users[0]->add_to_link_users( $users[2] );
# 結果を表示
foreach my $user ( @users ) {
print $user->username;
my $favorites = $user->favorite_users();
print "\tfavarites:\n";
while ( my $favorite = $favorites->next ) {
print "\t\t" . $favorite->username . "\n";
}
my $followers = $user->follower_users;
print "\tfollowers:\n";
while ( my $follower = $followers->next ) {
print "\t\t" . $follower->username . "\n";
}
my $friends = $user->friend_users;
print "\tfriends:\n";
while ( my $friend = $friends->next ) {
print "\t\t" . $friend->username . "\n";
}
}
結果はこんな感じ。
user0 favarites:
user2
followers:
friends:
user1
user1 favarites:
followers:
friends:
user0
user2 favarites:
followers:
user0
friends:
サブクエリを使うのが何ですが、結構お手軽かなと。
