Class::DBI(CDBI)で多対多(many_to_many)

なんかググっても全然良い例に出会えないの。誰も使ってないか、誰も嵌らないかどちらかだ。
半日つぶした。many_to_many が早くできて欲しいね。


テーブルはこんな感じだとする。(code がイけてねぇ〜。中間テーブルぐらい id 入れれば良かった…)

CREATE TABLE Users (
    code    varchar(8)   primary key,
    passwd  varchar(255) not null
);

CREATE TABLE CourseUsers (
    user_code     varchar(8)   not null,
    course_code   varchar(255) not null,
    primary key (user_code, course_code)
);

CREATE TABLE Courses (
    code varchar(5)    primary key,
    name varchar(255)  not null
);

lib/MyApp/Model/CDBI/Users.pm:

package MyApp::Model::CDBI::Users;
use strict;

__PACKAGE__->has_many(courses => ["MyApp::Model::CDBI::CourseUsers" => 'course_code'], 'user_code');

1;

has_many ね。
第1引数のハッシュのキー(courses)は、取り出したいサブルーチン名。
第1引数のバリュー(["MyApp::〜", 〜])は、中間テーブルクラス名とその先を取得する為のキーを指定。
第2引数(user_code)は、自分のキーと結合する中間テーブルクラスの列名。


lib/MyApp/Model/CDBI/CourseUsers.pm:

package MyApp::Model::CDBI::CourseUsers;
use strict;

__PACKAGE__->has_a(course_code => "MyApp::Model::CDBI::Courses");
__PACKAGE__->has_a(user_code => "MyApp::Model::CDBI::Users");
sub courses { shift->user->courses }
sub users { shift->course->users }

1;

has_a は簡単かな。自分の列名とそれに紐づくクラス名をハッシュで渡す。
(先の has_many の第1引数バリューのバリューと名前を合わせないといけないのだろうか?)
そして、サブルーチンがポイント! だけど何やってんのかワカラナス。shift で取り出してるのは $self だろうが、なんかバックして前に進んでるように見える、何故に? チョロQ?


こちらは、Users.pm と同じく:
lib/MyApp/Model/CDBI/Courses.pm:

package MyApp::Model::CDBI::Courses;
use strict;

__PACKAGE__->has_many(users => ["MyApp::Model::CDBI::CourseUsers" => 'user_code'], 'course_code');

1;


アクセスはこんな感じ(もちろん lazyload):
lib/MyApp/Controller/適当.pm

    my $user = MyApp::Model::CDBI::Users->retrieve("user-001");
    foreach my $c ($user->courses) {
        $str .= $c->name . ", ";
    }
    die $str;


すごく謎だができてしまった。多分、説明が間違っているので Perl ハカーの解説希望。
プライマリキーが id だったら記述量も減って苦労しないと思います。カスケードして Insert や Delete したい時は add_trigger 辺りでホゲろ!


参考URL: