saveall not saving associated data - cakephp

I'm having a problem trying to save (update) some associated data. I've read about a million google returns, but nothing seems to be the solution. I'm at my wit's end and hope some kind soul here can help.
I'm using 1.3.0-RC4, my database is in InnoDB.
Course has many course_tees
CourseTee belongs to course
My controller function is pretty simple (I've made it as simple as possible):
if(!empty($this->data))
$this->Course->saveAll($this->data);
I've tried a lot of different variations of that $this->data['Course'], save($this->data), etc without luck.
It saves the Course info, but not the CourseTee stuff. I don't get an error message.
Since I don't know how many tees any given course will have, I generate the form inputs dynamically in a loop.
$form->input('CourseTee.'.$i.'.teeName', array(
'error' => false,
'label' => false,
'value'=>$data['course_tees'][$i]['teeName']
))
The course inputs are simpler:
$form->input('Course.hcp'.$j, array(
'error' => false,
'label' => false,
'class' => 'form_small_w',
'value'=>$data['Course']['hcp'.$j]
))
And this is how my data is formatted:
Array
(
[Course] => Array
(
[id] => 1028476
...
)
[CourseTee] => Array
(
[0] => Array
(
[key] => 636
[courseid] => 1028476
...
)
[1] => Array
(
[key] => 637
[courseid] => 1028476
...
)
...
)
)

According to CakePHP conventions you must provide [course_id] => 1028476 and not [courseid] => 1028476. Check you model bindings as well (capitalizations and underscores). There must be "Course has many CourseTee".
Do this way to save:
if ($this->Course->saveAll($this->data, array('validate' => 'first'))) {
$this->_flash(__('Successfully saved.', true), 'success');
} else {
$this->_flash(__('Cannot save. Does not validates.', true), 'error');
}

Related

Cakephp contain and belongsTo hasMany conditions

From the CakePHP manual:
Let's say you had a belongsTo relationship between Posts and Authors. Let's say you wanted to find all the posts that contained a certain keyword ("magic") or were created in the past two weeks, but you want to restrict your search to posts written by Bob:
array (
"Author.name" => "Bob",
"OR" => array (
"Post.title LIKE" => "%magic%",
"Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
)
)
However, its not working for me. I've got Scrapes belongsTo Vargroup, and Vargroup hasMany Scrape. My scrapes table has a field vargroup_id
When I do:
$this->Vargroup->contain('Scrape');
$this->Vargroup->find('first');
I get:
Array
(
[Vargroup] => Array
(
[id] => 16950
[item_id] => 1056
[image] => cn4535d266.jpg
[price] => 22.95
[instock] => 1
[timestamp] => 2012-10-29 11:35:10
)
[Scrape] => Array
(
[0] => Array
(
[id] => 18163
[item_id] => 1056
[vargroup_id] => 16950
[timestamp] => 2012-05-23 15:24:31
[instock] => 1
[price] => 22.95
)
)
)
But when I do :
$this->Vargroup->contain('Scrape');
$this->Vargroup->find('first', array(
'conditions' => array(
'not' => array(
array('Scrape.timestamp >' => $today)
)
)
));
Im getting The following error with associated sql output
Warning (512): SQL Error: 1054: Unknown column 'Scrape.timestamp' in 'where clause'
Query: SELECT `Vargroup`.`id`, `Vargroup`.`item_id`, `Vargroup`.`image`, `Vargroup`.`price`, `Vargroup`.`instock`, `Vargroup`.`timestamp` FROM `vargroups` AS `Vargroup` WHERE `Scrape`.`timestamp` = '2012-10-29
It doesn't look like its binding the table at all..
Any help would be appreciated.
Thanks
Try with this:
$this->Vargroup->contain('Scrape');
$this->Vargroup->find('first', array(
'conditions' => array(
'not' => array(
'Scrape.timestamp >' => array($today)
)
)
));
i think this examples from cakephp page tell the right way to do a "not" condition,
check it out.
array(
"NOT" => array("Post.title" => array("First post", "Second post", "Third post"))
)
this is the complex find conditions page if you have more questions. Cakephp complex find conditions

CakePHP: saving associated data

I'm trying to save IssueHistoryDescription, which belongsTo IssueHistory.
So IssueHistory hasMany IssueHistoryDescription. This has all been set in the model.
Yet, when I save this in IssueHistory, using $IssueHistory->save($data);
(With or without a $IssueHistory->create(); before...)
Array
(
[IssueHistory] => Array
(
[id] => 22
)
[IssueHistoryDescription] => Array
(
[old_description] => OLD
[description] => NEW
)
)
It doesn't work, nothing is saved.
When I try to use saveAssociated() I get an error:
Fatal error: Cannot use string offset as an array in /var/www/xdev/kipdomanager/cakephp/lib/Cake/Model/Model.php on line 2248
You can try this:
$data = array(
'IssueHistory' => array('id' => 2),
'IssueHistoryDescription' => array(
array('old_description' => 'OLD', 'description' => 'new')
)
);
$IssueHistory->create();
$IssueHistory->saveAll( $data );

cakephp find('all') not returning properly

So here is my latest issue:
I run this query in my Cakephp controller:
$acctRenewLast2Entries = $this->AccountRenew->find
(
'all',
array
(
'conditions' => array('Acc_Id' => $plan["Account"]["Acc_Id"]),
'order' => array('AccR_Id' => 'DESC')
)
);
I am expecting 4 records for this SQL statement. Instead, on running Debug in my controller, this is what I get for each row for above (see first record):
app/controllers/admins_controller.php (line 2584)
1
app/controllers/admins_controller.php (line 2584)
Array
(
[AccountRenew] => Array
(
[AccR_Id] => 470
[AccR_Date] => 2012-06-23 01:21:11
[AccR_Hstart_Date] => 2012-06-23 01:21:11
[AccR_Hend_Date] => 2012-08-23 01:21:11
[AccR_End_Date] => 2013-08-23 01:21:11
[AccR_Status] => PAID
[AccR_Reason] => RENEWAL
[Inv_Id] => 467
[Inac_Id] =>
[Acc_Id] => 196
[AccT_Id] => 44
[Amount] => 16
[AccP_Id] => 0
)
)
app/controllers/admins_controller.php (line 2584)
Array
(
[AccountRenew] => Array
(
[AccR_Id] => 465
[AccR_Date] => 2012-06-23 01:17:35
[AccR_Hstart_Date] => 2012-06-23 01:17:35
[AccR_Hend_Date] => 2012-07-23 01:17:35
[AccR_End_Date] => 2012-07-23 01:17:35
[AccR_Status] => PAID
[AccR_Reason] => RENEWAL
[Inv_Id] => 462
[Inac_Id] =>
[Acc_Id] => 196
[AccT_Id] => 41
[Amount] => 16
[AccP_Id] => 0
)
)
app/controllers/admins_controller.php (line 2584)
Array
(
[AccountRenew] => Array
(
[AccR_Id] => 269
[AccR_Date] => 2012-06-06 10:15:56
[AccR_Hstart_Date] => 2012-06-06 17:15:56
[AccR_Hend_Date] => 2012-06-20 17:15:56
[AccR_End_Date] => 2012-06-20 10:15:56
[AccR_Status] => TRIAL
[AccR_Reason] =>
[Inv_Id] => 0
[Inac_Id] =>
[Acc_Id] => 196
[AccT_Id] => 0
[Amount] => 0
[AccP_Id] => 0
)
)
Now, when I run sql_dump, I get the following query that was run :
SELECT `AccountRenew`.`AccR_Id`, `AccountRenew`.`AccR_Date`, `AccountRenew`.`AccR_Hstart_Date`, `AccountRenew`.`AccR_Hend_Date`, `AccountRenew`.`AccR_End_Date`, `AccountRenew`.`AccR_Status`, `AccountRenew`.`AccR_Reason`, `AccountRenew`.`Inv_Id`, `AccountRenew`.`Inac_Id`, `AccountRenew`.`Acc_Id`, `AccountRenew`.`AccT_Id`, `AccountRenew`.`Amount`, `AccountRenew`.`AccP_Id` FROM `account_renews` AS `AccountRenew` WHERE `Acc_Id` = 196 ORDER BY `AccR_Id` DESC 4 4
And when I run the above query in MySQL, I do get all my 4 records, including the first one which in the array appears as 1 (way up top of my write-up).
I sincerely hope someone out there can help, because I spent the last 1.5 days without any luck as to why MySQL pulls up the complete set, but Cake only seems to retrieve the last 3, and replace the first record with an array of "1".
Thanks in advance!
#user996302 this issue seems remotely connected to one of my Cake Lighthouse tickets you pointed out.
#GMOAdmin I suspect that there could be a problem with the name of your model as the word "renew" is a verb and since it has no plural form this could somehow be obstructing the CakePHP conventions as the Inflector class may not be able to translate this.
The correct noun is renewal and it's plural form: renewals. You can try renaming (according to the convetions) the DB table, Model - name and classname, Controller - name and classname and see if that works.
You can test if Inflector is handling this correctly via the following Inflector methods:
Inflector::pluralize($singular), Inflector::classify($tableName), Inflector::tableize($camelCase)
A quick fix would be to issue the working query with $this->ModelName->query($queryToRun); this way you can go around this since, as you say, the query runs correctly when ran over the DB. Overall this is truly and interesting issue and I suggest you have the CakeCore team look at it - if it is reproducable then this is a BUG and it need fixin.
Very strange problem.
I don't know what's happening, but I read recently a bug report which sounds a lot like your problem: ticket.
You may try as suggested by the author: put the param $showHTML (cake docs) as true:
debug($var, true, true);
Hope that helps.
Okay, I think I have found the answer. At least, this was causing the problems for me.
The CakePHP counterCache
What I had:
public $hasMany = array(
'Flight' => array(
'className' => 'Flight',
'foreignKey' => 'user_plane_id',
'counterCache' => true
)
);
Very stupid, but it did cause the problems. So perhaps you are having a counterCache as well? Or maybe you have set another key in the relationships wrong?
If you don't see the problem at my code snippet. I should have added the counterCache to the $belongsTo instead of the $hasMany. So it would be something like this:
public $belongsTo = array(
'UserPlane' => array(
'className' => 'UserPlane',
'foreignKey' => 'user_plane_id',
'counterCache' => true
)
);
note: I am running CakePHP 2.x and not 1.3!

CakePHP: include fieldname alias under related model

I am willing to group MySQL results by a column, and also count the records which belong to the same category:
$this->find('all', array(
'fields' => array('UserCheckin.id', 'UserCheckin.capital_id', 'COUNT(UserCheckin.id) as capital_checkins'),
'group' => 'UserCheckin.capital_id'
));
The problem is that capital_checkins is grouped as a separate key in the returned array, whilst I would like it to be under the UserCheckin model. Here's how the results are returned:
Array
(
[0] => Array
(
[UserCheckin] => Array
(
[id] => 3
[capital_id] => 10
)
[0] => Array
(
[capital_checkins] => 2
)
[Capital] => Array
(
[id] => 10
[name] => London
)
)
[1] => Array
(
...
)
)
Is there a way to include the alias under UserCheckin model?
In cakephp 1.3 you can use virtual field:
in your UserCheckin model:
var $virtualFields = array(
'capital_checkins' => 'COUNT(UserCheckin.id)'
);
IMPORTANT UPDATE: As #Rob Wilkerson correctly notices, adding a virtual field with aggregate function permanently to your model will [probably] mess up your find queries which do not have a GROUP BY (implicitly group all rows in MySQL or just fail with SQL error in SQL Server). Solution is to add a virtual field temporarily at a runtime before query that uses it:
$this->virtualFields['capital_checkins'] = 'COUNT(UserCheckin.id)';
$result = $this->find('all', array(
'fields' => array('UserCheckin.id', 'UserCheckin.capital_id', 'UserCheckin.capital_checkins'),
'group' => 'UserCheckin.capital_id'
));
//reset virtual field so it won't mess up subsequent finds
unset($this->virtualFields['capital_checkins']);
return $result;
'COUNT(UserCheckin.id) as UserCheckin__capital_checkins'
http://book.cakephp.org/2.0/en/models/virtual-fields.html#virtual-fields-in-sql-queries

Save multiple records for one model in CakePHP

I would like to save several records for one model. This would have been done pretty easily with saveAll() if it hadn't been for a problem:
I have a notification form, where I select multiple users from a <select> and two fields, for subject and content respectively. Now, when I output what $this->data contains, I have:
Array([Notification] => Array
(
[user_id] => Array
(
[0] => 4
[1] => 6
)
[subject] => subject
[content] => the-content-here
)
)
I've read on Cake 1.3 book, that in order to save multiple records for a model, you have to have the $this->data something like:
Array([Article] => Array(
[0] => Array
(
[title] => title 1
)
[1] => Array
(
[title] => title 2
)
)
)
So, how do I 'share' the subject and content to all selected users?
First off, this database design needs to be normalized.
It seems to me like a Notification can have many Users related to it. At the same time, a User can have many Notifications. Therefore,
Introduce a join table named users_notifications.
Implement the HABTM Relationship: Notification hasAndBelongsToMany User
In the view, you can simply use the following code to automagically bring up the multi-select form to grab user ids:
echo $this->Form->input('User');
The data that is sent to the controller will be of the form:
Array(
[Notification] => Array
(
[subject] => subject
[content] => contentcontentcontentcontentcontentcontent
),
[User] => Array
(
[User] => Array
(
[0] => 4
[1] => 6
)
)
)
Now, all you have to do is called the saveAll() function instead of save().
$this->Notification->saveAll($this->data);
And that's the Cake way to do it!
Those values have to be repeat like this
Array([Notification] => Array(
[0] => Array
(
[user_id] => 4
[subject] => subjects
[content] => content
)
[1] => Array
(
[user_id] => 6
[subject] => subject
[content] => contents
)
)
)
$this->Notification->saveAll($data['Notification']); // should work
If you don't pass any column value, this cake will just ignore it
You're going to have to massage your form's output to suit Model::saveAll. In your controller:
function action_name()
{
if ($this->data) {
if ($this->Notification->saveMany($this->data)) {
// success! :-)
} else {
// failure :-(
}
}
}
And in your model:
function saveMany($data)
{
$saveable = array('Notification'=>array());
foreach ($data['Notification']['user_id'] as $user_id) {
$saveable['Notification'][] = Set::merge($data['Notification'], array('user_id' => $user_id));
}
return $this->saveAll($saveable);
}
The benefit here is your controller still knows nothing about your model's schema, which it shouldn't.
In fact, you could probably redefine saveAll in your model, which hands off correctly formatted input arrays to parent::saveAll, but handles special cases itself.
There might be a more cakey way of doing this, but I've used this kind of technique: First, change the form so that you get an array like this:
Array(
[Notification] => Array
(
[subject] => subject
[content] => contentcontentcontentcontentcontentcontent
),
[selected_users] => Array
(
[id] => Array
(
[0] => 4
[1] => 6
)
)
)
(Just change the multiselect input's name to selected_users.id)
Then loop through the user ids and save each record individually:
foreach( $this->data[ 'selected_users' ][ 'id' ] as $userId ) {
$this->data[ 'Notification' ][ 'user_id' ] = $userId;
$this->Notification->create(); // initializes a new instance
$this->Notification->save( $this->data );
}

Resources