cakephp saving data for a related model's related model (not typo) - cakephp-2.0

I've run into a bit of a problem saving data in cake php.
here are the models/relationships.
District hasMany Departments
Department hasMany Groups
I am in a view for creating new district, in which I've allowed the user to create multiple new departments. while creating each department, the user may create multiple groups for that dept. Now the trouble is I'm unsure of how to save the group data.
for each department that is created on the fly, im using the multiple index method for the inputs (i.e. "Department.0.name", Department.0.type) so this will be a cinch to save using the saveAll method. However, for each group that is created, i will need a department_id, and since none of the District's departments have yet been saved, they don't have an id. how can i save this new district's data, saving the new departments, and their associated new created groups? is there a way that i can address the name attribute of the group inputs that will create the proper association, something like "Department.0.Group.0.name", for instance?
Thanks in advance!!! if anything is unclear, please don't hesitate to say so, I'll be glad to rephrase.

What does your POST data array look like?
<?php
debug($this->data);
?>
If it is not in the correct format, the associated models won't get saved.. Cake knows to grab the "lastInsertId()" of the models which haven't been saved yet, so you don't have to worry about those... What i'm not sure about, and the docs don't really go into, is how deep the save goes. The example provided is as follows:
$this->data =
Array
(
[Article] => Array
(
[title] => My first article
)
[Comment] => Array
(
[0] => Array
(
[comment] => Comment 1
[user_id] => 1
)
[1] => Array
(
[comment] => Comment 2
[user_id] => 2
)
)
)
$this->Article->saveAll($this->data);
This is the correct structure (cakephp 1.3) for saving associated models of a 'hasMany' relationship, but i'm not sure if it goes any deeper than one child.
One thing that comes to my mind is to build the array according to the format above, but leave the parent model out. Then manually save the parent model data, grab the ::getLastInsertId(); then do a saveAll on departments and groups.
[UPDATE]
I just tested your theory and it will work the way you intend.
<?php
echo $this->Form->input('Department.0.Group.0.name');
?>
Will produce:
<input name="data[Department][0][Group][0][name]" type="text" id="Department0Group0name">
[UPDATE 2]
I did some exploring in lib/Cake/Model/Model.php and found this:
<?php
...
public function saveAssociated($data = null, $options = array()) {
...
... // code omitted.
...
if ($options['deep']) { // This will recurse infinitely through all associations
$saved = $this->{$association}->saveAssociated($values, array_merge($options, array('atomic' => false)));
}
...
...
... // code omitted.
...
?>

Related

Joining 3 tables in Cakephp

Using Cakephp 2.5.3 ... I have the following tables:
transactions (belongs to methods and deliveries)
id
delivery_id
method_id
methods (has many transactions)
id
name
deliveries (has many transactions)
id
date
In my delivery view I would like to see the method name for each delivery.
foreach ($deliveries as $delivery) {
echo $method['name'];
}
( a similar unanswered question is
here:)
I am (obv.) very new to Cakephp, What approach should I take to go about this? Thanks!
=========UPDATE==============
I ended up adding methods to the deliveries controller
$this->set('methods', $this->Method->find('all', array('recursive' => -1)));
And looped through the methods in my (read only) view :
//filtered method array for
$method['id'] == $delivery['Transaction']['0']['method_id'])
// got method name
$button_text = $method['name'];
It works fine but can anyone tell me if this may cause problems for me down the line?
use has many assotiations in Transaction model as -
public $hasMny = array('Method', 'Delivery');
then fetch them -
$this->set(
'methods',
$this->Method->find('all', array('contain' => array('Method', 'Delivery')))
);
you will get all the related result together.

How do I get data from associated models in CakePHP?

