Delete belongsTo association not working - cakephp

I have a problem with associations. I have two tables : Companies and Users.
User hasOne Company and Company belongsTo User (OneToOne)
In my models, I wrote :
/* User.php Model */
public $hasOne = array(
'Company' => array(
'className' => 'Company',
'dependent' => true
)
);
/* Company.php Model */
public $belongsTo = array(
'User' => array(
'className' => 'User',
'dependent' => true
)
);
My problem : when I do
$this->Company->delete($id, true)
in my CompaniesController, the Company with the id $id is deleted but the User associated is not.
Could you help me?

There is no 'dependent' option in belongsTo, so the only one actually working is the other way around.
If you delete a User, it will delete it's Company.
Basically, deleting a parent can delete it's dependent children. But deleting a child can't delete it's "dependent" parent (since there's really no such thing in Cake's case of a "dependent parent").
More details here: http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html
You could choose to run the association in both directions (requiring a field in each table to determine which it belongs to). That way, regardless of which you delete, it should always also delete the other.
Or, you could just delete the User that owns the company.

Related

CakePHP conditions clause on associations when using Model::find()

I just confused because of a find() result. This is my configurations:
First, users can have different User.role values: student, admin, and some others.
// Book
public $belongsTo = array(
'Student' => array(
'className' => 'User',
'foreignKey' => 'student_id'
'conditions' => array('User.role' => 'student')
);
);
When I chain Models like $this->Book->Student->find('list'); I was expecting to get only users whose role are 'student', but instead, it gets all users. What is going on here, what is conditions for on association definition, where can it and cannot be used. Any lead would help, thanks.
PS: I am aware that I could put conditions on find(), that's not the issue
There is a difference between associated data and accessing an associated model object. If you access $this->Book->Student you're accessing the Student model and work in it's scope. The conditions in the defined associations work only in the context of the accessed object.
So if you do a find on the Book and list the students for that book:
$this->Book->find('first', array('contain' => array('Student'));
Your code will work correctly. It will find the book plus the first user who has the role stundent. BUT your association is wrong then: It should be hasMany. because why would you filter a book by role if the book just belongsTo one student?
If you want to filter users by their role you can implement a query param that is checked in beforeFind(), pseudocode: if isset roleFilter then add contention to filter by that role from roleFilter.
Or, if you don't need to paginate just create a getStudents() method in the user model that will return a find('list') that has the conditions.
Or Student extends User and put the filter in the beforeFind() and use that model instead of the User model in your Book association.
If you want to filter on model level or per model I think the last one is a good option. Don't forget to set $useTable and $name or the inherited model will cause problems.
you have miss , inside your model.
try this:
public $belongsTo = array(
'Student' => array(
'className' => 'User',
'foreignKey' => 'student_id', //<------ miss
'conditions' => array('User.role' => 'student')
);
);
Yoi can debug your query to check what is the real query that you make.
Personally I have never use this approach, I prefer to use foreign key with another table for examples Rolesand User.role_id.
Is better for me to use this approach to have more flexibility inside your app.
After I prefer to use a conditions where inside controller to check well the query, because in your way every query you search always for student role not for the other and can be a problem for the rest of role, because inside controller you see a find without conditions but it doesn't take right value because in your model there is a particular conditions.
For me the good way is to create a new table, use foreign key and where conditions inside action of the controller to view well what are you doing.
For default all relations are "left join", you must set the parameter "type" with "inner" value
// Book
public $belongsTo = array(
'Student' => array(
'className' => 'User',
'foreignKey' => 'student_id'
'conditions' => array('Student.role' => 'student'), // <-- Fix That (field name)
'type' => 'inner', // <-- add that
);
);

CakePHP: bi-directional self-referential hasMany Through associations

I'm trying to get my head around bi-directional self-referential hasMany through relationships in CakePHP (what a mouthful!).
I'm working on a picture matching website.
Pictures are associated to other pictures via a 'match' (the join model).
Each match has two pictures and stores the current rating and the total number of votes.
When viewing a picture, all of its related images from either direction should be available (via its matches).
I've started by defining a hasMany through relationship with a join model.
The pictures_matches join table has this structure:
id | picture_id | partner_id | rating | total_votes
My match join model association looks like this:
class PictureMatch extends AppModel {
...
public $belongsTo = array(
'Picture' => array(
'className' => 'Picture',
'foreignKey' => 'picture_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Partner' => array(
'className' => 'Picture',
'foreignKey' => 'partner_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
Each picture needs to be able to access its related pictures from either direction, but this is where my grasp is slipping.
It looks like I need to save both sides of the relationship but this destroys the extra data stored in the join model - with two db entries, voting could vary depending on the direction.
Can anyone shed any light on the best way to do this in CakePHP? I'm rather confused.
Is it possible to create the inverse relationships on the fly?
You can create realtions on the fly vie Model::bindModel(), very usefull stuff this would alow you to bind reverse relations or rather any direction you would like on the fly.
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html
Also using Containable behaviour you can create infinite chain of retriving your associated date ex.
contain('Picture.PictureMatch.Partner.PictureMatch.Picture.....')
Basically you can loop through all of your models as long as each chain is somehow related to the next one to explain it better simple example ( please disregard logic in it )
Circle belongsTo Square
Square belongsTo Triangle
So Triangle is not related to Circle ( directly ), but Square is kinda in between
Circle->find('all', array('...', contain => array('Square.Triangle'));
or to have more fun lets get circle by circle with loop around
Circle->find('all', array('...', contain => array('Square.Trinagle.Square.Circle'));
and so on, of course those example are useless and without any programming logic, but I hope you understand the point that you can loop trough infinite number of relations going back and forth.
I am not sure if this is the best solution but if you did this:
public $belongsTo = array(
'Picture1' => array(
'className' => 'Picture',
'foreignKey' => 'picture_id',
),
'Picture2' => array(
'className' => 'Picture',
'foreignKey' => 'picture_id',
),
'Partner' => array(
'className' => 'Partner',
'foreignKey' => 'partner_id',
),
);
then when you do a search you just search for ($this->data['Picture1'] == $var || $this->data['Picture2'] == $var) and so long as you have recursive set to 1 or 2 you should get back all the related data for that Picture.
I assume this is abandoned, but it's easily resolvable -- it has to do with the phase
destroys the extra data stored in the join model
That means your saves are running a deleteAll and inserting the record on matching... instead you need to find and update that record...
This can be done in a few ways, but the easiest is before your save call, look it up, and include the primary key in the match record data. Basically don't save it as a HABTM, and only save it as a hasMany if you've already tried to find an existing match record primary key (id) and updated the data to save with it.

CakePHP: the very basics of Containable

I read much about how great containable is. Honestly I have read all docs, I have it working in my Users controller, but some things are not clear:
Do I have to use it in All actions or only in Index()?
Do I have to define it in every controller index() function or is it enough to it once in the Users controller
What about if e.g. Country_ID is a FK connected to both user and a related model? For example:
function index() {
$this->paginate = array(
'limit'=>10,
'order'=>'User.created DESC',
'fields'=>array('User.id','User.name', 'User.country_id', 'User.email'),
'contain'=>array(
'Post',
'Company' => array(
'Country' => array(
'fields' => array('id', 'country')
)
),
'Position' => array(
'Profession'
),
'Preference',
'Country',
'Type'
),
);
$this->set('users',$this->Paginate('User'));
}
Country is both connected to User and Company. How to define this without creating duplicates?
Many thanks!
You seem to have the wrong idea about containable. It "allows you to filter and limit model find operations". You use it whenever you need include (or exclude) specific related model data in your find().
For example, a User hasOne Profile, hasMany Roles, which belongsTo a Company. And you need to get all the roles and related companies for a user, but you don't need the profile, you can use $this->User->find('first',array('conditions'=>...,'contain'=>array('Role'=>array('Company'))))
It has nothing to do with index() or users_controller.
Country is both connected to User and Company. How to define this without creating duplicates? What duplicates?

CakePHP hasOne association conditions

Hi I am building my User model to have only one profile picture, which is an example of an Image model. I want the conditions of the hasOne to only include the image where the Image.id is equal to the "picture" column in the user table. I know there is something wrong...here is the relevant part of code.
var $hasOne = array(
'ProfileImage' => array(
'className' => 'Image',
'conditions' => array('ProfileImage.id' => 'User.picture'),
'dependent' => false,
'foreignKey' => 'user_id',
)
);
What is wrong with my conditions? How do I specifc ProfileImage.id to be equal to the picture column in the User model? Thanks!
If the foreign key (picture) is in the User model, then your association should be User belongsTo Image. If the image can only be used by one user at a time, the reverse would be Image hasOne user. If an image can be used by multiple users, it would be Image hasMany User.
But, if the image can only be used by one user at a time, the better data structure would be to add a user_id column to the images table. Then you have a User hasOne Image/Image belongsTo User relationship.
For neither you need conditions, only the foreignKey.
After your clarification of the issue, your condition needs to look like:
'conditions' => array('ProfileImage.id = User.picture')
Otherwise Cake thinks ProfileImage.id should equal the string "User.picture".

CakePHP - delete cascade not working

In CakePHP I have a model Type and SpecificType.
SpecificType belongTo a Type. (type_id field)
When I delete an entry of SpecificType, how can I also delete Type?
I have it as
$this->SpecificType->del($id, true)
However, the entry under Type isn't getting deleted.
Thanks,
Tee
I don't think you can delete Type with SpecificType cascade. you can only use cascade if there's hasMany or HABTM relation.
it is said in the manual.
Deletes the record identified by $id. By default, also deletes records
dependent on the record specified to
be deleted.
For example, when deleting a User
record that is tied to many Recipe
records (User 'hasMany' or
'hasAndBelongsToMany' Recipes):
* if $cascade is set to true, the related Recipe records are also
deleted if the models dependent-value
is set to true.
* if $cascade is set to false, the Recipe records will remain after the
User has been deleted.
you can always run
$this->del($id, true);
to remove your Type with related SpecificType-s.
You want to delete the Type, not the SpecificType. You will also need to make sure you have your model set correctly for Type:
var $hasMany = array(
'SpecificType' => array(
'className' => 'SpecificType',
'foreignKey' => 'type_id',
'dependent'=> true,
)
);
Then delete the type and it will work.
If you are deleting the child (SpecificType) and you want to delete it's parent, you must call the delete on the parent model. But keep in mind, if you have the Cascade set up correctly (dependent = true on the model) all of the SpecificType children will be deleted anyway.
Note: If you want to delete the parent of the child, you may want to reconsider your relationships and confirm they are correct. If that is really how you want them, then don't do the delete on the child. Simply make sure your cascade relationships are set correctly, pull the child's parent information, and delete the parent. Then all of the children will be removed as well.
I had the same problem in a situation where I did not want to follow the Cake model key convention.
I set the SpecificType model's belongsTo relationship to Type like so:
public $belongsTo = array(
'Type' => array(
'className' => 'Type',
'foreignKey' => 'type_id',
'conditions' => '',
'fields' => '',
'order' => '',
),
);
Then to get cascading delete to work, I added the following to the SpecificType model:
public function beforeDelete($cascade) {
// Make sure the parent method runs.
$continue = parent::beforeDelete($cascade);
if ($continue) {
// Delete the Type if there is one.
$typeId = $this->field('type_id');
if (!empty($typeId)) {
$continue = $this->Type->delete($typeId);
}
}
return $continue;
}
I hope that helps someone who has the same problem you had as I'm sure you're not still waiting for an answer.

Resources