Saving new associated records in CakePHP - cakephp

So I have a Message model. When a message is created, I want to create new Recipient records. (Message hasMany Recipient, Recipient belongsTo Message).
The intent is that the message_id field in these associated records gets populated automagically with the new Message id.
I've tried to format the data every which way, and am aware of saveAll and saveAssociated methods, but nothing seems to work... Here's what I assumed would work:
Data passed on to save:
[Message] => array
(
[subject] => Foo bar
[body] => Blah blah blah.
)
[Recipient] => array
(
[0] => array
(
[user_id] => 1
)
[1] => array
(
[user_id] => 5
)
[2] => array
(
[user_id] => 6
)
)
Using saveMany, saveAll, saveAssociated didn't seem to make a difference (even with 'deep' set to true).

A few things:
1) The Message being created was a new record. I realized after that I didn't have $this->Message->create() in my controller. Now, I've since tried without and it works fine but I believe it's best practice to include it.
2) Since I need to save multiple associated records, I have to use saveAll or saveMany but doing so was causing extra blank rows to be added, since the main record was not numerically indexed (which is what saveAll wants).
So here's some code from my controller:
if ($this->request->is('post')) {
$this->Message->create();
if ($this->Message->saveAll($data, array('deep' => true))) {
// code here
}
}
And here is how my data is structured:
Array
(
[0] => Array
(
[Message] => Array
(
[subject] => Foo
[body] => bar
[Recipient] => Array
(
[0] => Array
(
[user_id] => 1
)
[1] => Array
(
[user_id] => 6
)
[2] => Array
(
[user_id] => 38
)
)
)
)
)
This will create a new Message and properly associate the newly created Recipient records.

Related

CakePHP same array from the direct and associated model

I have simple model relationship in CakePHP 1.3 with Categories -> Products
Category hasMany Products
There is slight difference between the data arrays which I get in the different controllers. The Product data is in the main product array when getting as associated model in the Categories controller and is separated when getting it in Products.
For Example to get 'Product1'
in Categories - $category['Product'][0]['title']
and in Products - $product[0]['Product']['title']
I would like to use same element for displaying the products. It does not matter which array scheme will be used just to be the same. And where is the right place to make the modification? I can modify those arrays after getting them, but don't think that it is the best option.
When I am in the Categories controller and get a category I get this:
// $this->Category->findById('12');
Array
(
[ProductCategory] => Array
(
[id] => 12
[title] => Category 1
[updated] => 2013-02-24 10:06:15
[created] => 2013-02-24 10:06:15
)
[Product] => Array
(
[0] => Array
(
[id] => 4
[parent_id] => 12
[title] => Product1
[updated] => 2013-02-24 10:17:01
[created] => 2013-02-24 09:12:59
)
[1] => Array
(
[id] => 6
[parent_id] => 12
[title] => Product2
[updated] => 2013-02-24 10:16:54
[created] => 2013-02-24 09:13:53
)
)
And when getting all the products inside the Products controller:
// $this->Product->find('all');
Array
(
[0] => Array
(
[Product] => Array
(
[id] => 10
[parent_id] => 12
[title] => Product1
[updated] => 2013-02-24 10:16:42
[created] => 2013-02-24 09:16:35
)
)
[1] => Array
(
[Product] => Array
(
[id] => 8
[parent_id] => 12
[title] => Product2
[updated] => 2013-02-24 10:16:47
[created] => 2013-02-24 09:15:39
)
)
)
)
One of your finds is a find('all') and the other is a findById() (which uses find('first')).
Both of these return data in a different format, since find('first') knows you only want one item, and find('all') is an unknown set of item(s).
Just use find('all') for both, but set your limit based on whether you need only one or more than one. Then, your data will be returned exactly the same.
Which Controller you retrieve your data from has no effect on the data returned. Which MODEL however, does - so just make sure you're doing your find from the same model.
Eg.
//in your ProductsController
$this->Product->find('all');
//in your CategoriesController
$this->Category->Product->find('all');
// in some other controller
$this->loadModel('Product);
$this->Product->find('all');
PS - BUT it's better if you don't do your "finds" in your Controller - make a method in your Model, and call it from your Controller(s) so instead of $this->Product->find(), it would be $this->Product->getProducts() (or whatever you want to call it). (read more about "fat models, skinny controllers" for reasons/examples...etc).
Dave is right, the difference is the method you're using... Even though you claim that associated data is always merged, your find on the 'Product' model is NOT associated data, so the format WILL always be different.
I've been here for a short while and I've already noticed that Dave knows his stuff. :)
I agree with the fat models/skinny controllers paradigm for clean, efficient code.
If you changed:
<?php
$this->Category->contain(array(
'Product'
));
$this->Category->find('all',
array(
'conditions' => array(
'Category.id' => $id // 12 for your OP.
),
'limit' => 1
)
);
?>
Should give you:
<?php
array
(
[0] => array
(
'Category' => array
(
[id] => 12
[title] => Category 1
[updated] => 2013-02-24 10:06:15
[created] => 2013-02-24 10:06:15
),
'Product' => array
(
[0] => array
(
...
),
[1] => array
(
...
)
)
)
)
?>
Please correct me if I am mistaken, thanks!
Or if you want "Products" to look like:
<?php
'Product' => array
(
[0] => array
(
'Product' => array
(
...
)
)
)
?>
when fetching data from the category model, you would need to fetch the associated data manually, e.g.:
<?php
$this->Category->contain();
$cats = $this->Category->find('all');
foreach ($cats as &$cat) {
$this->Category->Product->contain(); // You have to contain for each find.
$cat['Product'] = $this->Category->Product->find('all',
array(
'conditions' => array(
'Product.category_id' => $cat['Category']['id']
)
)
);
}
?>

