Teng でリレーションを使う方法

前回は Teng でトランザクション処理をする方法を紹介しました。実際に色々試して、ブログに書くと頭にいい感じで入ってくるので続けていきたいです。今回は Teng でリレーションを使う方法を紹介していきます。

題材

説明に使用する題材は、掲示板へのエントリとそれに対するコメントの様なものを想定。

使用するテーブル

以下の2つのテーブルを使用します。

test@localhost:testdb> desc entry;
+------------+------------------+------+-----+---------------------+----------------+
| Field      | Type             | Null | Key | Default             | Extra          |
+------------+------------------+------+-----+---------------------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL                | auto_increment |
| name       | varchar(64)      | NO   |     | NULL                |                |
| title      | varchar(128)     | NO   |     | NULL                |                |
| body       | text             | NO   |     | NULL                |                |
| created_at | datetime         | NO   |     | 0000-00-00 00:00:00 |                |
+------------+------------------+------+-----+---------------------+----------------+
5 rows in set (0.00 sec)

test@localhost:testdb> desc comment;
+--------------+------------------+------+-----+---------------------+----------------+
| Field        | Type             | Null | Key | Default             | Extra          |
+--------------+------------------+------+-----+---------------------+----------------+
| id           | int(10) unsigned | NO   | PRI | NULL                | auto_increment |
| entry_id     | int(10) unsigned | NO   | MUL | NULL                |                |
| name         | varchar(64)      | NO   |     | NULL                |                |
| body         | text             | NO   |     | NULL                |                |
| is_invisible | tinyint(4)       | NO   |     | 0                   |                |
| created_at   | datetime         | NO   |     | 0000-00-00 00:00:00 |                |
+--------------+------------------+------+-----+---------------------+----------------+
6 rows in set (0.00 sec)

使用するデータ

すでに以下のデータが入れてあります。

test@localhost:testdb> select * from entry;
+----+-----------------+-----------------------+--------------------------------------------------------+---------------------+
| id | name            | title                 | body                                                   | created_at          |
+----+-----------------+-----------------------+--------------------------------------------------------+---------------------+
|  1 | amari3          | 肉まん食べたい        | 肉まんが食べたいでござる                               | 2011-11-30 15:07:41 |
|  2 | あまりさん      | シュークリーム        | シュークリームがいっぱい食べたいよ!                   | 2011-11-30 15:07:41 |
|  3 | amari3          | 魔法ジュース          | 魔法ジュース久しぶりに飲みたいなぁ                     | 2011-11-30 15:07:41 |
+----+-----------------+-----------------------+--------------------------------------------------------+---------------------+
3 rows in set (0.03 sec)
test@localhost:testdb> select * from comment;
+----+----------+-----------------+-----------------------------+--------------+---------------------+
| id | entry_id | name            | body                        | is_invisible | created_at          |
+----+----------+-----------------+-----------------------------+--------------+---------------------+
|  1 |        2 | 名無しさん      | 俺も俺も                    |            0 | 2011-11-30 15:07:41 |
|  2 |        2 | マヒャド        | うまいよねー                |            0 | 2011-11-30 15:07:41 |
|  3 |        2 | ヒャダルコ      | たまに食いたくなる          |            0 | 2011-11-30 15:07:41 |
|  4 |        3 | 名無しさん      | 何それ!                    |            0 | 2011-11-30 15:07:41 |
+----+----------+-----------------+-----------------------------+--------------+---------------------+
4 rows in set (0.00 sec)

モデルクラス

Teng を使うためのモデルクラスを定義します。

package My::DB;
use parent 'Teng';

1;

スキーマクラス

テーブル情報を持つスキーマクラスを定義します。

package My::DB::Schema;
use Teng::Schema::Declare;
use DateTime::Format::MySQL;

table {
    name 'entry';
    pk 'id';
    columns qw( id name title body created_at );

    inflate 'created_at' => sub {
        DateTime::Format::MySQL->parse_datetime(shift);
    };
    deflate 'created_at' => sub {
        DateTime::Format::MySQL->format_datetime(shift);
    };
};

table {
    name 'comment';
    pk 'id';
    columns qw( id entry_id name body is_invisible created_at );

    inflate 'created_at' => sub {
        DateTime::Format::MySQL->parse_datetime(shift);
    };
    deflate 'created_at' => sub {
        DateTime::Format::MySQL->format_datetime(shift);
    };
};

1;

上記の様に、entry/comment のテーブル情報を定義します。

entry/comment テーブルのリレーションシップを設定

Teng でリレーション設定をするには、Teng::Row(以下、Rowオブジェクト) へメソッドを追加することで実現できます。

