CakePHP1.3.6:HABTM(hasAndBelongsToMany)とscaffold

モデルのアソシエーション(リレーション)として、これまで、hasOne、hasMany、belongsTo検証したので、HABTM(hasAndBelongsToMany)、つまり、N:Mの関係を検証してみる。

scaffoldをやってびっくり。とっても簡単に定義できる。N:M(複数:複数)のリレーションは、設計上、どうしても敬遠したくなってしまうが、これなら躊躇無くモデリングできる。

テーブル

実験するために、以下の2つのテーブル(itemsとcategories)を用意する。

CREATE TABLE IF NOT EXISTS `items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `memo` text COLLATE utf8_unicode_ci NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `memo` text COLLATE utf8_unicode_ci NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

HABTMでは、中間テーブルを利用して、itemsとcategoriesの関係を保存する。
そのためのテーブルスキーマが以下。

REATE TABLE IF NOT EXISTS `category_items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `item_id` int(11) NOT NULL,
  `category_id` int(11) NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id` (`id`,`item_id`,`category_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

モデル。

categoriesとitemsのモデルを以下のように作成する。

category.php
<?php 

class Category extends AppModel {
	public $name = 'Category';

	public $hasAndBelongsToMany = array(
		"Item" => array(
			'className' => 'Item',
			'joinTable' => 'category_items',
			'foreignKey' => 'category_id',
			'associationForeignKey' => 'item_id',
			'unique' => true,
			'order' => 'item_id'
		)
	);
	
	public $validate = array(
			'name'=>array(
							array(
								'rule' => 'notEmpty',
								'message' =>'入力してください'),
							array(
								'rule' => 'isUnique',
								'message' =>'すでに登録されています'),
					),
	);
}

?>
item.php
<?php 

class Item extends AppModel {
	public $name = 'Item';

	public $hasAndBelongsToMany = array(
		"Category" => array(
			'className' => 'Category',
			'joinTable' => 'category_items',
			'foreignKey' => 'item_id',
			'associationForeignKey' => 'category_id',
			'unique' => true,
			'order' => 'category_id'
		)
	);
	
	public $validate = array(
			'name'=>array(
							array(
								'rule' => 'notEmpty',
								'message' =>'入力してください'),
							array(
								'rule' => 'isUnique',
								'message' =>'すでに登録されています'),
					),
	);
}

?>

scaffoldを作成する。

categories_contorller.php

まず、categoriesテーブルのためのscaffoldをつくる。

<?php
class CategoriesController extends AppController{
	public $name = 'Categories';
	
	public $scaffold;	
}
?>
items_controllers.php
<?php
class ItemsController extends AppController{
	public $name = 'Items';
	
	public $scaffold;
}
?>

scaffoldを使ってみる

まず、categoriesにアクセスしてカテゴリーの登録をする。
以下がscaffoldの登録画面。相変わらず画面上部にエラーが出るが、(エラー箇所を覗いてみると)どうやらtimestampフィールドをscaffoldでハンドルしようとする場合にでるようだ。
(timestampの件、解決しました。どうもありがとうございました。http://cakephp.jp/modules/newbb/viewtopic.php?topic_id=369&forum=3

最後のitemの欄はとりあえず無視しておいていい(後述)。

以下のように3つのカテゴリーを入力する。

それでは、itemを入力してみる。itemsにアクセスして、New Itemをクリックする。
開けてびっくり、categoryに入力したところがマルチでセレクトできる。

以下が参照画面。

この調子で商品1がカテゴリー1と3、商品2がカテゴリー2、3とアソシエートするように登録する。

それで、画面を戻してcategoriesにアクセスし、New Categoryをクリックすると以下のように、カテゴリー側の登録の際には、商品がマルチで選択できるようになっている。素晴らしい!!

では、この関係を表す情報は、というと、以下のようにcategory_itemsテーブルに保管されている(phpMyAdminでみたところ)。