How to save two related models with one form?

Hi,

I’ve three tables Post, Product and PostProduct to link the two former tables.

A Post can have 0 or 1 Product, a Product may be in 0 or 1 Post. I obtain this scheme :


Post                PostProduct           Product

--------            -----------           --------

id       1_____0..1 postId     0..1_____1 id

                    productId

and these relationships for Post model :


public function relations()

{

  return array(

    'product'=>array(self::HAS_ONE, 'PostProduct', 'postId'),

  );

}

, Product model :


public function relations()

{

  return array(

    'post'=>array(self::HAS_ONE, 'PostProduct', 'id'),

  );

}

and PostProduct model :


public function relations()

{

  return array(

    'post'=>array(self::BELONGS_TO, 'post', 'postId'),

    'product'=>array(self::BELONGS_TO, 'product', 'productId'),

  );

}

When I create a Post, I would like to eventually link a Product to it :


<div class="row">

<?php echo CHtml::activeLabel($post,'productId'); ?>

<?php echo CHtml::activeDropDownList($postProduct, 'productId', 

  CHtml::listData(

    $products,

    'id',

    'name'

)); ?>

</div>

But I fail to save PostProduct. I tried many ways, inspired by the forum, cookbook, or myself, but I got many different error messages.

This is the current actionCreate :


	public function actionCreate()

	{

		$post=new Post;

		$postProduct=new PostProduct;

		if(isset($_POST['Post'], $_POST['PostProduct']))

		{

			$post->attributes=$_POST['Post'];

			$postProduct->attributes=$_POST['PostProduct'];

			$valid=$post->validate();

			$valid=$postProduct->validate() && $valid;

			

			if($valid)

			{

				if(isset($_POST['submitPost']) && $post->save()) 

				{

					$postProduct->postId=$post->id;

					if($postProduct->save())

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

				}

			}

		}

		$products=new Product;

		$criteria=new CDbCriteria;

		$criteria->select='id, name';

		$criteria->join='inner join PostProduct on Product.id != PostProduct.productId';

		$criteria->condition='imageUrl is not null';

		$criteria->order="name";

		$products=Product::model()->findAll($criteria);

		$categories=Category::model()->findAll();

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

		  'post'=>$post, 

		  'categories'=>$categories,

		  'products'=>$products,

		  'postProduct'=>$postProduct,

		));

	}

which raise this error message :

Does anyone has an idea about the way to follow?

Kind regards,

Bruno.

Hi Bruno,

firts of all i don’t understand why you’re using a junction table with a 1 to 0…1 relation?

You’re saving PostProduct without setting the productId. You are using massive assignment so take care about save attributes. If you have assigned a foreign key constraint in your db this will at least always fail, if there was no product selected.

Greets

Thanks à lot yoshi, it works now!

I use a 1 to 0…1 relation because a post can have a Product.

This code work very well, but if you have a more beautiful idea, you’re welcome.


public function actionCreate()

{

  $post=new Post;

  $postProduct=new PostProduct;

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

  {

    $post->attributes=$_POST['Post'];


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

      $post->validate();

    else if(isset($_POST['submitPost']) && $post->save()) 

    {

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

      {

        $postProduct->productId=$_POST['PostProduct']['productId'];

        $postProduct->postId=$post->id;

      }

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

    }

  }

  $photographs=new Product;

  $criteria=new CDbCriteria;

  $criteria->select='id, name';

  $criteria->join='left outer join PostProduct on Product.id = PostProduct.productId';

  $criteria->condition='photograph is not null and PostProduct.productId is null';

  $criteria->order="name";

  $photographs=Product::model()->findAll($criteria);

  if(count($photographs)==0)

    $photographs = null;


  $categories=Category::model()->findAll();

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

    'post'=>$post, 

    'categories'=>$categories,

    'photographs'=>$photographs,

    'postProduct'=>$postProduct,

  ));

}

If it can have a product you just allow null for the foreign key column.

Secondly you can use an INNER JOIN statement instead of a LEFT JOIN combined with a NOT NULL in the WHERE clause. But i think using a FK column in the product table will simplify things a lot.

sorry for double post, please delete this one.

If it can have a product you just allow null for the foreign key column.

Secondly you can use an INNER JOIN statement instead of a LEFT JOIN combined with a NOT NULL in the WHERE clause. But i think using a FK column in the product table will simplify things a lot.

Well, that’s the point, I don’t want any [font=“Courier New”]null[/font] in this case. Post and Product are independent, they don’t refer each other. I like this way, it’s modular.

I’m not sure I can, it was my first shot and it returned all products when they’re all related to a Post. I’m not sure you see what I mean… But yes, it would work without the junction table.

See above, my first comment. The main goal is modularity. The shop can exist without the blog and conversely.

If you think I’m wrong, feel free to tell me your opinion. Well, actually, you’re already doing this.