Search code examples
phpcakephphas-and-belongs-to-many

CakePHP deletes other HABTM records on saveAll


I have two models related by HABTM: Absences and Users. For context, users apply for absences (and accordingly, user is aliased to applicant and absence is aliased to absenceapplied). I'm trying to create controller functions for 'apply' and 'retract' which would add a HABTM record and remove a HABTM record, respectively. I've gotten the code to the point where it will save one record, but no more. Right now my controller code for 'apply' is hardcoded to add a couple of records, but it only adds the last one requested. Here's my controller code:

function apply($id = null) {
            if (!$id) {
                    $this->Session->setFlash(__('Invalid id for absence', true));
                    $this->redirect(array('action'=>'index'));
            }

            $this->Absence->recursive = 1;
            $user = $this->Session->read('User');
            $absence = $this->Absence->read(null, $id);
            $data = array(
                    array(
                            'Applicant' => array('id' => 1),
                            'Absence' => array('id' => 4)
                    ),
                    array(
                            'Applicant' => array('id' => 2),
                            'Absence' => array('id' => 4)
                    )
            );

            //$data = array_unique($data);
            if ($this->Absence->saveAll($data)) {
                    $this->Session->setFlash('Application was successful');
            } else {
                    $this->Session->setFlash('Application failed');
            }
            $this->set(compact('data'));
    }

I can see both entries in $data being processed in the debug queries, but for some reason one is deleted. Anyone know why? Here are some of the queries that are run by that controller code (take note of 19, 25, and 26):

11  START TRANSACTION       0       0
12  SELECT COUNT(*) AS `count` FROM `absences` AS `Absence` WHERE `Absence`.`id` = 4        1   1   0
13  SELECT COUNT(*) AS `count` FROM `absences` AS `Absence` WHERE `Absence`.`id` = 4        1   1   0
14  SELECT COUNT(*) AS `count` FROM `absences` AS `Absence` WHERE `Absence`.`id` = 4        1   1   0
15  SELECT COUNT(*) AS `count` FROM `absences` AS `Absence` WHERE `Absence`.`id` = 4        1   1   0
16  SELECT COUNT(*) AS `count` FROM `absences` AS `Absence` WHERE `Absence`.`id` = 4        1   1   0
17  UPDATE `absences` SET `id` = 4, `modified` = '2011-07-03 00:28:39' WHERE `absences`.`id` = 4        1       0
18  SELECT `AbsencesUser`.`user_id` FROM `absences_users` AS `AbsencesUser` WHERE `AbsencesUser`.`absence_id` = 4       0   0   0
19  INSERT INTO `absences_users` (`absence_id`,`user_id`) VALUES (4,1)      1       0
20  SELECT COUNT(*) AS `count` FROM `absences` AS `Absence` WHERE `Absence`.`id` = 4        1   1   0
21  SELECT COUNT(*) AS `count` FROM `absences` AS `Absence` WHERE `Absence`.`id` = 4        1   1   0
22  SELECT COUNT(*) AS `count` FROM `absences` AS `Absence` WHERE `Absence`.`id` = 4        1   1   0
23  UPDATE `absences` SET `id` = 4, `modified` = '2011-07-03 00:28:39' WHERE `absences`.`id` = 4        0       0
24  SELECT `AbsencesUser`.`user_id` FROM `absences_users` AS `AbsencesUser` WHERE `AbsencesUser`.`absence_id` = 4       1   1   0
25  DELETE `AbsencesUser` FROM `absences_users` AS `AbsencesUser` WHERE `AbsencesUser`.`absence_id` = 4 AND `AbsencesUser`.`user_id` = (1)          1       1
26  INSERT INTO `absences_users` (`absence_id`,`user_id`) VALUES (4,2)      1       0
27  COMMIT      0       123

Solution

  • I can see both entries in $data being processed in the debug queries, but for some reason one is deleted. Anyone know why?

    Sure, because it is the default behavior of Cake. Read this section about saving data with HABTM.

    The section briefly talks about creating a separate model for your join table but I don't think it does a fair enough job explaining exactly what that means or how you do it. I'm not suggesting that this should be your approach, you should research the information and make a decision based on your app! The data schematic is easily one of the most important aspects.

    I'm not familiar with Ruby on Rails, but essentially we're wanting to duplicate RoR's has_many :through model association. In a nutshell, we want to be able to work with the HABTM join table just like we would any other model.

    So, we would set our new model associations up like this

    Your Absence model...

    class Absence extends AppModel {
    
        public $belongsTo = array('AbsentUser');
        ...
    

    Your User model...

    class User extends AppModel {
        public $belongsTo = array('AbsentUser');
        ...
    

    Your new AbsentUser model...

    class AbsentUser extends AppModel {
        public $hasMany = array('Absence', 'User');
        ...
    

    Now you can use AbsentUser as its own model, allowing you to bypass Cake's default HABTM behavior. In addition it allows you to easily extend the AbsentUser data to incorporate additional fields if you so choose. For example, you can store specific information about a particular absence.

    I also highly recommend you make sure you have fat models and skinny controllers.. You'll wind up loving yourself later if you do.