Saving MANY_MANY records

Hi,

Are there any examples anywhere of saving MANY_MANY data (I know about the tags in the blog demo, but that is all done manually, and using a single text field, etc.)?

For example, if I had an Article model with 1 or more Categories. I want to use a multi-select drop down list – which model do I specify in the CHtml::activeDropDownList() call and how do I save the data submitted?

Limiting the selection to 3 categories is something else that is required, but not as important right now.

Any help greatly appreciated.

Thanks,

_da.

bump.

Use Article[CategoryIds][] to contain the selected category id,and then foreach to save MANY_MANY data  in afterSave() function of Article model.

Quote

Use Article[CategoryIds][] to contain the selected category id,and then foreach to save MANY_MANY data  in afterSave() function of Article model.

Yeah but which model/field do I use in the activeDropDownList?

Thanks,

_da.

No one knows how to handle multi-select input!!??

Any movement here?

Really, do somebody knows how to save MANY_MANY relations?

@sharehua - I didn't understand you trully, would please post an example of some kind?

Thanks in advance… :)

hi,

what I do is have a custom model ppty called



/**


*@var array


*/


private $_categoryIds;


and have getter / setter for it

getter : checks if ppty is null / if not get the categories related to the article / return $this->_categoryIds

This  allows to get the related categories from db or from form if the _categoryIds is already populated (by a massive assignment in the controller for instance)

setter : takes an array of numeric key/categories ids value pairs as parameter and sets $this->_categoryIds

This is used when you do a massive assignment in the controller for instance

and also include categoryIds in safeAttributes so that the article model will get the data from the form if you do a massive assignment (if you set it  by hand in the action then you don't need this)



//if you do this somewhere you need to tell the model that your custom ppty is safe to be massively assigned


$article->attributes = $_POST['Article']


in the form you can use activeDropDownList on the Article object since now you categoryIds is an Article ppty (attribute)



echo CHtml::activedropDownList($article, 'categoryIds', CHtml::listData($categoriesList, 'id', 'name'), array('multiple' => 1));


then in an afterSave call (in Article)  you loop through your categoryIds and use the join model ArticleCategory to setup relations



public function afterSave() {


	


		//saving relations with Category


		


		//update scenario


		if(!$this->isNewRecord) {


			


			//delete ArticleCategory relations


			ArticleCategory::model()->deleteAll('ArticleId = :ArticleId', array(':ArticleId' => $this->id));


			


		}


		


		//set ArticleCategory relations


		foreach($this->categoryIds as $category_id) {


			


			$relation = new ArticleCategory;


			$relation->ArticleId = $this->id;


			$relation->Category_id = $category_id;


			$relation->save();


			


		}


			


		parent::afterSave();


	


	}


all this is prbably not  complete or completely errorLess but this should help you

… so you do have separete ActiveRecord class for your relation table?

I do

but I don't know if this is the best way

it works nicely for me

you can always do without and save your relations with commands (someone more experienced might say more)

I've checked the blog demo distributed with Yii… There is many_many relationship there, but the insertion is made with sql command…

Is that the only way?

So as far as I can understood it - the only wat to save many to many relationship is to create a separete AR model for the relationship table or to insert values manually with SQL insert query. Am I correct?

If yes - I think that there should be a way to do this through AR, it is kind of dum to use SQL just for many to many relationships since everything else is covered by ActiveRecrod…

I am wondering too if this is automated in yii

but I don't think so

it might be added later, tis is no trivial task I guess

maybe qiang will answer

We had something similar in the early version of ActiveRecords in Prado. It leads to many problems if AR try to also add/update/delete related records. So the best way IMO is to really use a separate ActiveRecord for your relation table as this gives you full control over your n:m relations.

Thanks very much for your help/example Thomas. ;)

Quote

We had something similar in the early version of ActiveRecords in Prado. It leads to many problems if AR try to also add/update/delete related records. So the best way IMO is to really use a separate ActiveRecord for your relation table as this gives you full control over your n:m relations.

You mean use an AR model for the join table just when saving? i.e. When working with forms, create a 'fake' model attribute as suggested by Thomas?

_da.

@Thomas

If no category is selected, the attribute will be set to null which will cause the getter to retrieve the category IDs from the database, so the form will never show the empty selection … any ideas?

Also, and more importantly, $this->_categoryIds is null afterSave() when adding an Article, so I can't save the category IDs … how did you do it? In the controller action? (it works when editing)

Quote

Also, and more importantly, $this->_categoryIds is null afterSave() when adding an Article, so I can't save the category IDs ... how did you do it? In the controller action? (it works when editing)

why should it be null ?

did you declare a setter ?

public function setCategoryIds($categoryIds = array()) { 


		


		$this->_categoryIds = $categoryIds;


		


	}

Quote

@Thomas

If no category is selected, the attribute will be set to null which will cause the getter to retrieve the category IDs from the database, so the form will never show the empty selection … any ideas?

in the getter ?

Quote

why should it be null ?

did you declare a setter ?

Yes, but I didn't set an empty array as the parameter default – Thanks.

Quote

Quote

@Thomas

If no category is selected, the attribute will be set to null which will cause the getter to retrieve the category IDs from the database, so the form will never show the empty selection … any ideas?

in the getter ?

If no categories are selected by the user, the massive assignment won't set a value for the categoryIds attribute, so it will be null and cause the getter to load from the database. I'm using the following (before assignment) to prevent that (I'm probably missing something obvious):



        if (!empty($_POST['Discussion']) && !isset($_POST['Discussion']['categoryIds'])) {


            $_POST['Discussion']['categoryIds'] = array();


        }


(I'm using a Discussion model and not an Article model)

Here is my getter:



    public function getCategoryIds()


    {


        if ($this->_categoryIds === null) {


            $ddc = DiscussionDiscussionCategory::model()->findAllByAttributes(array('discussion_id' => $this->id));





            $ids = array();


            foreach ($ddc as $record) {


                $ids[] = $record->discussion_category_id;


            }





            $this->_categoryIds = $ids;


        }





        return $this->_categoryIds;


    }


Thanks for your help.

_da.

hi,

this is a tricky one for me as well,

I think you are missing this in your afterSave method :



//update scenario


		if(!$this->isNewRecord) {


			


			//delete ArtistEvent relations


			ArtistEvent::model()->deleteAll('Event_id = :Event_id', array(':Event_id' => $this->id));


			


		}


I have an Event / Artist relation

so this deletes all relations before setting the new ones, set in _artistsIds, if the array is empty no new relations are set and so you end up with no relations

the only thing that was left for me to fix was the case of reloading the form with validation errors, if I had removed all relations in the form (by deselecting everything in a dropdown for instance) the getArtistsIds would repopulate with ids from the db.

I fixed it like this :



if(!$this->hasErrors()) {





				foreach($this->Artist as $artist) {


				


					array_push($this->_artistsIds, $artist->id);


				


				}


				


			}


in getArtistsIds

if(!$this->hasErrors())

let me know if this works for you

if someone sees a flaw I'm all ears

Quote

... the only thing that was left for me to fix was the case of reloading the form with validation errors, if I had removed all relations in the form (by deselecting everything in a dropdown for instance) the getArtistsIds would repopulate with ids from the db.

That is exactly what I meant. Your suggestion of checking hasErrors() is a good one.

Thanks,

_da.