joinsオプションを生成するMatchableビヘイビア

仕様変更しました。2010/6/06 13:00

  • find('matches')といった書式は破棄されました。'jointo'パラメータの指定のみで動作するようになりました。
  • この変更によりpaginateCountが適切に動作するようになりました

イントロダクション

ページネートする際、joinsオプションが肥大化することはよくあることですよね。
habtmとかを絡めると5つ以上のjoinsオプションを長々と書かなくてはならず、いっそquery()でSQLを直接発行したほうが早いよぷんすか! といった経験はありませんか?
それを解決するのがMatchableビヘイビアです。

このビヘイビアで何ができるの?

簡単な指定で、モデルのアソシエーションからjoinsオプションを自動生成できます。
指定のモデルから階層をくぐり抜けた先のアソシエーションも扱うことができる(再帰的)ので、複雑な条件指定もへっちゃらです。
※内部でunindModel()をしています。不要ならオプション等(後述)で回避できます。

使用方法

アプリケーションコードの概要
<?php
// Model
var $actsAs = array('Matchable');
// Controller その他
// 'jointo'という名前は設定で変更可能
$Model->find('all', array('jointo' => array('Hoge')))
$this->paginate['Model'] = array('jointo' => 'Hoge')
$Model->prepareJoins( ... )
設定
<?php
	var $optionName = 'jointo'; // joinsするモデルなどを指定するオプション名
	var $associations = array('hasAndBelongsToMany', 'hasOne', 'hasMany', 'belongsTo'); // 探索するアソシエーションの種類
	var $defaultOptions = array(
		'type' => 'LEFT',
		'unbind' => true,
	); // 'jointo' でのデフォルトのオプション。後述

上記のプロパティがデフォルトとして用意されています。
これらはビヘイビアのオプションで設定ができます。

<?php
// こんな感じで。
	$actsAs = array(
		'Matchable' => array(
			'optionNAme' => 'models',
		)
	);

生成するjoinsの指定方法、オプション

find()のパラメータで指定ができます。
デフォルトは'jointo'というパラメータ名になっています。

<?php
$Model->prepareJoins($jointo)

というメソッドの呼び出しも可能です。返り値はjoinsオプションになります。


基本的にこのパラメータにjoinsオプションに変換するモデル名を階層構造を用いて列挙するだけです。
この挙動はContainableビヘイビアの挙動とよく似ています。
複雑な指定方法は後述のサンプルを見てください。


更に、各アソシエーションにはオプションが指定できます。
設定できる項目は

  • type: デフォルト => 'LEFT'
  • unbind デフォルト => true

typeはJOINタイプの指定です。
unbindはそのアソシエーションをunbindModel()するかどうかです。

サンプルコード

User has Many Posts
Posts hasMany Comment
Posts habtm Tag
といった構造になっているとします。

<?php
	$this->User->find('all', 'jointo' => array(
		'Post' => array(
			'Tag',
			'Comment' => array(
				'type' => 'INNER',
			),
		)
	));
	// 上記のようにすると、内部でのjoinsオプションは以下のようになります
	array(
		array(
			'table' => 'posts',
			'alias' => 'Post',
			'type' => 'LEFT',
			'conditions' => array('User.id = Post.user_id'),
		),
		array(
			'table' => 'comments',
			'alias' => 'Comment',
			'type' => 'INNER',
			'conditions' => array('Post.id = Comment.post_id'),
		),
		array(
			'table' => 'posts_tags',
			'alias' => 'PostsTag',
			'type' => 'LEFT',
			'conditions' => array('Post.id = PostsTag.post_id'),
		),
		array(
			'table' => 'tags',
			'alias' => 'Tag',
			'type' => 'LEFT',
			'conditions' => array('PostsTag.tag_id = Tag.id'),
		),
	)

Containableビヘイビアとの共存

可能です。
ただし、このビヘイビアで指定したアソシエーションをContainableのマッピングでも指定するとうまく動くことはまずないでしょう。

最後に

説明が長いですが、機能は至ってシンプルです!
ソースコードもコメントがほとんど無いとはいえ、たかだか100行です。
眺めてみると、Behaviorを作る際の参考になるような仕掛けを見つけることができるかもしれません。