Saving Many to Many..

I read the blog tutorial but it solves the problem in adding a tagged field in the posts column… But how to do this if you dont have it?

I am trying to add positions to layouts…

_form.php




<div class="simple">	

	<?php echo CHtml::activeLabelEx($model,'positions'); ?>

	<div>&nbsp;</div>

	<?php echo CHtml::activeCheckBoxList($model, 'positions', CHtml::listData($positions, 'id', 'name' ));?>

</div>



layoutsController.php


public function actionCreate()

	{

		$model=new Layouts;

		$positions = Positions::model()->findAll();

		if(isset($_POST['Layouts']))

		{

			$model->attributes=$_POST['Layouts'];

			print_r($model->positions);

			die();

			if($model->save())

				$this->redirect(array('show','id'=>$model->id));

		}



But then I will get an empy array

Have you added positions to safeAttributes()?

Yes but it will not work because it is not in the attribute… I dont have a model for the join table.

My foreign key is position_id, and primary key of related table id…

So if I do:

<?php echo CHtml::activeCheckBoxList($model, ‘position_id’, CHtml::listData($positions, ‘id’, ‘name’ ));?>

I get an error that Layout.position_id does not exists…

If I do :

<?php echo CHtml::activeCheckBoxList($model, ‘id’, CHtml::listData($positions, ‘id’, ‘name’ ));?>

The select form works but how to fill in the relations? The blog example is kinda cheating with this as it has a tag column in the Posts table aswell have the tags in the seperate Tags table:

http://www.yiiframework.com/doc/blog/start.design

With me the tables look like:

I am trying this out: http://www.yiiframework.com/forum/index.php?/topic/3479-an-idea-for-handling-many-many-relationships/

But cannot get it working yet…

I put:

in components/ActiveRecord.php




<?php

class ActiveRecord extends CActiveRecord {

	private $_rel_attribs;


	public function __isset($name) {

		echo $relation;

				die();

		if(isset($this->_rel_attribs[$name])) {

			return true;

		}

		return parent::__isset($name);

	}


	public function __set($name,$value) {		

		if($name === 'attributes' && is_array($value)) {

			$all_habtm = array();			

			foreach($this->relations() as $rel_name=>$relation) {				

				if($relation[0] === self::MANY_MANY) {

					$all_habtm[strtolower($relation[1]).'_id'] = $rel_name;

					print_r($relation);

					die();

				}

			}

			foreach($value as $key=>$val) {

				if(isset($all_habtm[$key])) {

					$this->_rel_attribs[$key] = $val;

				}

			}

		}

		return parent::__set($name, $value);

	}


	public function __get($name) {

		if(isset($this->_rel_attribs[$name])) {

			return $this->_rel_attribs[$name];

		}

		$rel = $this->relations();

		foreach($rel as $r=>$data) {

			if($data[0]===self::MANY_MANY && $name==strtolower($data[1]).'_id') {

				$result = $this->relationshipIds($r);

				$this->_rel_attribs[$name] = $result;

				return $result;

			}

		}

		return parent::__get($name);

	}

}

And in components/behaviors:




<?php

/**

 * Deals with many to many relations

 */

class Habtmbehavior extends CActiveRecordBehavior {

	public function relationshipIds($relation) {

		if(empty($this->owner->primaryKey)) {

			return array();

		}

		$relations = $this->owner->relations();

		list($table,$fk1,$fk2) = $this->parseTableDefinition($relations[$relation][2]);

		$command=$this->owner->dbConnection->createCommand("SELECT $fk2 FROM $table WHERE $fk1 = {$this->owner->primaryKey}");

		$reader=$command->query();

		$ids = array();

		foreach($reader as $row) {

			$ids[] = $row[$fk2];

		}

		return $ids;

	}


	public function afterSave() {		

		foreach($this->owner->relations() as $rel_name=>$relation) {			

			if($relation[0]!==CActiveRecord::MANY_MANY) {

				continue;

			}


			list($table,$fk1,$fk2) = $this->parseTableDefinition($relation[2]);


			print_r( $this->owner->{$fk1});

			die();


			if(!isset($this->owner->{$fk2})){

				continue;

			}


			$data = $this->owner->$fk1;

			

			if(empty($data)) {

				$data = array();

			}

			

			list($table,$fk1,$fk2) = $this->parseTableDefinition($relation[2]);


			if(!$this->owner->isNewRecord) {

				$command=$this->owner->dbConnection->createCommand("DELETE FROM $table WHERE $fk1 = {$this->owner->primaryKey}");

				$command->execute();

			}

			foreach($data as $id) {				

				$command=$this->owner->dbConnection->createCommand("INSERT INTO $table($fk1,$fk2) VALUES({$this->owner->primaryKey},$id) ");

				$command->execute();

			}


		}

	}


	private function parseTableDefinition($table_definition) {

		preg_match('/([^(]+)\(([^,*]+), ([^)]+)\)/',$table_definition,$matches);

		if(count($matches) !== 4) {

			throw new CHttpException('404', "unable to parse $table_definition");

		}

		return array_slice($matches,1);

	}

}



And in my model:




<?php

class Layout extends ActiveRecord

{

	/**

	 * The followings are the available columns in table 'Layouts':

	 * @var integer $id

	 * @var string $name

	 * @var string $filename

	 * @var string $image

	 * @var string $description

	 * @var string $modified

	 * @var integer $theme_id

	 */


	/**

	 * Returns the static model of the specified AR class.

	 * @return CActiveRecord the static model class

	 */

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	/**

	 * @return string the associated database table name

	 */

	public function tableName()

	{

		return 'layouts';

	}


	/**

	 * @return array validation rules for model attributes.

	 */

	public function rules()

	{

		return array(

			array('name','length','max'=>125),

			array('filename','length','max'=>125),

			array('image','length','max'=>125),

			array('name, filename, image', 'required'),

		);

	}


	/**

	 * @return array relational rules.

	 */

	public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

			'positions' => array(self::MANY_MANY, 'Positions', 'layout_has_positions(position_id, layout_id)'),

			'theme' => array(self::BELONGS_TO, 'Themes', 'theme_id'),

			'modules' => array(self::HAS_MANY, 'Modules', 'layout_id'),

			'pages' => array(self::HAS_MANY, 'Pages', 'layout_id'),				

		);

	}


	/**

	 * @return array customized attribute labels (name=>label)

	 */

	public function attributeLabels()

	{

		return array(

			'id' => 'Id',

			'name' => 'Name',

			'filename' => 'Filename',

			'image' => 'Image',

			'description' => 'Description',

			'modified' => 'Modified',

			'theme_id' => 'Theme',

		);

	}


    public function behaviors(){

        return array(

			'HabtmBehavior' => array(

                'class' => 'HabtmBehavior'

            )

		);

    }


}

