More pondering about HABTM (let’s save new tags with a post)

There is a number of examples (some you can find even on this blog) on how to save a Post with a few Tags.
Most of them, so far, had shown how to easily save Tags when the ID’s are already known.

Now, we’ll take a look at how to save a Post and allow a user to enter a bunch of tags (hmmm… let’s say separated by a comma) and save it all together with very little hassle.

Let’s define some goals first:

  • We would like to allow the user to pick from a list of existing tags
  • We would like to allow the user to input a bunch of tags separated by comma
  • We need to ensure that we do not save already existing tags
  • If the user doesn’t select or input any tags, we should just go ahead and save the Post
  • If the user does something silly like input “some, some, some, tag”, we should only save two tags from the list

I think that should be good enough to get started…

First we’ll, build a simple a form add.ctp:


  echo $form->create();

  echo $form->inputs(array('name', 'body'));
  echo $form->input('Tag', array('multiple'=>'checkbox'));
  echo $form->input('Tag.new_tags');

  echo $form->end('Save');

As you see, we’ll have a bunch of checkboxes to allow the user to pick from existing tags, we’ll also allow a free-hand input for “new tags” $form->input('Tag.new_tags');

Now, let’s see our “add” action in the controller:


function add() {

   if (!empty($this->data)) {
      $this->Post->save($this->data);
   }

  $this->set('tags', $this->Post->Tag->find('list'));
}

Nothing out of the ordinary so far. Note that, $this->set('tags', $this->Post->Tag->find('list')); will let CakePHP automagically build a bunch of checkboxes from existing tags in the DB.

Now, let’s add a little something-something to our Post model:

   function beforeSave() {
        
        $tagIds = $this->_getTagIds();
          
        if(!empty($tagIds)) {
            if(!isset($this->data['Tag']['Tag'])) {
                $this->data['Tag']['Tag'] = $tagIds;    
            }
            else {
               foreach($tagIds as $tagId) {
               	    $this->data['Tag']['Tag'][] = $tagId;
               }
            }  
        }
                        
        return true;
    }
    
    function _getTagIds() {
        $tags = explode(',', $this->data['Tag']['new_tags']);
                
        if(Set::filter($tags)) {
            foreach($tags as $tag) {
                
                $tag = strtolower(trim($tag));
                
                $existingTag = $this->Tag->find('first', array('conditions' => array('Tag.name' => $tag)));
                
                if(!$existingTag) {
                    $this->Tag->create();
                    $this->Tag->saveField('name', $tag);
                    
                    $tagIds[] = $this->Tag->id;    
                }
                else {
                    $tagIds[] = $existingTag['Tag']['id'];
                }
            }
                       
            return array_unique($tagIds); 
        }
        
        return false;      
    }

Let’s examine what’s happening here…

In beforeSave() we prepare the array of Tag ID’s to be saved, by relying on CakePHP’s HABTM conventions.
We’ve also created a custom function _getTagIds() to process the input from the user from the “new_tags” field (remember our add.ctp?)

So, if the user did not provide any input we simply go ahead and save the Post (and possibly some selected tags), otherwise we check if the tag already exists and if so, grab the ID. If it does not exist we save it, and then grab the ID.

Once all that is accomplished, we go ahead and properly prepare $this->data['Tag']['Tag'] to save our Post and Tag relationship.

Of course, any questions and suggestions about the code are as always welcomed.

P.S. If you are completely lost about what’s going on here, I suggest you check out this post, and then come back to review the code in more detail.