I create a simple blog with cakephp where i have some posts and some comments.
After i bake an application it create a comments folder with the index.ctp and a posts folder with the index.ctp
What i want to do is display them as fallow:
Post1
comment1
comment2
Post2
comment1
comment2
If i place the comments inside the posts/index.ctp i get an error telling me that the $comments is not defined.
How can i do this?
Thanks
edit:
alright, im sorry for the comeback, but it's still a bit unclear. I do have the $hasMany relationship setup, in fact on the page that displays the posts i have a link that points me to the comments. The only thing that i want them to be displayed in the same page as the posts.
i should be able to say <?php echo $comment['Comment']['content']; ?>
Check your controllers.
If you want to display Comment information in a Posts view, you need to be sure that the Posts controller can load the data.
The "CakePHP" way is to define relationships between models. Check your baked models and see if there is something like this:
class Post extends AppModel{
var $hasMany = array( 'Comment' );
}
When the models are associated with each other, your Posts controller will automatically find Post objects and their associated Comment objects.
For instance, this line:
$this->Post->findById( $id );
Will produce something like this:
Array
(
[Post] => Array
(
[id] => 42
[text] => Post1
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 42
[text] => Comment1
)
[1] => Array
(
[id] => 2
[post_id] => 42
[text] => Comment2
)
)
)
Good documentation at http://book.cakephp.org/
Model Associations
Retreiving Data
EDIT: Adding more info after your comment
As long as the models have the association, CakePHP will pull the data appropriately (unless you set Recursive => false or are using Containable which I assume you aren't).
Check your PostsController controller and see how it's loading the data. I'm guessing it is doing something like the following:
$post = $this->Post->findById( $id );
$this->set( compact( 'post' ) );
or
$this->data = $this->Post->findById( $id );
Check which way it is storing the retrieved data, and then access that variable from the view.
For example, if it is storing the data in a variable named "$post", you would put something like this in your view:
// output the 'text' field of the 'post' object
echo $post[ 'post' ][ 'text' ];
// loop through associated comments
foreach ( $post[ 'comment' ] as $comment ){
//output the 'text' field of a 'comment' object
echo $comment[ 'text' ];
}
By default CakePHP stashes tons of detail in arrays after retrieving data. The trick is to know the hierarchy of the data and fetch it from the array accordingly.

cakePHP hasOne relationship not auto completing dropdown field

I'm trying to implement a hasone relationship between 2 models, but I can't have the 'add' form autocomplete with the possible options in the second model (the one that belongsTo the first one). This is my code:
- model 1: item.php
<?php
class Item extends AppModel{
var $name = 'Item';
var $primaryKey = 'id';
var $hasOne = 'CoverImage';
}
?>
- model 2: cover_image.php
<?php
class CoverImage extends AppModel{
var $name = 'CoverImage';
var $primaryKey = 'id';
var $belongsTo = array(
'Item' => array(
'className' => 'Item',
'foreignKey' => 'item_id'
));
}
?>
- add view of model 2: add.ctp
<?php echo $this->Form->create('CoverImage',array('url' => array('controller' => 'admins', 'action' => 'add')));?>
<fieldset>
<legend><?php __('Info'); ?></legend>
<?php
echo $this->Form->input('item_id');
echo $this->Form->input('description');
?>
</fieldset>
<?php echo $this->Form->end(__('Create', true));?>
For what I see in Cake's documentation, with this relationship, in the add view I should see a dropdown list in the item_id field to be able to select to which item does this CoverImage belongs to, but the dropdown is empty (and yes, I have some items in the items table already).
Maybe I'm missing something or I've done something wrong, but I can't figure it out. Thanks so much in advance for any clues!
EDIT
One think I've just realized is that if I do this:
echo $this->Form->input('item_id', array('type'=>'text'));
instead of this:
echo $this->Form->input('item_id');
I can add/edit the *item_id* field, I can see its value in the text box. However, if I leave the other one, I just see an empty dropbox and when I try to add/edit a CoverImage, it doesn't work, it just shows an empty white page, not even with errors...
Maybe this is a lead to something...
In order for that to work you have to create a list of possible options in the controller. That does not happen automatically.
public function add() {
$items = $this->CoverImage->Item->find('list');
$this->set(compact('items'));
}
The FormHelper only automatically infers that the field item_id should be populated by the options in the variable $items (plural, no _id).
Do be careful that Items that already haveOne CoverImage should not be part of that list. find('list', array('conditions' => array('CoverItem.id' => null))) will probably* take care of that, but you'll need to recheck just before saving as well, or you need to rethink your associations.
* Not sure off the top of my head whether that'll work for 'list' searches.
EXCELLENT QUESTION. You've run afoul of a disingenuous feature of Cake's associations:
Considering you defined the relationship as hasOne? Guessing at the trace but Cake probably even correctly inferred your preference for list functionality. You got your automagic list...
...of One.
$hasOne is pretty exclusive like that. It "uses up" those "has" relationships (it's makes the relationship a de facto Singleton - so Users only have 1 Profile <-> Profile only has 1 User). Consider - Database can have many configurations, but Dbo will only ever have one Connection at a time and Connection will only have one Dbo. Thus -> hasOne is for marrying two Models til die() do they part.
-- So it doesn't get used nearly as much as hasMany and belongsTo.
For your purpose, you probably want to change to a different association.
Adding an additional $this->Item->find doesn't really fix what's wrong (and I wouldn't recommend it, unless you're mostly done with both models/controllers, or you actively want things to start getting weird fast.)
Also, changing how you call the Form Helper methods - if you return a 'list' type fetch from a find, Cake will automatically produce an option list out of it. What's actually happening is, you're sneaking around your Model on a very thin margin of View functionality. That's why specifying the input type to "break the magic" tends to be discouraged (which, you totally can if you want. Just understand what's actually happening, or: see, weird, fast.)
But you might want to rethink how you've associated your models - wouldn't it also be correct to say, each Item belongsTo a CoverImage (same as each CoverImage belongs to an Item) -- because you have a form expressly permitting a CoverImage to select an Item, any Item, to be displayed with? You'll probably get better results.
HTH. :)

AutoMagic select box not populating in CakePHP