But it will not set the foreign key’s in the join table.

Ok I finally have it working if I change:




	public function afterSave() {		

		foreach($this->owner->relations() as $rel_name=>$relation) {			

			if($relation[0]!==CActiveRecord::MANY_MANY) {

				continue;

			}


			list($table,$fk1,$fk2) = $this->parseTableDefinition($relation[2]);

			$data = $this->owner->{$fk1};

			

			if(empty($data)) {

				$data = array();

			}		


			if(!$this->owner->isNewRecord) {

				$command=$this->owner->dbConnection->createCommand("DELETE FROM $table WHERE $fk2 = {$this->owner->primaryKey}");

				$command->execute();

			}

			

			foreach($data as $id) {

				$command=$this->owner->dbConnection->createCommand("INSERT INTO $table($fk2,$fk1) VALUES({$this->owner->primaryKey},$id) ");

				$command->execute();

			}


		}




Still it is not really a nice solution as the foreign key in relations:

‘positions’ => array(self::MANY_MANY, ‘Position’, ‘layout_has_positions(position_id, layout_id)’),

With Crud is inserted the other way around and it will stop working… :blink:

Any help for these issues would be welcome :P and why isn’t there a Cookbook example for this or something in the manual? I think it’s a fairly common situation

In time we will simplify active record as far as we can, because it is regularly used in nearly every applications.

I think it is not high priority now, since even small improvements would involve significant amount of work.

Feel free to share your findings in cookbook pages, it’d definitely be appreciated.