please test my AR Enhancement: automatically sync MANY_MANY table when calling save()

Hi,

Thanks for great component!

I found small issue with current CAdvancedArBehavior version.

Here:




    protected function writeRelation($relation) 

    {

        ...

        // An array of objects is given

        foreach($this->owner->$key as $foreignobject)

        {

            if(!is_numeric($foreignobject))

            {

                $foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};                

            }

            $this->execute($this->makeManyManyInsertCommand($relation, $foreignobject));

        }

    }



The problem is that {$foreignobject->$relation[‘m2mForeignField’]} is the name of the field inside relation table and not in the foreign table.

For example, we have table posts with id primary key and categories also with id primary key and relation table post_category(category_id, post_id).

Then $foreignobject->{$foreignobject->$relation[‘m2mForeignField’]} is evaluated as $foreingobject->category_id and I have no such field in categories table.

This can be fixed with this change (actually CAdvancedArBehavior v0.2 works this way):




    protected function writeRelation($relation) 

    {

        ...

        // An array of objects is given

        foreach($this->owner->$key as $foreignobject)

        {

            if(!is_numeric($foreignobject))

            {

                //$foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};

                $foreignobject = $foreignobject->{$foreignobject->tableSchema->primaryKey};

            }

            $this->execute($this->makeManyManyInsertCommand($relation, $foreignobject));

        }

    }



Here we use primary key name from foreign table instead of key name from relation table.

I did the following modification to the original code, this is even shorter




diff --git a/protected/extensions/CAdvancedArBehavior.php b/protected/extensions/CAdvancedArBehavior.php

index 803b75f..2a00aeb 100644

--- a/protected/extensions/CAdvancedArBehavior.php

+++ b/protected/extensions/CAdvancedArBehavior.php

@@ -156,7 +156,7 @@ class CAdvancedArbehavior extends CActiveRecordBehavior

                {

                        if(!is_numeric($foreignobject))

                        {

-                               $foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};

+                               $foreignobject = $foreignobject->getPrimaryKey();

                        }

                        $this->execute($this->makeManyManyInsertCommand($relation, $foreignobject));

                }




I like this extension, it works beautifully. One thing: My app is using a SQLite database, which causes the "insert ignore" and "delete ignore" queries to fail. Dropping the ignore keyword from the query fixes this, so maybe you could conditionally insert the ignore keyword only for dbms that actually support it.

Apart from that little thing, well done!

Seems to work great, only problem I have is that it doesn’t seem to work with $model->attributes assignment.

The relation I have is $model->extensions.

If I do this (which is the default) when updating:


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

$model->extensions isn’t updated, even though $_POST[‘Foo’][‘extensions’] exists and is an array. I ended up doing this:




if(isset($_POST['Foo']['extensions'])){

        $model->extensions = $_POST['Foo']['extensions'];

        unset($_POST['Foo']['extensions']);

}

else $model->extensions = Array();



Also had to update lines 109 and 114 per rafa.informatica’s review, in order to get it to remove all if the array is empty

Did you add a rules() element for your extensions property to declare it safe? Otherwise Yii omits it by default during mass assignment.

Good point! Hadn’t considered that.

This is a great extension!

One question: how do I delete all MANY_MANY entries for a given related record? For example, what if I wanted to delete all MANY_MANY relation records for a given post_id?

in my opinion, you have at least two ways to solve this:

1 - set in the database relation "on delete cascade"

2 - put in the afterDelete() event of your model some code to delete all related records

i think the 1 is more elegant

:)

regards!!

I have faced problems when the relation is empty with v.3. I have changed writeRelation like above




  /** writeRelation's job is to check if the user has given an array or an

   * single Object, and executes the needed query */

  protected function writeRelation ( $relation )

  {

    $key = $relation['key'];

    

    // Only an object or primary key id is given

    if ( is_object( $this->owner->$key ) )

    {

      $this->owner->$key = array( $this->owner->$key );

    }

    

    // An array of objects is given

    foreach ( $this->owner->$key as $foreignobject )

    {

      if( !empty( $foreignobject ) )

      {

        if ( ! is_numeric( $foreignobject ) )

        {

          // $foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};

          //fix seb

          //$foreignobject = $foreignobject->{$foreignobject->tableSchema->primaryKey};

          //fix mrBig

          $foreignobject = $foreignobject->getPrimaryKey();

        

        }

        $this->execute( $this->makeManyManyInsertCommand( $relation , $foreignobject ) );

      }

    }

  }



this is the latest version, yes?

http://code.google.com/p/yii-user-management/source/browse/trunk/user/models/CAdvancedArBehavior.php?r=179

do I have to do anything special to use it with a checkboxlist or listbox? such as marking attributes as safe, or loading them , or calling save?

Hi,

I’ve tested your extension with transaction and it doesn’t seem to work.

Here my code




//set relations

$model->roles=$role->Ruolo;


$transaction=$model->dbConnection->beginTransaction();

try

{

	$checkVal=$model->save(false);


	if($checkVal){

		if(!empty($ips)){

			foreach($ips as $i=>$item){

				if(!empty($item->IP)){

					$item->IDUser=$model->IDUser;

					$item->sdave(false);

				}

			}

		}

	}


	$transaction->commit();


	if($checkVal)

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

}

