0
votes

I'm using a self referencing HABTM model with Participants. You sign up for an event and when you log in to your reservation/profile you see a list of other participants and you can choose to add yourself and others into various groups; share hotel room, share transportation from airport etc.

What I've managed so far:

1) In my profile I see the list of all other participants with checkboxes. Great so far.
2) Adding another participant works fine. Next time I edit, the participant I added is shown as checked.
3) Removing another participant works fine too as long as you still have checked participants before you submit!

Again, with words:

There are 3 participants. I'm logged in as one of them, and I see the 2 other people on the participants list. I choose to check both of them. This works fine (always). Later I choose to remove one of them (by unchecking the checkbox and hitting submit). This also works fine (always). If I want to remove the last checkbox... nothing is updated (always!). What's curious is that I can add and remove any odd combination of participants and it will always work UNLESS I choose to remove every participants in one go (removing a one and only participant is a special case of "remove all checked participants").

As far as I know, HABTMs work by first deleting all relations, then re-saving them. I can see that in my tables when I remove, add, remove, add the same participant over and over again - the id on the HABTM table is always increasing. When I deselect all participants at once, however, the relations are not updated. The ids stay the same, so it's like the save never happened.

This behaviour is so specific and peculiar, I have a feeling I'm missing something obvious here. Anyway, here's the relevant code:

Model

class Participant extends AppModel {
 var $hasAndBelongsToMany = array(
  'buddy' => array(
   'className' => 'Participant',
   'joinTable' => 'participants_participants',
   'foreignKey' => 'participant_id',
   'associationForeignKey' => 'buddy_id',
   'unique' => true,
  )
 );

Controller

function edit($id = null) {
 if (!$id && empty($this->data)) {
  $this->Session->setFlash(__('Invalid Participant', true));
  $this->redirect(array('action'=>'index'));
 }
 if (!empty($this->data)) {
  if ($this->Participant->saveAll($this->data)) {
   $this->Session->setFlash(__('The Participant has been saved', true));
   $this->redirect(array('action'=>'index'));
  } else {
   $this->Session->setFlash(__('The Participant could not be saved. Please, try again.', true));
  }
 }
 if (empty($this->data)) {
  $this->data = $this->Participant->read(null, $id);
 }

 // Fetching all participants except yourself
 $allParticipants = $this->Participant->find('list', array('conditions' => array('participant.id ' => $id)));

 // Fetching every participant that has added you to their list
 $allBuddies = $this->Participant->ParticipantsParticipant->find('list', array(
  'conditions' => array('buddy_id' => $id),
  'fields' => 'ParticipantsParticipant.participant_id',
  'order' => 'ParticipantsParticipant.participant_id ASC'
 ));

 $this->set(compact('allParticipants','allBuddies'));
}

View

    echo $form->create('Participant');
    echo $associations->habtmCheckBoxes($allParticipants, $this->data['buddy'], 'buddy', 'div', '\'border: 1px solid #000;\'', '\'border: 1px solid #000;\'');
    echo $form->end('Submit');

I'm using a slightly modified helper, habtmCheckBoxes, found here: http://cakeforge.org/snippet/detail.php?type=snippet&id=190 It works like this: function habtmCheckBoxes($rows=array(), $selectedArr=array(), $modelName, $wrapTag='p', $checkedDiv, $uncheckedDiv) {}

1

1 Answers

0
votes

This happens because of the way HABTM works within CakePHP - it will only save HABTM data if the HABTM key exists in the array. Hence, when no checkboxes are checked, there is no data passed through and cake doesn't touch your existing habtm records.

A quick fix would be to add a few lines of code to your controller.

if (!empty($this->data)) {
  if (empty($this->data['buddy'])) {
    $this->data['buddy'] = array('buddy' => array(''));
  }
  if ($this->Participant->saveAll($this->data)) {
   // ...
  } else {
   // ...
  }
}

However, if might also be possible for you to use cake's form helper (instead of the other helper you are using) to do this in your view:

echo $form->inputs(array(
    'legend' => 'Nominate your artwork for awards',
    'buddy' => array('label' => false, 'multiple' => 'checkbox', 'options' => $allBuddies)
));