« 東京ヤクルト銀行 | Home | Re: DBICでTwitterのユーザー関係を表してみる »

[Perl]   DBICでTwitterのユーザー関係を表してみる

追記:
勘違いしてたので別エントリーで補足

Twitterで言うところのfavorites、followers、friends。
もごもごで言うところの片想い、想われ、両想いの関係をDBICで表してみました。

まず関係の定義から。

2007051601.gif

favorite(片想い):
 AがBをお気に入りに入れるとAから見てBはfavoriteな関係(片方向)
follower(想われ):
 Bから見てAはfollowerな関係(片方向)
friend(両想い):
 Aのお気に入りに入ってるBがAをお気に入りに入れると両方から見てfriendな関係(双方向)

で、テーブル設計ですが、それぞれをテーブルとして持ってもいいのですが、関係を追加するごとにあちこちのテーブルを見なきゃならんので、一個のテーブルで済ましてみました。

2007051602.gif

要は
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:

サブクエリを使うのが何ですが、結構お手軽かなと。

Trackbacks:

このエントリーのトラックバックURL:

コメントを投稿