I have the following Models:
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
}
class City extends AppModel { // for "cities" table
public $belongsTo = 'Country';
}
class Country extends AppModel { // for "countries" table
public $hasMany = 'City';
}
..how do I fetch cities for a country. Something like this I'm trying to find out:
$countries = $this->Country->first(); // fetch a country
$cities = $country->city->find('all'); // get the cities for that country
I've set AppModel in this manner to avoid fetching cities every time I call for a country. Sometimes I don't need all the cities to be retrieved so don't want the default join. But, there are times I do want to fetch cities for a given country. The following is the only way I know how:
$cities = City->find('all', array(
'conditions' => array(
'City.country_id' => $country['Country']['id']
)
))
Is this the most convenient way to access cities once relationships have been established in the model? If so, I don't really understand why bother with $belongsTo and/or $hasMany. Thanks
Associated models first will find the main model, and then will find associated models based on the first query. So it's impossible to limit them based on associated model conditions. So, if you want to limit the main model based on the associated model, you have two options:
Do a Join find
Do a reverse find. It means that you can find City based on conditions, and contain the Country associated to it. For example (assuming that you're on CountriesController):
$this->Country->City->find('all', array(
'conditions' => array(
//your conditions
),
'contain' => array('Country')
));
As you are using CakePHP Model relationship, then it's not require to write Join query.
First thing add the following in Model of Country
class Country extends AppModel { // for "countries" table
public $recursive = 1;
public $hasMany = 'City';
}
Just write the following code,
$countries = $this->Country->find('all');
You can get the cities from $countries in an array format.
Related
i have the Model ExternalShare, which hasOne SfileVersion. And a third Model Sfile, which hasMany SfileVersion.
In SfileVersion i have in $virtualFields the following code:
class SfileVersion extends AppModel {
public $virtualFields = array(
'active' => 'SfileVersion.is_active',
'externalShare' => 'ExternalShare.id'
);
[...]
}
AppModel looks like the following code, so, Containable is available everywhere:
class AppModel extends Model {
public $recursive = -1;
public $actsAs = array('Containable');
}
Now i try to call the find('all') method in SfileController, to get data from Sfile and SfileVersion:
class SfilesController extends AppController {
public function index() {
$this->Sfile->recursive = 0;
$this->Sfile->contain('SfileVersion');
$allSfiles = $this->Sfile->find('all');
}
[...]
}
Now i get the follwing error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'ExternalShare.id' in 'field list'
In the CookBook (http://book.cakephp.org/2.0/en/models/virtual-fields.html#limitations-of-virtualfields), here is
A common workaround for this implementation issue is to copy
virtualFields from one model to another at runtime when you need to
access them
, which i tried, by adding the following lines in front of the find('all'):
$this->Sfile->virtualFields['externalShare'] = $this->Sfile->SfileVersion->virtualFields['externalShare'];
But i get still the same error. I didnt directly understand, what the workaround is doing. Also, my virtual fields may not the problem (active seems to work), just the special one, which points to an other (third) Model association (ExternalShare.id). Does someone have an idea or solution? Thank you very much!
As an example lets imagine we have a simple tv show database. Show and Episode as Model.
An Episode belongsTo one Show and one Show hasMany Episodes.
In the episodes/index view we are just echoing all episodes, the same goes for the shows/index view. But I also want to echo lets say the first 5 episodes of each show (just the title). I could simply limit the episodes by setting the limit attribute for the hasMany association.
In shows/episode/x(id) view I want to echo all episodes. And therefore I can't simply use the limit attribute for the hasMany association since it is view dependent.
What solution should I choose to implement that? I could only archive that by using some "dirty workarounds/hacks" but I feel like this is an usual problem and there might be some actual solution.
I believe what you are looking for is the containable behaviour.
Read the doc:
http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
Then remove any limit to your associations. Below there is a way of how you can use containable behavior in your example.
class Shows extends AppModel {
public $actsAs = array('Containable');
}
class ShowsController extends AppController {
//Bring all the shows and 5 episodes
public function index(){
$this->Show->find('all', array('contain' => array(
'Episode' => array('limit' => 5)
)));
}
public function view($id){
//Bring the show
$this->Show->findById($id);
//Then bring the episodes of the show
$this->Show->Episode->findByShowId($id);
//Or you can use
$this->Show->find('all', array(
'contain' => array('Episode')),
'conditions' => array('id' => $id)
);
}
}
I have a model, called "Cliente" and this model have a association with another table called ClienteRelFot. I declared that ClienteRelFot has a useTable = 'rel_fot_ec', but the cake are looking for "rel_fots".
The rel_fot_ec table exists on my database because I use to find another data.
Someone have a idea to solve this problem?
I tried clear cache and delete all files from tmp folders.
Below, we have the error:
Error: Table rel_fots for model RelFot was not found in datasource default.
Your associations are trying to pull data from the model 'RelFot' (per the error), not 'ClientRelFot', so declaring that 'ClienteRelFot' uses the table 'rel_fot_ec' will have no effect.
Try adding:
public $useTable = 'rel_fots';
in your 'RelFot' model.
I had this problem too, even though using public $useTable = ...
My data model: Event hasMany > Submissions hasMany > Authors
Cake was telling me [MissingTableException] Table authors for model Author ..., the problem was not in the Author model, but in the Submission model:
class Submissions extends AppModel {
public $hasMany = array(
'Author' => array(
'className' => 'authors', // author should be singular
'foreignKey' => 'submission_id'
)
);
I have a 'Complex' model which hasMany Unit. My model associations are correct, everything works, it's just that I am having trouble getting a different model/association working correctly and it has made me wonder if I need to set this model up differently.
See, not ALL Unit will have a Complex associated with it. Some Unit are houses, some Unit are hotel rooms, and some Unit are just rental companies.
Because Complexes haveMany Units I am tempted to leave it alone, but I am wondering if I shouldn't redefine the relationship as Complex belongingTo Unit. I am trying to not repeat myself in my database with adding in multiple instances of the same complex.
Here is my current Unit Model:
class Unit extends AppModel {
public $name='Unit';
public $belongsTo=array(
'User'=>array(
'className'=>'User',
'foreignKey'=>'user'
),
'Complex'=>array(
'className'=>'Complex',
'foreignKey'=>'complex_id'
)
);
public $hasOne=array(
'Location'=>array(
'className'=>'Location',
'foreignKey'=>'location_id'
)
);
}
and here is my current Complex model:
class Complex extends AppModel {
public $name='Complex';
public $hasMany=array('Unit');
public $hasOne=array(
'Location'=>array(
'className'=>'Location',
'foreignKey'=>'location_id'
)
);
}
By the way, 'Location' is the model I am having trouble with (the Location part of my array currently returns empty when I call my unit/complex information in my view, so I need to get my association right).
If the Unit is a condo with a complex, I want it to return the Location of Complex. If it is a house or hotel or rental company, I want it to return the Location of Unit. I have to do several of these associations (images, location, and so forth) so I want to get everything straight right up front.
Is my current relationship between Unit and Complex correct or do I need to define everything differently?
UPDATE Here is my controller logic:
$this->paginate['Unit']=array(
'limit'=>9,
'order' => 'RAND()',
'contain'=>array(
'User'=>array(
'email'),
'Complex'=>array(
'id','complex_name', 'realname', 'photo1','photo2','photo3','photo4','photo5'),
'Location'=>array(
'complex_id','address', 'city','state','zip','area_code','exchange','sln','lat','lon','website')
),
'conditions'=>array(
'Unit.type'=>array('condo', 'rentalco'),
'Unit.active'=>1)
);
$data = $this->paginate('Unit');
$this->set('allcondos', $data);
}
And you can see the result I get here:
I am not currently interested in displaying a view of all Complexes, but the index page logic does have ContainableBehavior limited to just Complex(as originally I was going to have views for various Complexes):
public function index() {
$this->Complex->Behaviors->attach('Containable');
$this->Complex->contain();
$c=$this->Complex->find('all');
$this->set('complexes', $c);
}
UPDATE I have it working now, for anyone who encounters this problem. I had to change Location to the root and make Complex belongTo Location. Here are my models updated:
class Location extends AppModel {
public $name='Location';
public $hasMany=array('Complex', 'Unit');
var $belongsTo=array(
'Restaurant'=>array (
'className'=>'Restaurant',
'foreignKey'=>'restaurant_id'
)
);
}
class Complex extends AppModel {
public $name='Complex';
public $hasMany=array('Unit');
public $hasOne=array('Image');
public $belongsTo=array(
'Location'=>array(
'className'=>'Location',
'foreignKey'=>'location_id'
)
);
}
class Unit extends AppModel {
public $name='Unit';
public $belongsTo=array(
'User'=>array(
'className'=>'User',
'foreignKey'=>'user'
),
'Complex'=>array(
'className'=>'Complex',
'foreignKey'=>'complex_id'
),
'Location'=>array (
'className'=>'Location',
'foreignKey'=>'location_id'
),
);
public $hasOne=array('Image');
}
Many thanks to Wylie for the help, this was a doozy!
http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html?containing-deeper-associations#containing-deeper-associations
When using ‘fields’ and ‘contain’ options - be careful to include all foreign keys that your query directly or indirectly requires. Please also note that because Containable must to be attached to all models used in containment, you may consider attaching it to your AppModel.
You can do that in your AppModel like this: public $actsAs = array('Containable');
You don't have to use Contain when you don't want to so it shouldn't get in the way.
When a model A has model B, you use the foreign key within model B. So your foreign key on the Location should be complex_id. Although, that shouldn't be so because not all units have a complex.
It seems to me you should make the Location the "root". So, Complex belongsTo Location and Unit belongsTo Complex and Location. Then Location hasOne/Many complex, etc. Either that or just put the address columns in the Unit/Complex tables.
//index.ctp, this forms points to action updateData in profilesController
$this->Form->input('User.lastname');
$this->Form->input('Profile.age');
$this->Form->input('Profile.height');
$this->Form->input('Associate.city');
$this->Form->end('Submit');
//user.php
Class User extends AppModel {
var $hasOne = array('Profile', 'Associate'};
var $primaryKey = 'user_id';
}
//profile.php
Class Profile extends AppModel {
var $belongsTo = array('User');
var $hasOne = 'Associate';
var $primaryKey = 'user_id';
}
//associate.php
Class Associate extends AppModel {
var $belongsTo = array('User');
var $primaryKey = 'user_id';
}
//profiles_controller.php
Class ProfilesController extends AppController{
function updateData(){
//output incoming request for debugging purposes
debug($this->request->data);
//here i fetch the db to get the id of user
$options =
array('conditions' => array('User.username' => $this->Auth->user('username')),
'fields' => array('User.id')
);
//find user id so we can find user in related tables
$id = $this->Profile->User->find('first', $options);
//here I modify request data so cakephp finds the users through primaryKeys
$this->request->data['Profile']['user_id'] = $id['User']['id'];
$this->request->data['Associate']['user_id'] = $id['User']['id'];
$this->request->data['User']['id'] = $id['User']['id'];
if($this->request->is('post'){
//this updates data in table no problem
$this->Profile->save($this->request->data);
//this updates data in table no problem either
$this->Profile->Associate->save($this->request->data);
//this returns false...it breaks here
$this->Profile->User->save($this->request->data);
}
}
}
Table structure:
User
|id|int|auto increment
|firstname|varchar
|lastname|varchar
|date|timestamp
Profile
|id|int|autoincrement
|user_id|int
|age|int
|height|int
Associate
|id|int|autoincrement
|user_id|int
|city|varchar
|country|varchar
Ok I know what some of you might tell me, why do I do this on the profilesController and
not on the UsersController. Well, my idea is to separate some actual important user
data from the profile data so it's my intention to write the code for profile on the ProfilesController...as I was developing I was assuming that the same Model association would have automatically updated the User.lastname field in the User table..but that is the part where my code breaks and I have tried but I can't make it work
The current association in my mind at least is as follows:
User has one Profile
User has one Associate
Profile belongs to User
Associate belongs to Profile and User
Can anyone tell me what am I doing wrong? i am following what I think is a logical approach for my application, cakephp updates Profile and Associate models but User remains unaffected.
Assuming the primaryKey of your users table is 'id', just remove all of the $primaryKey lines, and try again.
The only reason to set the primary key is if it doesn't follow the default that CakePHP has in place. I would GUESS (can't see your tables) that the primaryKey field in your 'users' table isn't 'user_id' - more likely it's just 'id', and in the other tables, it's 'user_id'. If that's the case, there's no need to specify the $primaryKey, since that's the default of CakePHP.
As it turns out after reading the cakephp documentation (and obviously being a n00b) the reason why my code was breaking is because I had a callback beforeSave in my model. I didn't know that in order to save data I had to disable the callback which was unrelated to the part of the code I presented to you. The solution in a case like this is to do as follows:
$this->Profile->User->save($this->request->data, array('callbacks' => false));
I don't know you guys but sometimes I feel the cakephp documentation is a little too simplistic, I discover this by looking at the API.