Help needed on Relational ActiveRecord

Hi all,

I am new to Yii, now trying to use ActiveRecord to do my projects. Below is the 2 model generated by Yii shell from to MySQL linked table, Package and PackageDetail :




class Package extends CActiveRecord

{

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	public function tableName()

	{

		return 'Package';

	}


	public function relations()

	{

		return array(

			'packageDetails' => array(self::HAS_MANY, 'PackageDetail', 'packageId'),

		);

	}

	..

	..

}






class PackageDetail extends CActiveRecord

{

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	public function tableName()

	{

		return 'PackageDetail';

	}


	public function relations()

	{

		return array(

			'package' => array(self::BELONGS_TO, 'Package', 'packageId'),

		);

	}

	..

	..

}



I can find the record with package id 1, and want to read the colume data in the packageDetail




	$package =Package::model()->findByPk(1);

	$packageDetail = $package->packageDetails;


	$packageDetail->COLUME_NAME.. // Error here..



Can any expert advice what will be the code? I use 2 days to figure out… but :(

Or can guard me where/what to read to understand more about this ?

Thanks!

As a package can have multiple details (HAS_MANY), the related object will always be an array.

Try $packageDetail[0]->COLUME_NAME

Hi Pestaa,

It works! Thank you for your kind advice. My ActiveRecord concept still bad, trying to make use to it… Previously use query directly… lots of dirty works when update and maintaining.

Can I ask you another question, related to the above situation.

When come to add new record, I am using the /protected/views/package/_form.php generated by Yii shell. I would like to allow user to add a new record (in my case a new Package), together with the package details (HAS_MANY).

Can you brief me what is the steps I need to take to make the modification?

Here is my steps (not yet try, just thinkning):

  • Modify the /protected/views/package/_form.php to add some html form elements for the pakcage details.

  • Modify the /protected/models/Package.php, but don’t what to do.

  • Modify the /protected/controllers/PackageController.php actionCreate(), actionUpdate() method, also has no idea…

  • other steps…

Can you please advice? I actually migrating my old php apps to yii, and time is rush…

Reference: (actually, 100% generated by Yii shell)

/protected/controllers/PackageController.php




	public function actionCreate()

	{

		$model=new Package;

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

		{

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

			if($model->save())

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

		}

		$this->render('create',array('model'=>$model));

	}


	public function actionUpdate()

	{

		$model=$this->loadPackage();

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

		{

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

			if($model->save())

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

		}

		$this->render('update',array('model'=>$model));

	}



If PackageDetail has proper safeAttributes, you probably don’t have to touch the models for this scenario.

Make sure that form displays all the details by iterating through them. Provide a way to add new detail at the end of the page (it is done by javascript most of the time).

In your create and update actions, process all the details creating a new model each time. (Don’t forget to set the foreign key unless you set it via hidden field.)

E.g:

I would like to add a new package, name = "Children Package", link to packageDetail has 2 records,

  • detailName="Vitamin C", detailDos="500mg"

  • detailName="Calcium", detailDos="100mg"

I try to add only 1 package details first (should be "Vitamin C").

I have this code in my _form.php

[html]

<!-- Package -->

<div class="simple">

<?php echo CHtml::activeLabelEx($model,‘name’); ?>

<?php echo CHtml::activeTextField($model,‘name’,array(‘size’=>50,‘maxlength’=>50)); ?>

</div>

<!-- Package details -->

<div class="simple">

<?php echo CHtml::activeLabelEx($model,‘detailName’); ?>

<?php echo CHtml::activeTextField($model,‘detailName’,array(‘size’=>50,‘maxlength’=>50)); ?>

</div>

<div class="simple">

<?php echo CHtml::activeLabelEx($model,‘detailDos’); ?>

<?php echo CHtml::activeTextField($model,‘detailDos’,array(‘size’=>50,‘maxlength’=>50)); ?>

</div>

[/html]

Yii give me error. Property "Package.detailName" is not defined.

Can you please advice? I guess I am asking to much question already… but I have not much time to figure out my self… find not enough documentation about all this…

Yes, I can understand.

Unclear, question asked above.

Will try later…

Hi,

I tried my self, and has make it.




public function actionCreate()

{

	$model = new Package;

	$modelPackageDetail = new PackageDetail; # Added by me

	

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

	{

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

		

		# If the new package saved successfully, then save the package details

		if($model->save()) {

			$modelPackageDetail->attributes=$_POST['PackageDetail'];	# Added by me

			$modelPackageDetail->packageId = $model->id;	# Added by me, get the autoincrement value

			if($modelPackageDetail->save())					# Added by me

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

		}

	}


	# Modified, add the modelPackageDetail to pass to render

	$this->render('create',array('model'=>$model, 'modelPackageDetail'=>$modelPackageDetail));

}



Now the package + packageDetail has been saved to db properly, going to add some transaction commit/rollback handling.

Is this the normal way to perform this kind of logic? Do you have any advice on this?

Anyway thanks for your help so much!! ;D

You may want to call validate() instead of save() on package, because if the details cannot be saved, then you have to rollback the changes you made to package. Of course, don’t forget to save all models, once you make sure they are valid.

detailName is probably a column of packageDetail, so you cannot reach it via Package. You should try as following:




foreach($model->packageDetails as $packageDetail)



This way you can pass $packageDetails and ‘detailName’ to CHtml to generate all details. Please reread my previous post if this is clear now.

Hi Pestaa,

Yes you are right, I not using the validate() which is need to, get many data inconsistency error if the input got problem, will update it!

Thank you for your kind comment and advice. I will try to digest all the advice that you given. This is the first time I move to Framework, has no framework concept before, Yii is my choice after reading around the net.

Currently found Yii really help a lots in the application development time. I do believe I help also in the application maintainance efforts.

You help me a lots, I am moving further now in my coding. I will post again in this forum if I really cannot found the solutions.

Thanks again, friend!

You are very welcome. I know it is hard to pick up the idea to use someone else’s work to speed up your own development time, but this is really worth it, and will eventually make you a better programmer, especially if you are self taught, like many others here.

Good luck!

Hi Pestaa,

I need your advice on this case again.

My question is on massive assignment for dynamic generated html form elements.

The number of packageDetail for different package is different. At the add new package page, my application setup will need to define the number of packageDetail to show for every new package.

Let said I define 5 packageDetails for a new package.

When come to the "add new pacakge" view page, I should display 5 packageDetail html form to allow user to input.

Yes, I can hardcode the 5 set of packageDetails in the html form view. But the problem is, the value 5 is changeable by the user, the particular user might change the 5 to different value.

How can I dynamically solve this problem, by using ActiveRecord and massive assignment? I know it can be done by not using massive assignment (by reading the number of details and generate number of html form elements for each packageDetails), but there should have a better way…

Thanks.

Hi, I found the solution already. Actually the sample already generated by yii shell, inside my code ;D




	/**

	 * Lists all models.

	 */

	public function actionList()

	{

		$criteria=new CDbCriteria;


		$pages=new CPagination(Package::model()->count($criteria));

		$pages->pageSize=self::PAGE_SIZE;

		$pages->applyLimit($criteria);


		$models=Package::model()->findAll($criteria);


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

			'models'=>$models,  # <= Its here.. passing the array of models.

			'pages'=>$pages,

		));

	}



Thank you :lol: