jump to navigation

Working with HABTM associations August 23, 2006

Posted by rossoft in CakePHP.
trackback

I’ve done some functions for working with HABTM associations.
For example:
You’ve the model Post and the model Tag. Post HABTM Tag.
You want to add the Tag with id 12 to the Post with id 3. Then you can do it easy with
$this->Post->addAssoc(3,’Tag’,12);

Now you want to delete the association between the Post 3 and the Tag 15? $this->Post->deleteAssoc(3,’Tag’,15);

You can even add more than one tag at once:
$this->Post->addAssoc(3,’Tag’,array(11,12));

Put these functions in your AppModel:
    /**
     * Adds to a HABTM association some instances
     *
     * @param integer $id The id of the record in this model
     * @param mixed $assoc_name The name of the HABTM association
     * @param mixed $assoc_id The associated id or an array of id’s to be added
     * @return boolean Success
     */
    function addAssoc($id,$assoc_name,$assoc_id)
    {
        $data=$this->_auxAssoc($id,$assoc_name);
        if (!is_array($assoc_id)) $assoc_id=array($assoc_id);
        $data[$assoc_name][$assoc_name]=am($data[$assoc_name][$assoc_name],$assoc_id);
        return $this->save($data);
    }

    /**
     * Deletes from a HABTM association some instances
     *
     * @param integer $id The id of the record in this model
     * @param mixed $assoc_name The name of the HABTM association
     * @param mixed $assoc_id The associated id or an array of id’s to be removed
     * @return boolean Success
     */
    function deleteAssoc($id,$assoc_name,$assoc_id)
    {
        $data=$this->_auxAssoc($id,$assoc_name);
        if (!is_array($assoc_id)) $assoc_id=array($assoc_id);
        $result=array();
        foreach ($data[$assoc_name][$assoc_name] as $id)
        {
            if (!in_array($id, $assoc_id)) $result[]=$id;
        }
        $data[$assoc_name][$assoc_name]=$result;
        return $this->save($data);
    }

    /**
     * Returns the data associated with a HABTM in an array
     * suitable for save without deleting the current relationships
     *
     * @param integer $id The id of the record in this model
     * @param mixed $assoc_name The name of the HABTM association
     * @return array Data array with current HABTM association intact
     */
    function _auxAssoc($id,$assoc_name)
    {
        //disable query cache
        $back_cache=$this->cacheQueries;
        $this->cacheQueries=false;

        $this->recursive=1;
        $this->unbindAll(array(‘hasAndBelongsToMany’=>array($assoc_name)));
        $data=$this->findById($id);
        $assoc_data=array();
        foreach ($data[$assoc_name] as $assoc)
        {
            $assoc_data[]=$assoc[‘id’];
        }
        unset($data[$assoc_name]);
        $data[$assoc_name][$assoc_name]=$assoc_data;

        //restore previous setting of query cache
        $this->cacheQueries=$back_cache;

        return $data;
    }

Comments»

1. Shane Shepherd - August 23, 2006

Thanks for sharing this tool. I can see how this could be very useful in certain scenarios.

I’m glad to see you writing on this topic in particular (HABTM); it seems that many people have had a difficult time getting up to speed on the hasAndBelongsToMany association. I think it is underdocumented.

2. Merijn Bertels - August 25, 2006

This is great.. don’t you think we all need this in cake 1.2.x.x.

3. Andrzej Borkowski - August 30, 2006

$this->unbindAll(array(’hasAndBelongsToMany’=>array($assoc_name)));
$data=$this->findById($id);
$assoc_data=array();
foreach ($data[$assoc_name] as $assoc)
{
$assoc_data[]=$assoc[’id’];

first line and last line have error (>>’>’

4. rossoft - August 31, 2006

$this->unbindAll is a function of AppModel. Copy it from:

unbind All associations except some

5. Tapter - September 28, 2006

I’ve rewritten the unbindModel function to something simpler. As I usually dont have two associations with the same name I hate to provide the relationship type every time. So this function just takes a string or array of model name(s) that will be considered with the next find operation. All others will be ignored.

function considerModel($params = array())
{
if( !is_array($params) )
$params = array($params);

$classname = get_class($this); // for debug output

foreach($this->__associations as $ass)
{
if(!empty($this->{$ass}))
{
// This model has an association ‘$ass’ defined (like ‘hasMany’, …)

$this->__backAssociation[$ass] = $this->{$ass};

foreach($this->{$ass} as $model => $detail)
{
if(!in_array($model,$params))
{
debug(“Ignoring association $classname $ass $model… “);
$this->__backAssociation = array_merge($this->__backAssociation, $this->{$ass});
unset($this->{$ass}[$model]);
}

}

}
}

return true;
}

Example from within a Module: $this->considerModel( array(‘Post’, ‘Comment’) );

6. Axel - October 3, 2006
7. rdoggsv - November 6, 2006

wow rossoft ! this is just what i was looking for, i was about to do the Model::query() to delete the habtm associations, this info is really usefull thank you !

8. tfe - December 13, 2006

Freakin’ awesome, but I had to make the following modification to the last line of addAssoc() to turn off validation. This is probably just specific to my implementation, but perhaps others can benefit:

return $this->save($data, false);

9. chowsapal - December 21, 2006

Two things:

First: when copying and pasting from the site I get the wrong quotes and have to change them in the text editor. No biggie.

Second: This gives problems if you use other associations besides HABTM. I have one model that belongs_to User and habtm User, and had to alter the loop in _auxAssoc to check for numeric keys or I got extra assoc’s added:

foreach ($data[$assoc_name] as $key => $assoc)
{
if (is_numeric($key)) $assoc_data[]=$assoc[‘id’];
}

Thanks for posting this.

10. AlexW - January 16, 2007

I wonder how to fetch all posts which have two (or more) specific tags (like “Select all posts with the tags ‘book’ and ‘car'”)
how to do this with cake?

11. Marco Pegoraro - March 1, 2007

Hi, I’m working whith HABTM associations fine but I have a problem and I don’t know where to write. (May be you’ll tell me a place?)

I have, example, my Post model and Tag model. Post have many Tasg.
I have “posts” table:
> id, title, message
I have “tags” table:
> id, tag
I have “posts_tags” table
> post_id, tag_id

All work fine but I want to filter post’s list to find all post who share a specific Tag.

I didn’t find a way to do that!!!

I tryed:
$this->Post->findAll(“Tag.id = $filterId”);
but doesn’t work.

At the end I get all post then i filter them with “array_filter()” and a callback function.

I think it must be a way to do this job with Cake!!!!

Help me please!

12. Marco Pegoraro - March 1, 2007

EUREKA!

I found a way!

$this->Post->findAll(“Post.id IN ( SELECT tag_id FROM posts_tags WHERE tag_id = $myTagId )”);

But still not be a Cake’s solution….

13. lucaspirola - March 7, 2007

thanks, thanks a lot, this tools, help me understand and find a solution to this problem… thanks again, u save my day 😀

see ya!

14. CakeBits » 27 Useful CakePHP Tutorials - March 28, 2007

[…] Working with HABTM associations […]

15. sonia - July 3, 2007

hi

16. TJ - September 8, 2007

This is awesome, and I’m grateful for it. One suggestion: why not merge both addAssoc() and deleteAssoc() into one function with an additional argument, something like this:

updateAssoc($id, ‘Tag’, array(id’s to save), array(id’s to delete))

I will do this for my project. Perhaps it’s just due to quirks of my implementation, but I find myself calling both functions back-to-back in many cases. This would permit a single save() call to the DB, rather than a call to remove some items followed by a call to add items.

17. TJ - September 10, 2007

I add this function to my code – hope someone finds it useful. I use this for multiple select’s, where I had to both add all selected values and delete all unselected values. I just took the logic from the add/delete functions and combined them, but there’s only 1 query and 1 save() call. This reduces the number of calls to the DB by about 1/2. It also will only save each association once (I was seeing attempts to save duplicate entries in the join table. DB won’t allow it, but why waste the effort?).

/**
* @param integer $id The id of the record in this model
* @param mixed $assoc_name The name of the HABTM association
* @param mixed $save_id The associated id or an array of id’s to be saved
* @param mixed $delete_id The associated id or an array of id’s to be removed
* @return array Data array with current HABTM association intact
*/
function updateAssoc($id,$assoc_name,$save_id=null,$delete_id=null)
{
if ( $save_id || $delete_id ) {
$data=$this->_auxAssoc($id,$assoc_name);
if ($save_id) {
if (!is_array($save_id)) {
$save_id=array($save_id);
}
$data[$assoc_name][$assoc_name]= $data[$assoc_name][$assoc_name] + $save_id;
}
if ($delete_id) {
if (!is_array($delete_id)) {
$delete_id=array($delete_id);
}
} else {
$delete_id = array();
}
$result=array();
foreach ($data[$assoc_name][$assoc_name] as $id)
{
if (!in_array($id, $delete_id)) {
$result[$id]=$id;
}
}
$data[$assoc_name][$assoc_name]=$result;
return $this->save($data, true);
}
}

18. TJ - September 10, 2007

@#$*#&!
s/@return array Data array with current HABTM association intact/@return boolean Success/

Post also clobbered the indention 😦

19. Tutoriales muy útiles de CakePHP - September 17, 2007

[…] Trabajando con las asociaciones HABTM – Visitar […]

20. Tutoriales de CakePHP - September 18, 2007

[…] Trabajando con las asociaciones HABTM – Visitar […]

21. mzee.richo - October 14, 2008

Nice stuff bro . looking through and liking it

22. CakePHP Tutorials | lonerunners.net - October 29, 2008

[…] How to use the official CakePHP test suite […]

23. Recursos para iniciarse con CakePHP | Mareos de un Geek - November 21, 2008

[…] Trabajando con las asociaciones HABTM – Visitar […]

24. Arvind K Thakur - February 9, 2009

As of latest release 1.2.1.8004 do we still need to use this model class for HABTM associations?


Leave a comment