hasAndBelongsToManyなテーブルでpaginate

cakephpのアソシエーションは便利ですが、マニュアルには詳しく書いてなかったので解決に時間がかかりました。


hasAndBelongsToManyな関係のテーブルがあった場合
書籍とタグの関係で、書籍名とタグで検索したい、という場合。

books
 id
 name

books_tags
 id
 book_id
 tag_id

tags
 id
 name

モデルでの記述

class Book extends AppModel {
    $hasAndBelongsToMany = array(
        'Tag' =>array(
            'className' => 'Tag',
            'joinTable' => 'books_tags',
            'with' => 'BooksTag',
            'foreignKey' => 'book_id',
            'associationForeignKey' => 'tag_id'
        )
    );
class Tag extends AppModel {
    $hasAndBelongsToMany = array(
        'Book' =>array(
            'className' => 'Book',
            'joinTable' => 'books_tags',
            'with' => 'BooksTag',
            'foreignKey' => 'tag_id',
            'associationForeignKey' => 'book_id'
        )
    );


書籍名で検索するなら

$this->paginate('Book',array('Book.name LIKE'=>$this->data['Book']['name'].'%'));

と簡単にできるのだが、タグで検索しようとして

$this->paginate('Book',array('Tag.id'=>$this->data['Tag']['id']));

なんてやっても

$this->paginate('Book',array('BooksTag.tag_id'=>$this->data['Tag']['id']));

なんてやっても、そんなカラムはないとエラーになってしまう。

デバッグ出力のクエリを見てみると、TagテーブルもBooksTagもJoinされていないので、エラーになるのは当たり前。


検索してみるといくつかヒットしたので、参考にして書き直してみる。(参考URL控えるの忘れてた(汗))

$this->paginate('BooksTag',array('BooksTag.tag_id'=>$this->data['Tag']['id']));

つまり「中間テーブルを基本にして検索する」という方法。
検索結果は意図通りに動いた。。。が、しかし、タグが必ず紐づいてる場合にしかヒットしなくなってしまうため、再び困る。


bindModelを使えば動的にModelのアソシエーションを変更できるのだから、検索時だけBooksTagをLEFT JOINする方法があるはず…と思い、キーワードを変えて再検索してみると、1.2の情報だけれど見つかったので実装してみる。

$conditions = array(
    'Book.name LIKE'=>$this->data['Book']['name'].'%',
    'BooksTag.tag_id'=>$this->data['Tag']['id']
);
$this->paginate['joins']=array(
    array(
        'type' => 'LEFT',
        'table' => 'books_tags',
        'alias'=>'BooksTag',
        'conditions' => 'Book.id=BooksTag.book_id'
    )
);
$this->paginate('Book',$conditions);

とりあえず解決。
paginateでのJOIN方法にたどり着くまでに時間がかかりすぎ(汗)

ただし、これは検索でもタグ指定が1つの場合のみ。複数指定するとカウントがおかしくなるらしいので注意が必要とのこと。(同一書籍に対して2タグヒット→2冊になる?)


この件に関して参考になりそうなサイトはこちら。
http://d.hatena.ne.jp/aroundthedistance/20090728/1248784179