nested form with CActiveForm and updating record --- SOLVED

I’ve struggled to understand the nested form as the example in the Documentation talks about using CForm while if you generate the forms using CRUD via Gii it uses CActiveForm which behaves differently than CForm.

Now I’ve managed to create one page with one form for two different data models -hopefully I’ve done it right.

I have a User model with ID, Username, Password and a UserDetail model with Id, User_Id (FK), FirstName, LastName

so what I have done is to work on the UserDetail controller for the action update to also add the User model (username and password)the method’s code looks like this




	public function actionUpdate()

	{

		$userDetailModel=$this->loadModel();

                $userModel=new User;


		// Uncomment the following line if AJAX validation is needed

		$this->performAjaxValidation($userDetailModel);

                $this->performAjaxValidation($userModel);


		if(isset($_POST['UserDetail']) && isset($_POST['User']))

		{

                      // HERE IS WERE I SAVE BUT WILL TALK ABOUT THIS BIT LATER

		}


		$this->render('update',array(

			'userDetailModel'=>$userDetailModel,

                        'userModel'=>$userModel,            

		));

	}



Now the userDetail view is composed by two part, the partial _form and the userDetail view itself, i’m including here only the partial as the main view contains nothing substantial.




<div class="form">


<?php $form=$this->beginWidget('CActiveForm', array(

	'id'=>'user-detail-form',

	'enableAjaxValidation'=>false,

)); ?>


	<p class="note">Fields with <span class="required">*</span> are required.</p>


	<?php echo $form->errorSummary($userDetailModel); ?>


	<?php echo $form->errorSummary($userModel); ?>


	<div class="row">

		<?php echo $form->labelEx($userModel,'username'); ?>

		<?php echo $form->textField($userModel,'username',array('size'=>60,'maxlength'=>128)); ?>

		<?php echo $form->error($userModel,'username'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($userModel,'password'); ?>

		<?php echo $form->passwordField($userModel,'password',array('size'=>60,'maxlength'=>128)); ?>

		<?php echo $form->error($userModel,'password'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($userDetailModel,'email'); ?>

		<?php echo $form->textField($userDetailModel,'email',array('size'=>60,'maxlength'=>80)); ?>

		<?php echo $form->error($userDetailModel,'email'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($userDetailModel,'first_name'); ?>

		<?php echo $form->textField($userDetailModel,'first_name',array('size'=>60,'maxlength'=>80)); ?>

		<?php echo $form->error($userDetailModel,'first_name'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($userDetailModel,'last_name'); ?>

		<?php echo $form->textField($userDetailModel,'last_name',array('size'=>60,'maxlength'=>80)); ?>

		<?php echo $form->error($userDetailModel,'last_name'); ?>

	</div>


	<div class="row buttons">

		<?php echo CHtml::submitButton($userDetailModel->isNewRecord ? 'Create' : 'Save'); ?>

	</div>


<?php $this->endWidget(); ?>


</div><!-- form -->



Now everything works in terms of loading the correct data, however the directions I’ve followed from the documentation and from some tutorials advocate that my saving/update statement should look like this:




        $user = $form['user']->model;

        $profile = $form['profile']->model;

        if($user->save(false))

        {

            $profile->userID = $user->id;

            $profile->save(false);

            $this->redirect(array('site/index'));

        }



now transposing the above for my code an putting it in the saving statement (where I’ve put the comment in the code above) I should have something like this (remember I am working with UserDetail and not User)




        if($userDetailModel->save(false))

        {

            $userModel->id = $userDetailModel->user_id;

            $userModel->save(false);

            $this->redirect(array('site/index'));

        }



the code above will result in a data integrity exception error from SQL, because it is attempting to create a new record in the table user with an existing ID… At this point I’ve got lost what I want to do is to update an existing record and not creating a new one.

Digging around I’ve found somebody else using another method where the code above transform in a oneliner




if($userDetailModel->save(false) && $userModel->updateByPk($userDetailModel->user_id,$_POST['User']))

 $this->redirect(array('update','id'=>$userDetailModel->id));



I’ve tried it and works perfectly, my question is: Is this the correct approach? Why I have to use updateByPk method and not a more intuitive save() on the object?

I’m quite new to Yii not to OOP or PHP though, have been developing for over a decade. Any comment/suggestion is very welcome.

Thanks.

Cheers.

Try to use the blog example.

The update action:

public function actionUpdate()

{

&#036;model=&#036;this-&gt;loadModel();


if(isset(&#036;_POST['Post']))


{


	&#036;model-&gt;attributes=&#036;_POST['Post'];


	if(&#036;model-&gt;save())


		&#036;this-&gt;redirect(array('view','id'=&gt;&#036;model-&gt;id));


}


&#036;this-&gt;render('update',array(


	'model'=&gt;&#036;model,


    ));

}

I guess you code will be a little bit different and you will use both $userDetailModel and $userModel models.

I’m afraid your comment doesn’t really help, thanks for trying.

Is there anybody else from the Yii core community who may be able to suggest best practices here?

Thanks

Your mistake is the second line of action update:




        public function actionUpdate()

        {

                $userDetailModel=$this->loadModel();

                $userModel=new User;



I suppose that the model you need already exist in database (or you should not get the data integrity exception error from SQL), so maybe you can do something like that:




        public function actionUpdate()

        {

                $userDetailModel=$this->loadModel();

                $userModel=$userDetailModel->user;



Under the hipotesys that in userDetail there is a relation named "user" that refers to his own user.

For "best practice with multiple form" give a look to this post

Hi there, yes I’ve soon realized yesterday that the problem was in there as I was creating a new object instead of loading up the related recordset linked to the user detail table.

Firstly I’ve created a method in the user detail controller to load the parent model … but

soon I’ve realized that I’ve forgot there were relations defined between the data models, and came up

to the same solution you proposed.

It took a little bit of suffering but I’ve got there, and that’s good.

thanks a lot for the link. This is the kind of things the documentation and tutorial should includes as sometime people get lost in those simple things and give up using an excellent tool like this one is.

Cheers

T.