Deleting association from HABTM table - cakephp

I have a Users and a Reports table, connected with HABTM relationship.
I can save a report that creates a correct record in reports_users table, I can view the relationship table by using $this->User->ReportsUser->.., and so on.
Now I want to delete a specific row in reports_users table, but I can't seem to do it.
I have tried the following:
$this->User->ReportsUser->deleteAll(array(
'ReportsUser.report_id' => $this->data['Report']['report_id'],
'ReportsUser.user_id' => $this->data['Report']['user_id']
));
..but it deletes all the rows with the given user_id, with this query:
What am I doing wrong here. Is it a bad data in the deleteAll call?
I don't want to delete any users or reports, only the relationship between them.
I can confirm that the table names are correct, and that the variables exist and are set.

It seems no error with your code. Either your $this->data['ReportUser']['report_id'] is having some problem. The best way is to first try to print your $this->data. And check whether it exists?
For the safety reasons, use the second argument $cascade = false and also unbind all the ReportUser model Associationship using $this->ReportUser->unbindModel() method.
The other reason seems to be deleting data through
$this->User->ReportUser. Try to use $this->ReportUser->deleteAll('your conditions', false); directly.
Please ask if it not worked for you.

Related

CakePHP Insert Statements

I am having trouble inserting data into a MySQL table using CakePHP. I am making a table that once a row is inserted, it should not be able to be overwritten. With the save method of CakePHP, if I try to enter something with the same primary key but different information, it will just update that entry instead of returning an error. I tried to unset the id for the model and also tried to do Model->create() before the call, but it still just overwrites the data.
Additionally, I have been trying to use the Model->query() method instead, but I cannot get it to properly check for errors. I want it to insert, but return an error message if the ID is already taken, so I tried this.
$insertQuery = ("INSERT INTO `students` VALUES ('{$id}', '{$lastname}', '{$firstname}', '')");
$this->Student->query($insertQuery) or die("error" .mysql_error());
However, the query command returns an array and not a truth value, so this will call die every time. I would appreciate any advice someone can give.
Your table should have only the Id as the primary key and it should be IDENTITY.
In CakePHP if you dont specify the Id in the form you create to submit your data it will create another record, even with the same values.
But you should revise your model to be sure that the id is not required or something like that.

Saving a user in CakePHP with ACL group name specified rather than group id

I have set up a Cake 2.3.1 app with ACL following this tutorial so I have a users table and a groups table.
Everything is working fine, but I'm a little confused about how I should go about saving new users since I don't want the user to be able to select their own group. The group will be chosen based on a number of things that the user inputs on the form so it will be determined by conditional statements in the controller.
At the moment I'm doing this:
$this->request->data['User']['group_id'] = '2'; (or 1, 3 or 4 depending on conditional statements in the controller).
Then, I'm doing a save on $this->data. This works but I'd like to be able to specify the group name rather than hardcode the id so that when we move to production and clear the database, it won't matter if the groups are added in a different order.
I see two ways of doing this.
The first I haven't tried, but it would be to change the primary key of the group table to the name of the group and set the primaryKey variable in the Group model and all subsequent reference and associations with this table in all the other models. That would probably bring you more than a headache and I wouldn't recommend it because you'd probably break the ACL component one way or another.
The other way is what I'd do, and it requires a bit of programming on your part (Cake doesn't have an easy way of doing this that I know of).
In the User model, put a beforeSave and in that function, create a dummy variable with the group name, do the lookup of the name and insert the correct id, and delete that variable.
On User.ctp model
public function beforeSave() {
if (isset($this->data[$this->alias]['group_name'])) {
//do the lookup with the group_name
$group = $this->Group->find('first', array('conditions'=>array('name'=>$this->data[$this->alias]['group_name'])));
if (count($group) <= 0)
//... throw an Exception or handle any way you want this case
$real_group_id = $group['Group']['id'];
$this->data[$this->alias]['group_id'] = $real_group_id;
unset($this->data[$this->alias]['group_name']);
}
return true; //remember this is important! otherwise the save will stop
}
When you want to change the user's group or add another user, just do this in the controller
$data_to_save['User']['group_name'] = 'webuser';
and the beforeSave function will take care of the rest.
I realize you asked for a Cake-easy-way of doing this, but I guess there's none (not sure though, feel free to correct me), the beforeSave function could save you a lot of repetitive code on the controllers. The only thing you'd have to be careful when clearing the database or changing group ids is to reassociate them correctly with the users already in the database, but since is a "moving to production" change, those users already predefined should be few or none.

