AR question

When I create new User(), related objects are not saved:

User model:

class User extends CActiveRecord


{


	public $name;


	public $password;


	public $loginIp;


	public $registrationDate;


	public $userStatusId = UserStatus::WaitingApproval;


	public $userRoleId = UserRole::User;





	/**


	 * 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 'user';


	}





	/**


	 * @return array validation rules for model attributes.


	 */


	public function rules()


	{


		return array(


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


			array('password','length','max'=>32),


			array('loginIp','length','max'=>15),


			array('password, registrationDate', 'required'),


		);


	}





	/**


	 * @return array relational rules.


	 */


	public function relations()


	{


		return array(


			'status' => array(self::BELONGS_TO, 'UserStatus', 'userStatusId'),


			'role' => array(self::BELONGS_TO, 'UserRole', 'userRoleId'),


			'profile' => array(self::HAS_ONE, 'UserProfile', 'userId'),


			'emails' => array(self::HAS_MANY, 'UserEmail', 'userId'),


		);


	}





	/**


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


	 */


	public function attributeLabels()


	{


		return array(


		);


	}


}

code:



$user = new User(array(


	'password' => md5($this->password),


	'registrationDate' => date('Y-m-d H:i:s'),


	'loginIp' => Yii::app()->getRequest()->getUserHostAddress(),


	'emails' => array(new UserEmail(array(


		'email'=>$this->email,


		'isPrimary' => true,


	))),


	'profile' => new UserProfile(array(


		'firstName' => $this->firstName,


		'lastName' => $this->lastName,


	)),


));





$user->save();


where is my mistake?

This is by design. Related objects are not saved/deleted automatically by AR.

It’s bad :) And what the main problem to make automatic saving?

There's no technical problem.

The main problem lies in education. That is, in order to support this feature, users need to learn a lot more. And it often causes more trouble than benefit.

We may add this feature in future if we receive enough requests for it.

You as Stive Jobs and pleasefixtheiphone resource :)

So what purpose have relations if you can't modify collection items?

I would like to see such functionality too.

The main benefit of having relations is to simplify relational queries.

Quote

This is by design. Related objects are not saved/deleted automatically by AR.

I also think its better this way.

I do no see why someone could not create a behavior for this (unless I am being short-sited)

Please, implement this :)

I am implementing a custom afterValidate() and afterSave() that considers related objetcs that are set, but I cannot access a private variable in a subclass of CActiveRecord.




abstract class CActiveRecord extends CModel

{

...

	private $_related=array();					// attribute name => related objects

...

}



Is it possible and/or correct to change visibility to protected? Only this way I can implement a setRelated() in a subclass.

Is there any other concrete way to implement this? Behaviors are not so well documented, I can’t imagine how would it be.

The work is done with these two methods, afterValidate() and afterSave(), changing the visibility of variable above and implementing a custom setAttribute().

I think a method should be implanted into the core to get the relations… create a ticket?

can’t you just use the relations method that the AR class has anyway?

btw …

I would also vote for this feature to be implement

comment.user = some_user_object is a lot prettier than comment.user_id = some_user_object.id

I have implemented the following:




class ActiveRecordWithRelated extends CActiveRecord

{

	/**

	 * As parent class including related objects without load them.

	 * @return array Same as parent including related

	 */

	public function getAttributes($names=true)

	{

		$attributesValues = parent::getAttributes($names);

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

		foreach($relations as $name => $relation) {

			if($this->hasRelated($name)) {

				$attributesValues[$name] =

				    $this->getRelated($name);

			}

		}

		return $attributesValues;

	}


	public function saveWithRelated($runValidation=true,$attributes=null)

	{

		if(!$this->save($runValidation,$attributes)) {

			return false;

		}

		$relatedsToSave = array();

		$saveAttributesAfterRelateds = array(/*attribute => object to getPrimaryKey()*/);

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

		foreach($relations as $name => $relation) {

			if($this->hasErrors()) {

				break;

			}

			if($this->hasRelated($name)) {

				$relateds = $this->getRelated($name);

				if(empty($relateds)) {

					continue;

				}

				if(!is_array($relateds)) {//HAS_ONE or BELONGS_TO

					$relateds = array($relateds);

				}//} else { HAS_MANY or MANY_MANY

				$pk = $this->getPrimaryKey();

				$fk = $relation->foreignKey;

				foreach($relateds as $related) {

					if($relation instanceof CBelongsToRelation) {

						$saveAttributesAfterRelateds = array(

						    $fk=>$related

						);

						continue;

					}

					$related->$fk = $pk;

					$relatedsToSave[] = $related;

				}

			}

		}

		$alreadySaved = array();

		$ret = true;

		foreach($relatedsToSave as $related) {

			$pk = $related->getPrimaryKey();

			if(in_array($pk, $alreadySaved)) {

				continue;

			}

			if(!$related->saveWithRelated($runValidation, $attributes)) {

				$ret = false;

				break;

			}

			$alreadySaved[] = $related->getPrimaryKey();

		}

		if($ret) {

			$attributesToUpdate = array();

			foreach($saveAttributesAfterRelateds as $fk => $related) {

				$attributesToUpdate[$fk] = $related->getPrimaryKey();

			}

			if(!empty($attributesToUpdate)) {

				$pk = $this->getPrimaryKey();

				$this->updateByPk($pk, $attributesToUpdate);

			}

		}

		return $ret;

	}


	/**

	 * As parent class but permits setting related objects.

	 * @param string attribute name or name of related in {@link relations}

	 * @param mixed attribute value or list of {@link CModel}

	 * @return boolean true or false

	 * @see hasAttribute

	 */

	public function setAttribute($name,$value)

	{

		$resp = parent::setAttribute($name, $value);

		if(!$resp && array_key_exists($name, $this->relations())) {

			$this->_related[$name] = $value; // I needed to change to protected to this work in this subclass! :-(

			$resp = true;

		}

		return $resp;

	}



Maybe it does not cover all relations idiosyncrasies, but it is a starting point.

I think it is better to have another save method that does save related objects.