entry.id と comment.entry_id がそれぞれのテーブルで関係する ID となるので、これで関連付けをします。関連付けをすることにより、あるエントリに対するコメント一覧等の取得が簡単にできるようになります。

任意の entry に関連する comment のイテレータを返すメソッドを定義

entry → comment は1対多の has_many なリレーションとなります。

package My::DB::Row::Entry;
use strict;
use warnings;
use parent 'Teng::Row';

sub to_comments {
    my $self = shift;
    $self->{teng}->search(comment => +{ entry_id => $self->id });
}

1;
comment の Rowオブジェクトに関連する entry を取得するメソッドを定義

comment → entry は多対1 のbelongs_to なリレーションとなります。

package My::DB::Row::Comment;
use strict;
use warnings;
use parent 'Teng::Row';

sub to_entry {
    my $self = shift;
    $self->{teng}->single(entry => +{ id => $self->entry_id });
}

1;

サンプルコードと実行結果

これまででリレーションの設定ができたので、実際に使ってみます。

まずは has_many なリレーションのサンプルコードです。

use strict;
use warnings;
use utf8;
use feature qw( say );

use FindBin;
use lib "$FindBin::Bin/lib";
use My::DB;

my $teng = My::DB->new(connect_info => [
    'dbi:mysql:database=testdb',
    'test',
    'tes10',
    +{
        RaiseError => 1,
        PrintError => 0,
        AutoCommit => 1,
        on_connect_do => [
            "SET NAMES 'utf8'",
            "SET CHARACTER SET 'utf8'",
        ],
    },
]);

my $entry = $teng->single(entry => +{ id => 2 });
say "entry.id:         ", $entry->id,    "\n",
    "entry.name:       ", $entry->name,  "\n",
    "entry.title:      ", $entry->title, "\n",
    "entry.body:       ", $entry->body,  "\n",
    "entry.created_at: ", $entry->created_at;
say "----";

my $it = $entry->to_comments;
while (my $comment = $it->next) {
    say "comment.id:           ", $comment->id,           "\n",
        "comment.entry_id:     ", $comment->entry_id,     "\n",
        "comment.name:         ", $comment->name,         "\n",
        "comment.body:         ", $comment->body,         "\n",
        "comment.is_invisible: ", $comment->is_invisible, "\n",
        "comment.created_at:   ", $comment->created_at;
    say "--";
}

実行結果です。

entry.id:         2
entry.name:       あまりさん
entry.title:      シュークリーム
entry.body:       シュークリームがいっぱい食べたいよ!
entry.created_at: 2011-11-30T15:07:41
----
comment.id:           1
comment.entry_id:     2
comment.name:         名無しさん
comment.body:         俺も俺も
comment.is_invisible: 0
comment.created_at:   2011-11-30T15:07:41
--
comment.id:           2
comment.entry_id:     2
comment.name:         マヒャド
comment.body:         うまいよねー
comment.is_invisible: 0
comment.created_at:   2011-11-30T15:07:41
--
comment.id:           3
comment.entry_id:     2
comment.name:         ヒャダルコ
comment.body:         たまに食いたくなる
comment.is_invisible: 0
comment.created_at:   2011-11-30T15:07:41
--

続いて、belongs_to なリレーションのサンプルコードです。(重複するコードは割愛)

my $it = $teng->search(comment => +{ entry_id => 2 });
while (my $comment = $it->next) {
    my $entry = $comment->to_entry;
    say "entry.name:         ", $entry->name,   "\n",
        "entry.title:        ", $entry->title,  "\n",
        "comment.id:         ", $comment->id,   "\n",
        "comment.name:       ", $comment->name, "\n",
        "comment.body:       ", $comment->body, "\n",
        "comment.created_at: ", $comment->created_at;
    say "--";
}

実行結果です。

entry.name:         あまりさん
entry.title:        シュークリーム
comment.id:         1
comment.name:       名無しさん
comment.body:       俺も俺も
comment.created_at: 2011-11-30T15:07:41
--
entry.name:         あまりさん
entry.title:        シュークリーム
comment.id:         2
comment.name:       マヒャド
comment.body:       うまいよねー
comment.created_at: 2011-11-30T15:07:41
--
entry.name:         あまりさん
entry.title:        シュークリーム
comment.id:         3
comment.name:       ヒャダルコ
comment.body:       たまに食いたくなる
comment.created_at: 2011-11-30T15:07:41
--

最後に

Teng でリレーションを使うのは、非常に簡単だということ分かりました。今回は 1対1 や、多対多のリレーションは扱いませんでしたが、同じ様に簡単に扱えると思います。