cakePHP HABTM, am I getting it all wrong?

I understood that every new row, causes the deletion of the rows that were there before?
What is the idea behind it? I don't believe that it is ..
So, what am i getting wrong?
Edit A
I have a form that adds a store to the Stores table. the store have a column named owner_id which is associated to the Users table through a belongsTo relationship.
There is also a table named stores_users that supposed to store the mangers for each store, using the HABTM relationship.
For this table there is a form with an email field, that connects the user to the store by saving the record directly to the stores_users table.
So, there is no full HABTM save anywhere, if I understand the term correctly.
So, my questions are:
Should I expect problems using it this way?
Can you advice me about how to it, if my method is not the proper way?
How can I use the stored data, using $this->User->find(...) to get all the stores that the user can manage?
yes, thats the default behavior of HABTM in cakephp
although this is not on "every row", but "every HABTM save".
this is working IF you always provide all HABTM values.
and with baked views according to the specifications for such HABTM this is all working out of the box.
if you change the default behavior (old ones get not deleted) you will need to make sure that there are no duplicates. there are behaviors out there, I think, which try to accomplish that.
but I would recommend for you to build your forms the way that the default behavior of cake can do its job.
Example:
IS: 1,3,6 (in DB for this key)
NEW: 2,3,6 (coming from form)
(cake deletes 1,3,6 and adds 2,3,6)
=> overall result (forgetting about different primary keys): "1" deleted, "2" added
so it might not be the most resource sparing way but its sure the easiest and fastest.

Delete a belongsTo Association

There's surprisingly little information to be found about this and I guess I've never run into it before, but I'm in a situation where I have a model with a belongsTo relationship. The model is the key and when I delete a record, I need to ensure that its associated record is also deleted.
Specifically, I have a Building model that belongsTo an Address. When I delete the building, I need to be sure that the associated address is also deleted.
I can't flag the association as dependent, of course, so is a callback the best way to ensure that the address record gets deleted or is there a better way? This is one of those cases where I know I can do it with a callback, but at a visceral level, it seems like there should be a better way. I'm wondering whether that's the case.
Thanks.
why not using Foreign Keys in the database and select on DELETE CASCADE and let the database do the work...
[Based on the comment] if you the Address is attached to other models that you dont want to delete, you can set those FK to ON DELETE RESTRICT and the building wont be deleted.
And if you need something more complex you can add the beforeDelete() callback in your model, there's an example in the doc
Good Luck
In cakephp 3.x you can use the unlink method like described here:
http://api.cakephp.org/3.3/class-Cake.ORM.Association.HasMany.html#_unlink
This would set the foreign key field in the database to null
In your case it should be something like this everytime when you want to delete records from buildings so integrate it in your delete function. Of course it requires properly associations in the models.
$address = $this->Addresses->get('address_id'):
$building = $this->Buildings->find()->where(['building_id' => 'some_id'])->toArray();
if ($this->Addresses->association('Buildings')->unlink($address, $building)) {
$this->Flash->success(__('Success!'));
return $this->redirect($this->referer());
} else {
$this->Flash->error(__('Error.'));
return $this->redirect($this->referer());
}

Entity Deletion Strategy

Say you have a ServiceCall database table that records down all the service calls made to you. Each of this record contains a many to one relationship to Customer record, where it stores which customer made the Service Call.
Ok, suppose the Customer has stop doing business with you and you do not need the Customer's record in your database. No longer need the Customer's name to appear in the dropdown list when you create a new ServiceCall record.
What do you do?
Do you allow the user to delete the Customer's record from the database?
Do you set a special column IsDeleted to true for that Customer's record, then make sure all dropdown list will not load all records that has IsDeleted set to true? Although this keeps the old records from breaking at innerjoins, it also prevents user from adding a new record with the same name as the old Customer, won't it?
Do you disallow deletion at all? Just allow to 'disable' it?
Any other strategies you used? I am guessing everyone have their way, I just need to see your opinions.
Of course the above is quite simplified, usually a ServiceCall record will link to many other entity tables. All of which will face the same problem when they are required to be deleted.
I prefer to set an IsDeleted flag, one of the benefits is you can still report on historical information (all teh data is still there).
As to the issue of not being able to insert another customer with the same name, this isn't a problem if you use an ID column (eg CustomerId) which is generally auto populated.
I agree with #Tetraneutron's answer.
Additionally, you can create a VIEW that lists only the active customers, to make it more convenient to populate drop-down lists and such.

Resources