views:

61

answers:

2

I'm using Cake 1.2.6 and last night I noticed that a HABTM relationship wasn't being saved when I submit a form.

I have a HABTM relationship between Committee and Volunteer. The primary key for a Volunteer is a UUID while the primary key for a Committee is a human readable string (e.g. BOARDOFDIRECTORS, FAIRCOMMITTEE, FAIRASSOCIATES, etc.). I have a form to create/edit volunteers and that form includes a select box whose options are exactly what you'd expect and are populated with options returned from Cake's find( 'list' ) method. Although I can't think of a reason it would matter, only one committee can be selected for a volunteer (the HABTM is for expected future needs).

Initial results show that selecting the BOARDOFDIRECTORS option works as expected, but the others do not. Tracing the execution through the core code leads me to Model->__saveMulti() where, in Line 1393, this code is executed:

 $data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $id;

If I dump $data before that code, the output is FAIRASSOCIATES. Immediately after, its value is 4AIRASSOCIATES. It seems safe to assume that's why the relationship isn't being saved, but I haven't figured out why the data is changing at this point in the execution.

Has anyone else seen this? Am I missing some critical piece? To the best of my knowledge, this was working fine in v1.2.1 (I upgraded a week ago or so).

UPDATE

The first bit of apparent weirdness I see is that, although my $row is a string, the condition in Line 1366 evaluates to true so I drop into that code block. If my data is a string, how can it have a member value?

UPDATE

I clearly have some thinking to do, but here's the bottom line. If I drop log writes immediately before and immediately after Line 1394 like so:

$this->log( 'Setting ' . $data . '[' . $this->hasAndBelongsToMany[$assoc]['foreignKey'] . '] = ' . $id, LOG_DEBUG );

$data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $id;

$this->log( 'Creating ' . json_encode( $data ) . ' on ' . $join, LOG_DEBUG );

The relevant output is:

2010-03-05 18:57:08 Debug: Setting FAIRASSOCIATES[volunteer_id] = 4b78717f-8ad4-4671-b81c-4e8745591fb4
2010-03-05 18:57:08 Debug: Creating "4AIRASSOCIATES" on CommitteesVolunteer

Possible issues:

  1. I'm not sure how/why Cake is trying to set the volunteer_id member on a string
  2. "FAIRASSOCIATES" is the ID of a committee to which a volunteer is grouped, not a model of any sort, so I don't understand the relevance of FAIRASSOCIATES[volunteer_id] at all.
  3. I have no idea how or why the value of $data is being morphed into 4AIRASSOCIATES by that one line of code.
A: 

It looks like my problem is caused by doing something non-conventional. For lookup tables whose values rarely change (and there are no plans for them to do so via the application), I like the PK values to be human readable. That allows me to look at the data directly in the database and read a related record in a meaningful way. In this case, I can look at a Volunteer record and, without requiring any kind of join, see that s/he is in the Committee named FAIRCOMMITTEE . Kind of handy when looking at the data outside of the application.

In this case, one of my 3 Committee values just happened to be 16 characters long--a length CakePHP interpreted as a UUID--so it behaved correctly. The others aren't 16 characters and didn't behave correctly.

The condition that fails is on Line 1355. I've entered a ticket to see whether there's any room for improvement in the UUID detection intelligence.

Rob Wilkerson
+1  A: 

I wrote a small fix and it is in TESTING, (atm it works): it overrides uuid detection (stupid IF just asking if the data has 16 length or 32 length) and asumes it is a valid id if it is a string or a number for the data entered.

just add the function (copy the whole function):

function __saveMulti($joined, $id)

From lib's model.php to your app's app_model.php

Then replace the IF previously in model.php line 1355 :

if ((is_string($row) && (strlen($row) == 36 || strlen($row) == 16)) || is_numeric($row)) {

NOW your app_model.php for this one ( dont edit your lib's model.php, do this in your app_model.php ):

if( (is_string($row) && ((strlen($row) == 36 || strlen($row) == 16) || !$this->{$assoc}->autoPrimaryKey )) || ( is_numeric($row) ) ) {

then in your app_model.php add this variable:

    var $autoPrimaryKey = true;

and in models that you want this behavior add the same variable but in false, it will asume that the model doesnt have auto generated UUID for HABTM and fix the issue

    var $autoPrimaryKey = false;

I repeat this is just a fix in testing, it solved the problem for me, any question you have, send it to my mail zydriel AT gmail.com

Jose
Nice solution, Jose. My intention has been to override this in a similar manner, but I hadn't decided on an approach. This looks good pending some extensive testing.
Rob Wilkerson