catch(Exception $e)

{

	$transaction->rollBack();

	$errorMessage="Achtung!! error!";

	Yii::app()->user->setFlash('errorMessage',$errorMessage);

}



the model is the following:

User

Role

IP

the User has many to many relations with Role and 0 to many relations with IP

without error it works well and save the related table well.

If I trigger an error (sdave method) the values are still saved in the db

solved. one of my tables was myisam.

I think first version (+update part) is the best version. Is there any alternative for this script right now ( after about 2 years )

Check Thyseus’ extensions, he continued this thought process into a packaged extension long ago.

Hey Rangel Reale:

When use ‘$this->owner’. It occurs the following error: Property “UserGroupsUser.owner” is not defined

Please help me

Cheers

http://code.google.com/p/yii-user-management/source/browse/trunk/user/components/CAdvancedArBehavior.php

In line 159 the id of the realted object is retrieved if a object is passed to store as related:


$foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};

I think it must be:


$foreignobject = $foreignobject->{$foreignobject->tableSchema->primaryKey};

Example:

Category.id

PostCategories.category_id

PostCategories.post_id

Post.id

$Post->categories = array(Category::model()->findByPk(1));

$foreignobject is an object of Category, its id is stored in $foreignobject->id but the value of $relation[‘m2mForeignField’] is category_id.

Ihe downloads at http://www.yiiframework.com/extension/cadvancedarbehavior/ are outdated (v0.1 and v0.2)

As people write above,

the latest version is on google code and you should apply mrbig’s patch

thyseus, please update your extension on yii site and on google code!

Hey people,

good to know that people still uses this extension.

I just packaged a new version.

Thanks to dhampik to drop me a notice that there is still demand for this extension.

changelog:

  • added $ignoreRelations to ignore specified relations. The Behavior

will take all found many2many relations by default. Specify exceptions in

this array.

  • fixes all the bugs and glitches found in the discussion

please TEST and tell me if i forgot something ? :)

thyseus,

I just tested it and I get an error when I try to save model with the current version of extension.

The exception is on line


$foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};

It seems that the following happens:

3 tables {{news <id, title, text>}}, {{news_region <id, news_id, region_id>}} and {{region <id, name>}}.

Your script tries to get region.region_id property, instead of region.id property and that causes an error.

This problem could be solved with mrbig’s patch. See the discussion above.

Thanks for the useful extension.

A. With Postgresql as database, I had to change the following lines.




142c142

<                                               $info['m2mThisField'] = $this->owner->tableSchema->PrimaryKey;

---

>                                               $info['m2mThisField'] = $this->owner->tableSchema->primaryKey;






193c193

<               return sprintf("delete ignore from %s where %s = '%s'",

---

>               return sprintf("delete from %s where %s = '%s'",



B. To recap procedure to use this extension (collected from various places)

Use Case : A medicine contains many constituents, A constituent can be part of many medicines.

  1. Download into protected/extensions http://www.yiiframework.com/extension/cadvancedarbehavior/files/CAdvancedArBehavior.php.bz2

  2. Extract




bunzip2 CAdvancedArBehavior.php.bz2



  1. Import in config/main.php

        

'import'=>array(

                'application.models.*',

                'application.components.*',

+               'application.extensions.CAdvancedArBehavior',

        ),



  1. To define many to many relation between medicine and contents

In relations() method of models/Constituent.php




   return array(

+ 'medicines' => array(self::MANY_MANY,'Medicine','masters.medicine_contents(constituent_id,medicine_id) '),

  );



In relations() method of models/Medicine.php




   return array(

+'constituents' => array(self::MANY_MANY,'Constituent','masters.medicine_contents(medicine_id,constituent_id) '),

  );



5. To make CAdvancedArBehavior available, add the following in models/Medicine.php




+       public function behaviors(){

+          return array( 'CAdvancedArBehavior' => array(

+            'class' => 'application.extensions.CAdvancedArBehavior'));

+          }



  1. In actionCreate() and actionUpdate() methods of controllers/MedicineController.php
  • Assign constituents from POST before save() is called.

  • Based on the relations defined for medicine (above), CAdvancedArBehavior makes corresponding entries in medicine_contents table




+$medicine->constituents=$_POST["Medicine"]["constituents"];

$medicine->save();

$this->redirect(array('view','id'=>$medicine->medicine_id));



  1. UI to select 5 constituents for each medicine using a Drop Down list (To Do : option to select 0 or more, using javascript)

In views/medicine/_form.php




<div class="row">

<?php

$criteria=new CDbCriteria(array('order'=>'constituent_name',));

$constituentArs=Constituent::model()->findAll($criteria);


for($i=0; $i<5; $i++)

  {

  ?>

  <div class="row"> 

    <?php echo CHtml::activeDropDownList($medicine, "constituents[$i]", CHtml::listData($constituentArs, 'constituent_id', 'constituent_name'),array('prompt'=>'--Select--'));?>

<?php //echo $form->textField($constituent,'quantity',array('size'=>4,'maxlength'=>4));?>

  </div> 

  <?php

  }

?>

</div>