Cakephp 1.3 Save Multiple Model rows, some with an id and some without an id

Is it possible to update existing data and save new data at the same time without having to loop through the array? I would like the array with the id to update and the array without an id to create a new entry. See the example below. Thank you for your help.
[Workload] => Array
(
[0] => Array
(
[phase_count] => 1
[id] => 17
[value] => {"phases":[{"rep_range":"20-30","rep_set_count":"1"}]}
[user_id] => 1
)
[4] => Array
(
[phase_count] => 1
[value] => {"phases":[{"rep_range":"20-30","rep_set_count":"1"}]}
[user_id] => 1
)
);
and then this
$this->Workload->saveAll($this->data['Workload']);
EDIT ======================================
Here is the code that actually saves this array
if($this->data){
array_walk($this->data['Workload'], function (&$value,$index){
// This will need to be changed once users are setup
if(empty($value['user_id'])){
$value['user_id'] = 1;
}
$value['value'] = json_encode($value['value']);
});
debug($this->data);
$this->Workload->saveAll($this->data['Workload']);
In fact, I do exactly that in one of my apps. If there is no 'id' a table entry will be created. If the 'id' is specified, the record will be updated.

Trying to edit multiple records at once, saveAll not working

I'm saving multiple Widgets and associated WidgetsItems to a menu, which I have working fine.
The problem is, my edit function is not working. I understand that the data array structure needs to be as following, as it is what is saving my data properly in the first place:
Array (
[Widget] => Array
(
[23] => Array
(
[title] => Cocktails
[id] => 23
[WidgetsItem] => Array
(
[147] => Array
(
[item] => Martini: Noilly Pratt, Ginor Vodka
[price] => 24
[id] => 147
)
[148] => Array
(
[item] => Negroni: Campari, Gin, Sweet Vermouth
[price] => 16
[id] => 148
)
)
)
using the following controller code:
foreach($this->data['Widget'] as $widgetKey => $widget) :
$widgetData = array(
'title' => $widget['title'],
'id' => $widget['id']
);
$saveableWidget = Set::insert($widget, 'Widget', $widgetData);
if($this->Widget->saveAll($saveableWidget)) : $saveSuccess = true; endif;
endforeach;
Which is copied and pasted from the initial save function, then modified a little for editing. Instead, it's creating new entries, not editing them. I know it's something to do with the IDs, but it's just not saving. What am I doing wrong?
Please help, I feel like I'm very close to a nearly finished product.
Thanks,
~harley

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 );
}

hasMany reduced to hasOne in CakePHP

Basically I have following models in CakePHP:
User(id, username)
Photo(id, user_id, path)
I have set up following relation: User hasMany Photo.
On one screen, I would like to list users, and show random photo next to each user. I tried setting up following relation:
User hasOne SamplePhoto (where SamplePhoto is just Photo model)
but when user has two photos for instance, he is listed twice on the list.
basically my question is: can you reduce hasMany relation to hasOne, without adding any fields to table schema presented above? I would like to tell cake - find the first record in the Photo table which matches a certain user_id.
You could also use the Containable behaviour and then set up something like:
$this->User->find(
'all',
array(
'contains' => array(
'Photo' => array(
'order' => 'rand()',
'limit' => 1
)
)
)
);
You should then get something like
Array
(
[User] => Array
(
[id] => 121
[username] => tom
)
[Photo] => Array
(
[0] => Array
(
[id] => 123
[user_id] => 121
[path] => Somewhere
)
)
)
if you do a find like $this->User->read(null,$id), the return will be an array that looks something like:
Array
(
[User] => Array
(
[id] => 121
[username] => tom
)
[Photo] => Array
(
[0] => Array
(
[id] => 123
[user_id] => 121
[path] => Somewhere
)
[1] => Array
(
[id] => 124
[user_id] => 121
[path] => SomeOtherPlace
)
)
)
From this array you can pick the photo however you like, be it the first:
$this->data['Photo'][0]
the last:
$this->data['Photo'][count($this->data['Photo'])]
an explicit record:
$this->data['Photo'][3]
or by some random means:
$this->data['Photo'][$rnd]
Don't make this more complicated than it needs to be. :)
$data = $this->User->Photo->find('first',
array('conditions' => array('Photo.user_id' => $id)));
Gives you a photo in $data['Photo'] with the user attached in $data['User'].
You'd do something like:
$user = $this->User->find('first', array('conditions' =>
array('username'=>$this->data['User']['username'],
'active'=>true) ));

Resources