Cakephp Multiple Relations to the Same Model - cakephp

I'm working on a real estate application, where one Home can be listed by either one or two Realtors. As such, I'm trying to follow the example given in section 3.7.6.7 of the Cake Cookbook. Here's what's going on in my Realtors model:
class Realtor extends AppModel {
var $primaryKey = $num;
var $name = 'Realtor';
var $hasMany = array('Homes' =>
array('className' => 'Home',
'foreignKey' => 'realtor_num',
...),
'CoListedHomes' =>
array('className' => 'Home',
'foreignKey' => 'realtor2_num',
...)
);
This completely breaks the application, with the error message "Database table co_listed_homes for model CoListedHomes was not found."
Referring back to the Cookbook 3.7.6.7, second example, it seems clear that they shouldn't have or need separate "messages_received" and "messages_sent" tables in their database, so what is it that I'm doing wrong?
ETA: Weirdly (to me) I think I got the relationships to work by swapping around the order of the arrays: the first array, on foreignKey = realtor2_num, is called 'ListedHomes', the second array, on foreignKey = realtor_num is called 'Home'. I suppose my question then is, why should the order matter, and why should Cake started talking about unfound database tables in some circumstances?

Just in case, I'd have built this as a categories setup. Then created a join table between Category and Home.
Then you can have a hasAndBelongsToMany beween both categories and homes

"Database table co_listed_homes for model CoListedHomes was not found" probably refers to you not having a model defined for CoListedHomes that points to the homes table.
I would, however, code this as a n:m otherwise known as hasAndBelongsToMany, as stated by DavidYell.

Related

CakePHP multiple "has many" links

am running CakePHP 2.4.6 and have two tables/models with multiple foreign key relationships. In simplified terms, I have an StockGroup model which is linked to an Account model by two foreign keys - sale_account_id and purchase_account_id. The documentation tells me to set up a $hasMany structure in the Account model like this :
public $hasMany = array(
"StockGroupSaleAccount" => array(
"className" => "StockGroup",
"foreignKey" => "sale_account_id"
),
"StockGroupPurchaseAccount" => array(
"className" => "StockGroup",
"foreignKey" => "purchase_account_id"
)
);
When I try to open a view, I get the message
"Error: StockGroupSaleAccounts controller not found" (if I use the alias "StockGroup", the same as the class name then there is no problem, but that stops me specifying the multiple links).
False alarm, I'm afraid! Was extracting data belonging to the linked tables, and was using the keys from $hasMany to get the model, and thence the relevant controller name - which was obviously incorrect in this situation - have now corrected it to look at the class name. Thanks for your help and forbearance!

CakePHP - toMany relationship not working with UUIDs

I have the following model class:
class Property extends AppModel
{
var $name = 'Property';
var $hasMany = array(
'Inventory' => array(
'className' => 'Inventory',
'foreignKey' => 'property_id'
)
);
}
My database schema is set so that the id fields are all set to CHAR(36) so that CakePHP generates UUIDs for each entity. When I attempt to perform a find on my Property entity, it doesn't seem to be adding the necessary join to retrieve any related Inventories. Does anyone have any experience with this issue?
Thanks!
UUIDs will have nothing to do with it, I have a very similar model setup and use UUIDs everywhere.
You technically don't need those className and foreignKey declarations in there, as you seem to be following the CakePHP convention :)
I'd remove those lines, check your database for actual inventories with product IDs. If not, post the find()s you're doing.
The answer turns out to be somewhat stupid.
I'd migrated this from a CakePHP 2.0 install to a CakePHP 1.3 install (couldn't update PHP on the server I was using), and all of my model classes still had uppercase first characters. Changing the file from Property.php to property.php fixed it.

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. :)

cakephp: how to access another model, which has no association

I've 3 tables:
project, amenities and project_amenities.
I add project form only, i want to add new amenities, which first added to amenities table and then project_amenities table.
So I defined these relations:
Project hasMany Amenities
Amenity belongsTo Project
I declared in projects_controller:
var $uses = array('Amenity');
This gives error for my add action here
if (!empty($this->data)){
$this->Project->create();
as
Notice (8): Undefined property: ProjectsController::$Project
[APP\controllers\projects_controller.php, line 60]
edit
Also, I cannot do:
$this->Amenity->create();
$insertData = array('name' => $this->data['Project']['Amenity'][$i]['name']);
$this->Amenity->save($insertData);
I wouldn't recommend using $uses = array() as it defies the purpose of having model relationships. Plus it adds a fair bit of overhead too.
You should use your model relationships where ever possible:
$this->Project->create();
$this->Project->Amenity->create();
$this->Model1->Model2->Model3->Model4->create();
If you want to use an unrelated model, you can do:
$this->loadModel('NewModelName');
$this->NewModelName->action();
Check out http://book.cakephp.org/view/1031/Saving-Your-Data and http://book.cakephp.org/view/992/loadModel
I would like to suggest you to use $this->loadModel('ModelName'); in the function where you needed. Then you can use the model as below
$this->ModelName->function();
By using $uses array it make available to entire controller. Which will affect your performance.
you must specify the project model in your $uses variable:
$uses = array('Project','Amenity');
but I'm curious why do you have three tables when you need one to many relationship?
Solution for :save for hasAndBelongsToMany
I created
model: projects_amenity.php which has relation belongsTo project and amenity.
model: projects has relation hasAndBelongsToMany with amenity
controller: projects_amenities_controller
and in code:
$this->loadModel('ProjectsAmenity');
for($i = 0; $i< count($this->data['Project']['Amenity']);$i++)
{
$this->Amenity->create();
$insertData = array('name' => $this->data['Project']['Amenity'][$i]['name']);
$amenity = $this->Amenity->save($insertData);
$amenityid = $this->Amenity->getLastInsertID();
$projectid = $this->Project->getLastInsertID();
$this->ProjectsAmenity->create();
$data['AmenityArr'] = array('project_id' => $projectid,'amenity_id' => $amenityid );
$this->ProjectsAmenity->save($data['AmenityArr']);
}

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