I made one big form with many associations hasMany/HABTM. All is workin great on creating (!). When updating all is working nice too, but in tables where association is hasMany, data is not updated or replaced, but just inserted. This brings many rows with trash data. How can I make saveAll() do the update/replace in hasMany fields:
Model:
class MainModel extends AppModel {
var $hasAndBelongsToMany = array(
'HABTMModel1',
...
'HABTMModeln',
);
var $hasMany = array(
'Model1' => array(
'dependent' => true
),
...
'Modeln' => array(
'dependent' => true
),
);
}
One of the problematic hasMany models look like:
class Model1 extends AppModel {
var $belongsTo = array(
'MainModel'
);
}
And his table have:
id <- Primary key, auto increment, int (11)
main_model_id <- Foreign_key int (11)
name <- text field, string
The $this->data is looking like:
array(
[MainModel] => array(
'id' => 123
*** aditional data named identicaly to table fields (working great)***
),
[Model1] => array(
[0] => array(
[name] => Test1
),
[2] => array(
[name] => Test2
),
),
*** all other models ***
);
Model1 table results after first creating and after updating:
id main_model_id name
--------------------------------------------------------------
11 306 Test1
12 306 Test2
13 306 Test1 (Thease are dublicates)
14 306 Test2 (Thease are dublicates)
What can I do to update/replace data in hasMany and not insert new values un edit using saveAll ?
Thank you.
In the view, just put hidden input for all the Model1, Model2 ids
echo $form->create('MainModel');
echo $form->hidden('Model1.0.id');
// more stuffs...
echo $form->end('Save');
You probably specify the 'fields' for Model1, Model2 to have only 'name'. That's why $this->data looks like that. So just add 'id' to that.
So I made not so beautiful, but working:
// If edit, then delete data from hasMany tables based on main_model_id
if(isset($this->data['MainModel']['id']) && !empty($this->data['MainModel']['id'])) {
$conditions = array('main_model_id' => $this->data['MainModel']['id']);
// Delete Model1
$this->MainModel->Model1->deleteAll($conditions, false);
...
all other models
...
}
$this->MainModel->saveAll($this->data);
You need to set the id field for your hasMany model too e.g.
array(
[MainModel] => array(
[id] => 123
*** aditional data named identicaly to table fields (working great)*** ),
[Model1] => array(
[0] => array(
[id] => 1,
[name] => Test1
),
[2] => array(
[id] => 2
[name] => Test2
), ), *** all other models *** );
Related
I have an issue with using the find query in cakephp.
The problem is as follows :
I have to two models associated with each other..say MODEL 1 hasmany MODEL2.
Now I want to run a find query through model 1 in such a way that it has to fetch different number of results from Model2 Table as follows :
array(
0 => ( Model1 : { table contents }
Model2 : { array( 0 =>
1 =>
2 =>
3 =>
)
}
)
1 => ( Model1 : { table contents }
Model2 : { array( 0 =>
1 =>
2 =>
3 =>
4 =>
)
}
)
)
and so on. In other words Result of Model2 should vary but Model1 has fixed number of entries. I wanted to know what would be the most efficient ways to obtain the above mentioned result.
Further adding -
Consider the following table - Student (id, class, marks)
I want the following result:
There are 100 rows in the table. I want to fetch a total of 11 rows.
Condition => class is 'Computer'
First row should print details of student having id = 15;
Remaining rows should have any 10 ids ie. id != 15.
In total I need a total of 11 rows.
One more constraint is that user having id = 1 can be anywhere in the table.
This is a relation between two model. Hope it helps you....
Model-1:
var $hasMany = array(
'ProductOptionValue' => array(
'className' => 'ProductOptionValue',
'foreignKey' => 'product_option_id',
'conditions' => array('ProductOptionValue.is_active' => 1)
)
);
Model-2:
var $belongsTo = array(
'ProductOption' => array(
'className' => 'ProductOption',
'foreignKey' => 'product_option_id',
'fields' => array('id', 'product_option_name')
)
);
I'm a cakephp newbie, and I was ordered to use the 1.3 version.
I can't understand (and both guides and api docs don't tell it) how I could create an HABTM association in a POST request.
I'm trying to create a wine, that could be made of many vines. For example I'm creating a "soave" whine, that is made of "garganega" and "chardonnay" vines.
How should the POST params should be?
Given theses models
class Wine extends AppModel{
var $hasAndBelongsToMany = array(
'Vine' => array(
'className' => 'Vine',
'joinTable' => 'wine_vines',
'foreignKey' => 'wine_id',
'associationForeignKey' => 'vine_id',
'with' => 'WineVine',
),
);
}
class Vine extends AppModel{
var $hasAndBelongsToMany = array(
'Wine' => array(
'className' => 'Wine',
'joinTable' => 'wine_vines',
'foreignKey' => 'vine_id',
'associationForeignKey' => 'wine_id',
'with' => 'WineVine',
),
);
}
class WineVine extends AppModel{
var $name = "WineVine";
public $belongsTo = array("Wine", "Vine");
}
I tried a POST like this:
Array
(
[Wine] => Array
(
[denomination] => Soave DOP
[fantasy_name] =>
[kind] => White
)
[Vine] => Array
(
[0] => Array
(
[name] => garganega
)
[2] => Array
(
[name] => chardonnay
)
)
)
but it does not perform any inserts in vine table, only in wine.
Here's the log:
2 INSERT INTO `wines` (`denomination`, `fantasy_name`, `kind`, `modified`, `created`) VALUES ('', '', '', '2013-10-25 17:27:14', '2013-10-25 17:27:14') 1 55
3 SELECT LAST_INSERT_ID() AS insertID 1 1 1
4 SELECT `WineVine`.`vine_id` FROM `wine_vines` AS `WineVine` WHERE `WineVine`.`wine_id` = 2 0 0 1
5 SELECT `Vine`.`id`, `Vine`.`name`, `Vine`.`created`, `Vine`.`modified` FROM `vines` AS `Vine` WHERE 1 = 1 5 5 0
6 SELECT `Wine`.`id`, `Wine`.`denomination`, `Wine`.`fantasy_name`, `Wine`.`kind`, `Wine`.`created`, `Wine`.`modified`, `Wine`.`producer_id`, `WineVine`.`id`, `WineVine`.`wine_id`, `WineVine`.`vine_id`, `WineVine`.`created`, `WineVine`.`modified` FROM `wines` AS `Wine` JOIN `wine_vines` AS `WineVine` ON (`WineVine`.`vine_id` IN (1, 2, 3, 4, 5) AND `WineVine`.`wine_id` = `Wine`.`id`)
after saving the wine, try injecting its id into the data array
$this->data['Wine']['id'] = $this->Wine->id;
and then call an overloaded Model::saveAssociated() that will save all vines and update the join table by itself.
this overloaded method is described at:
http://bakery.cakephp.org/articles/ccadere/2013/04/19/save_habtm_data_in_a_single_simple_format
edit: sorry, that's for cake 2.x
i just realized 1.3 has no saveAssociated method
edit 2: but it does work in cake 1.3 if you change the last line of the saveAssociated method to
return parent::saveAll($data, $options);
I'm currently using cakephp 2.2.3.
I have the following Model Associations:
VehicleModel -> Vehicle -> Order
Plan -> Order
Vehicle HABTM Tag
Inside the Vehicle controller, add action, I have:
if(!empty($this->request->data)) {
if($this->Vehicle->saveAll($this->request->data)) {
$this->Session->setFlash('Vehicle was successfully added.');
}
}
The $this->request->data array is formatted like this:
array(
'VehicleModel' => array(
'category_id' => '2',
'make_id' => '1'
),
'Order' => array(
'plan_id' => '2'
),
'Vehicle' => array(
'vehicle_model_id' => '13',
'price' => ' 8700',
'year' => '1994',
'km' => '100',
'color' => '61',
'fuel' => '1',
'gear' => '20',
'type' => '51',
'city' => 'Rio de Janeiro',
'state' => 'RJ'
),
'Tag' => array(
'Tag' => array(
(int) 0 => '69',
(int) 1 => '11'
)
)
)
The orders table has the following fields:
id , plan_id , vehicle_id , created , modified.
Vehicle Model:
class Vehicle extends AppModel {
public $belongsTo = array('User' , 'VehicleModel');
public $hasMany = array('Order' , 'Image');
public $hasAndBelongsToMany = array('Accessory' , 'Tag');
}
Order Model:
class Order extends AppModel {
public $belongsTo = array('Vehicle' , 'Part' , 'Plan');
public $validate = array(
'plan_id' => array(
'rule' => 'notEmpty'
)
);
}
The problem I'm having is that the Order.plan_id field is not being saved, although all other fields are being saved normally. What can I be doing wrong?
Just to be clear, When I do the multiple saving manually, everything
works just fine. I mean, when I write:
$this->Vehicle->save()
and then set
$this->request->data['Order']['vehicle_id'] = $this->Vehicle->id
and finally
$this->Vehicle->Order->save()
everything works just fine. It's the saveAll that is causing me
trouble.
If that is the case, see where $this->request->data['Order']['vehicle_id'] = $this->Vehicle->id. Comparing this to your var dump above, order never contains the relation to the main model, which leads me to ask if this is a new record you are trying to save or an update? I think you might have to not go with a saveAll here if you are setting a new record because the main id is not yet set. Please see:
http://book.cakephp.org/2.0/en/models/saving-your-data.html#saving-related-model-data-hasone-hasmany-belongsto
Particularly: "If neither of the associated model records exists in the system yet (for example, you want to save a new User and their related Profile records at the same time), you’ll need to first save the primary, or parent model."
They basically do the long version you are doing.
My model relationship is as follows:
Student hasMany ClassStudent
Class hasMany ClassStudent
ClassStudent belongsTo Student
ClassStudent belongsTo Class
ClassStudent is a join model.
What I want to do is create Student(s), use an existing Class or create a new one, and create a join model record that links the Students and classes.
I want to do this all in one call to save (if this is even possible).
I have tried:
$data = array(
'Student' => array(
'0' => array( ... ), // Data in here
'1' => array( ... ),
...,
'n' => array( ... )
),
'Class' => array(
'class_id' => x // The class that I want the above students to be associated with
)
)
What I want to do is create n records of students and also add them to a class (possibly creating a class at the same time if the users wants to add a new one). I also want to create a join model record for each Student to the Class when I am creating the Student records.
Is this possible? I am using Cake 2.1.0 (today's stable release), and I have tried the different types of saveAll (saveAssociated and saveMany) with $options['deep'] = true.
Is it possible my data array is not in the correct format?
EDIT:
I have also tried:
$data = array(
'ClassStudent' => array(
'0' => array(
'Student' => array (...), // Data
'Class' => array(id => x) // The id of the Class the Student should be associated to
...,
'n' => array(
'Student' => array(...), // n-th Student
'Class' => array(id => x)
)
);
$this->saveAll($data['ClassStudent'], array('deep' => true));
In the above case, it successfully creates new Student records in the students table, but nothing is created in the join table.
saveAll (saveAll is a wrapper to saveMany and saveAssociated) is the right tool for the job. Take a look at the documentation for saveAll, I don't see any notes about it changing for 2.1. Having taking more time to read, here are some thoughts
First off, is there a type in your array structure. You have
'Class' => array ( 'class_id' => x )
If the Class is already defined and you are just wanting to add a student, then it would be
'ClassStudent' => array( 'class_id' => x )
With that said, CakesPHP ORM should allow you to use a saveAll with on a hasMany using a numerical index, so if assuming you have a typo, there follow might work for you
$data = array(
'Student' => array(
'0' => array( ... ), // Data in here
'1' => array( ... ),
...,
'n' => array( ... )
),
'ClassStudent' => array(
'class_id' => x // The class that I want the above students to be associated with
)
)
$this->ClassStudent->saveAll($data);
I have two tables: internet_access_codes and radacct.
The internet_access_codes hasMany radacct records.
The join is internet_access_codes.code = radacct.username AND internet_access_codes.fk_ship_id = radacct.fk_ship_id
I created 2 models and wanted to use $hasMany and $belongsTo respectively so that the related radacct records would be pulled when getting and internet_access_codes record.
Here's the code:
class InternetAccessCode extends AppModel{
var $name = 'InternetAccessCode';
var $hasMany = array(
'Radacct' => array(
'className' => 'Radacct',
'foreignKey'=> false,
'conditions'=> array(
'InternetAccessCode.code = Radacct.username',
'InternetAccessCode.fk_ship_id = Radacct.fk_ship_id'
),
)
);
}
class Radacct extends AppModel{
var $name = 'Radacct';
var $useTable = 'radacct';
var $belongsTo = array(
'InternetAccessCode' => array(
'className' => 'InternetAccessCode',
'foreignKey' => false,
'conditions'=> array(
'InternetAccessCode.code = Radacct.username',
'InternetAccessCode.fk_ship_id = Radacct.fk_ship_id'
)
),
);
}
When I find() a record from internet_access_codes I expect it to give me all the relevant radacct records as well. However I got an error because it didnt do the join.
Here's the outcome and error:
Array
(
[InternetAccessCode] => Array
(
[id] => 1
[code] => 1344444440
[bandwidth_allowed] => 20000
[time_allowed] => 30000
[expires_at] => 31536000
[cost_price] => 0.00
[sell_price] => 0.00
[enabled] => 1
[deleted] => 0
[deleted_date] =>
[fk_ship_id] => 1
[downloaded_at] => 2011-09-10 22:18:14
)
[Radacct] => Array
(
)
)
Error: Warning (512): SQL Error: 1054: Unknown column
'InternetAccessCode.code' in 'where clause'
[CORE/cake/libs/model/datasources/dbo_source.php, line 684]
Query: SELECT Radacct.id, Radacct.fk_ship_id,
Radacct.radacctid, Radacct.acctsessionid,
Radacct.acctuniqueid, Radacct.username, Radacct.groupname,
Radacct.realm, Radacct.nasipaddress, Radacct.nasportid,
Radacct.nasporttype, Radacct.acctstarttime,
Radacct.acctstoptime, Radacct.acctsessiontime,
Radacct.acctauthentic, Radacct.connectinfo_start,
Radacct.connectinfo_stop, Radacct.acctinputoctets,
Radacct.acctoutputoctets, Radacct.calledstationid,
Radacct.callingstationid, Radacct.acctterminatecause,
Radacct.servicetype, Radacct.framedprotocol,
Radacct.framedipaddress, Radacct.acctstartdelay,
Radacct.acctstopdelay, Radacct.xascendsessionsvrkey FROM
radacct AS Radacct WHERE InternetAccessCode.code =
Radacct.username AND InternetAccessCode.fk_ship_id =
Radacct.fk_ship_id AND Radacct.deleted <> 1
In the app_model I also added the containable behaviour just in case but it made no difference.
Sadly cakephp doesn't work too well with the associations with foreign key =false and conditions. Conditions in associations are expected to be things like Model.field = 1 or any other constant.
The has many association first find all the current model results, then it finds all the other model results that have the current model results foreignKey... meaning it does 2 queries. If you put the conditions it will try to do it anyway but since it didn't do a join your query will not find a column of another table.
Solution
use joins instead of contain or association to force the join you can find more here
an example of how to use join
$options['joins'] = array(
array(
'table' => 'channels',
'alias' => 'Channel',
'type' => 'LEFT',
'conditions' => array(
'Channel.id = Item.channel_id',
)
));
$this->Model->find('all', $options);
Possible solution #2
BelongsTo perform automatic joins (not always) and you could do a find from radaact, the bad thing of this solution, is that it will list all radacct and put its internetAccesCode asociated instead of the internetAccesCode and all the radaact associated.... The join solution will give you something similar though...
You will need to do a nice foreach that organizes your results :S it won't be to hard though....
Hope this solves your problem.