JD Isaacks
JD Isaacks

Reputation: 57974

Saving HABTM with extra fields?

I am trying to save an order, and the products in the order.

The order is being saved, but the products are not.

I have an orders table and a products table and a orders_products table.

In the Order model I set $hasAndBelongsToMany = 'Product';

on the orders_products table I have a couple extra fields: order_id, product_id plus price, quantity to capture the sale price and quantity sold.

I am saving the data via:

$this->Order->saveAll($data);

Here is what $data is:

Array
(
    [Order] => Array
        (
            [user_email] => [email protected]
            [billing_first] => Steve
            ... //more excluded
            [total] => 5000
        )

    [Product] => Array
        (
            [0] => Array
                (
                    [id] => 1
                    [price] => 5000.00
                    [quantity] => 1
                )

        )

)

The order gets saved to the order table but nothing is getting saved to the orders_products table. I am expected the orders_products table to save [new_order_id], 1, 5000.00, 1

I do get this notice:

Notice (8): Undefined index: id [CORE/cake/libs/model/model.php, line 1391]

Model::__saveMulti() - CORE/cake/libs/model/model.php, line 1391
Model::save() - CORE/cake/libs/model/model.php, line 1355
Model::__save() - CORE/cake/libs/model/model.php, line 1778
Model::saveAll() - CORE/cake/libs/model/model.php, line 1673
CartsController::saveOrder() - APP/controllers/carts_controller.php, line 128
CartsController::checkout() - APP/controllers/carts_controller.php, line 172
Dispatcher::_invoke() - CORE/cake/dispatcher.php, line 204
Dispatcher::dispatch() - CORE/cake/dispatcher.php, line 171
[main] - APP/webroot/index.php, line 83

Any ideas?

Upvotes: 4

Views: 4801

Answers (6)

Davorama
Davorama

Reputation: 76

It could be that your orders_products table does not have it's own primary key. You will get this error unless your table looks something like this

CREATE TABLE orders_products (
  id integer,
  order_id integer,
  product_id integer,
  ...

I only just now had this same problem. Sorry such a simple answer did not get to you sooner.

Upvotes: 0

Borislav Sabev
Borislav Sabev

Reputation: 4856

Well HATBM works in an very strange way. If you really need HATBM I would suggest NOT changing a model association, but using an interesting shorthand for saving additional data fields in the join table.

I've encountered this a lot of times and this is the best solution: Actually, just for saving your additional fields you can unbind all HATBM relations and add a new hasMany binding "on the fly" pointing to the join model. Firstly you should unbind the existing HATBM relation.

$this->Order->unbindModel(array('hasAndBelongsToMany' => array('Product')));

Then add a new "on-the-fly" hasMany binding:

$this->Order->bindModel(array('hasMany' => array('OrdersPost')));

Then if your data is:

$this->data['Order']['id'] = '1';
$this->data['OrdersPost'][0]['product_id'] = '15';
$this->data['PostsTag'][0]['price'] = '5000.00';
$this->data['PostsTag'][0]['quantity'] = '1';
$this->data['OrdersPost'][1]['product_id'] = '16';
$this->data['PostsTag'][1]['price'] = '4000.00';
$this->data['PostsTag'][1]['quantity'] = '2';

As mentioned above, HATBM always erases all existing records before inserting new ones. So maybe you should take care about that before you do:

$this->Order->saveAll($this->data);

This works when you dont want to change your datamodel, but still need additional functionality. Hope this helps. Original Solution and Credits HERE

Upvotes: 2

gdm
gdm

Reputation: 7938

You can use the previous solution, BUT if have also to update the parent tables, you must unbind. In my example I have a table Order and a table Product. I needs to put two extra fields for every product with respect to the Order.id . See this example:

    $this->Order->create(); 
    $this->Order->unBindModel(array('hasAndBelongsToMany'=>array('Product')));
    $f  = $this->Order->save($this->data,false);

    /* Save extra columns in HABTM TABLE */
    $this->Order->bindModel(array('hasMany'=>array('OrderProduct')));
    $q = array();
    if (!isset($this->data['Product']))
    {
        $v  = false;
    }
    else
    {
        $v =true;

        for($i=0;$i<count($this->data['Product']);$i++)
        {
            $this->data['OrderProduct'][$i]['order_id']     = $this->Order->getLastInsertID();
            $this->data['OrderProduct'][$i]['product_id']   = $this->data['Product'][$i];
            $this->data['OrderProduct'][$i]['quantity']         = $this->data['quantity'][$i];
            $this->data['OrderProduct'][$i]['state_id']         = $this->data['State'][$i];
        }
    }

    $s  = $this->Order->OrderProduct->saveAll($this->data['OrderProduct']);

Upvotes: 0

vindia
vindia

Reputation: 1678

Saving that additional data in your orders_products table doesn't seem like a good idea to me. What if a order has more than 1 product. Do you store the total amount of the order for each entry in the orders_products table? Why don't you just store that information in the Order? That makes much more sense to me.

Your error seems to come from a broken data model.

Upvotes: 0

junwafu
junwafu

Reputation: 333

Another problem I usually encounter with using SaveAll is that it doesn't save the related records. In your example, the Order is saved but the OrderProducts(or OrderItems) are not saved. What I usually do is something like this:

if ($this->Order->save($this->data)) {
    for($i=0; $i<sizeof($this->data['OrderProduct']); $i++){
        $this->data['OrderProduct'][$i]['order_id'] = $this->Order->id;
    }
    $this->Order->OrderProduct->saveAll($this->data['OrderProduct']);
}

What happens here is that the Order is saved first, then its ID is copied to each OrderProduct. Then OrderProduct records are saved.

Upvotes: 1

Jason McCreary
Jason McCreary

Reputation: 72991

HABTM is over-sold. A lot of the times it fails to meet the needs, such as when you have additional data to store. You'll be better off to do a hasMany/belongsTo relationship between the models.

Taken from the CakePHP Book:

What to do when HABTM becomes complicated?

By default when saving a HasAndBelongsToMany relationship, Cake will delete all rows on the join table before saving new ones. For example if you have a Club that has 10 Children associated. You then update the Club with 2 children. The Club will only have 2 Children, not 12.

Also note that if you want to add more fields to the join (when it was created or meta information) this is possible with HABTM join tables, but it is important to understand that you have an easy option.

HasAndBelongsToMany between two models is in reality shorthand for three models associated through both a hasMany and a belongsTo association.

In your case I would suggest making a LineItem model and joining everything that way:

  • Order hasMany LineItem
  • LineItem belongsTo Order, Product

Upvotes: 9

Related Questions