I've got the following relationship set-up between two models
Story belongsTo StoryType
StoryType hasMany Story
I've set-up a form to select the StoryType for each story using the following code:
echo $this->Form->input('Story.story_type_id', array('tabindex' => 2));
with this code in the controller to populate the list
$this->set('story_types', $this->Story->StoryType->find('list', array ('order' => 'title')));
But it's not populating the select box with anything. I know that the find() option is working because doing a debug within the controller produces this:
Array
(
[1] => First Person
[3] => Third Person
)
The weird thing is that it's exactly the same code, just querying other models, to populate select lists for things like users and genres, it's just the story types that isn't working.
Any ideas? Cheers.
You don't mention which version of CakePHP you're using, but try setting storyTypes rather than story_types:
$this->set( 'storyTypes', $this->Story->StoryType->find( 'list', array( 'order' => 'title' ) ) );
Older versions of CakePHP (pre-1.3) modified set variable names to headlessCamelCase and, even if you're using 1.3.x, there may be a little bit of that infrastructure lingering. It's a bit of a reach, but it's easy enough to test and it seems plausible that this could be the root of your problem.
I'll be curious to see what you find out.
This is a little hacky, but I think it will work:
echo $this->Form->input('Story.story_type_id', array('tabindex' => 2, 'options' => $story_types));
here's what you should really do .. (esp, for version 2.x) - in case if some people are facing the same problem.
[inside your constroller action]
$oneOfTheColumns = 'title'; //just for sake of making it clear - if you have to order the results
$storyTypes = $this->Story->StoryType('find', array('order'=>$oneOfTheColumns));
$this->set(compact('storyTypes'));
[inside your view]
echo $this->Form->input('StoryType');

CakePHP HABTM: Editing one item casuses HABTM row to get recreated, destroys extra data

I'm having trouble with my HABTM relationship in CakePHP.
I have two models like so: Department HABTM Location. One large company has many buildings, and each building provides a limited number of services. Each building also has its own webpage, so in addition to the HABTM relationship itself, each HABTM row also has a url field where the user can visit to find additional information about the service they're interested and how it operates at the building they're interested in.
I've set up the models like so:
<?php
class Location extends AppModel {
var $name = 'Location';
var $hasAndBelongsToMany = array(
'Department' => array(
'with' => 'DepartmentsLocation',
'unique' => true
)
);
}
?>
<?php
class Department extends AppModel {
var $name = 'Department';
var $hasAndBelongsToMany = array(
'Location' => array(
'with' => 'DepartmentsLocation',
'unique' => true
)
);
}
?>
<?php
class DepartmentsLocation extends AppModel {
var $name = 'DepartmentsLocation';
var $belongsTo = array(
'Department',
'Location'
);
// I'm pretty sure this method is unrelated. It's not being called when this error
// occurs. Its purpose is to prevent having two HABTM rows with the same location
// and department.
function beforeSave() {
// kill any existing rows with same associations
$this->log(__FILE__ . ": killing existing HABTM rows", LOG_DEBUG);
$result = $this->find('all', array("conditions" =>
array("location_id" => $this->data['DepartmentsLocation']['location_id'],
"department_id" => $this->data['DepartmentsLocation']['department_id'])));
foreach($result as $row) {
$this->delete($row['DepartmentsLocation']['id']);
}
return true;
}
}
?>
The controllers are completely uninteresting.
The problem:
If I edit the name of a Location, all of the DepartmentsLocations that were linked to that Location are re-created with empty URLs. Since the models specify that unique is true, this also causes all of the newer rows to overwrite the older rows, which essentially destroys all of the URLs.
I would like to know two things:
Can I stop this? If so, how?
And, on a less technical and more whiney note: Why does this even happen? It seems bizarre to me that editing a field through Cake should cause so much trouble, when I can easily go through phpMyAdmin, edit the Location name there, and get exactly the result I would expect. Why does CakePHP touch the HABTM data when I'm just editing a field on a row? It's not even a foreign key!
From the CookBook the 1st problem is:
By default when saving a
HasAndBelongsToMany relationship, Cake
will delete all rows on the join table
before saving new ones.
I am not quite sure why Cake is trying to save the HABTM data even though you don't have a foreign key in your data, but there is an easy solution for that. Simply destroy the association for the save call:
$this->Location->unbindModel(
array('hasAndBelongsToMany' => array('Department'))
);
I'm thinking of one reason why this might be happening. When you retrieve Location, you also retrieve locations_departments data. And when you do a save($this->data) it looks for models in the array and saves them.
A way to solve this is setting the recursive attribute (of a model) to -1 or 0 (try, I'm not sure, just print out the data to see what comes out). You can set it in the model: var $recursive = -1; or in the controller method (action): $this->ModelName->recursive = -1;
More about recursive: http://book.cakephp.org/view/439/recursive
It's really similar to what harpax suggested, just if you don't need that data, tell it to Cake, so that it won't fetch it.
Trouble is that when saving your Location, you gave the save method an array containing all the DepartmentsLocations too. Thus CakePHP destroys everything and try to recreate it.
This is a common mistake with cake since it will often pull far too many results for you.
Be sure to pass only the data that needs to be saved, or better to fetch only the datas you need.

Resources