views:

388

answers:

3

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!

+1  A: 

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'))
);
harpax
+1  A: 

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.

PawelMysior
A: 

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.

Guillaume Esquevin