Sunday, January 27, 2013

Re: Validating multiple fields problem

Ok, heres some of the code and its setup. The Characters model is just a join table for the Event and the CharactersEvent models. I used HasMany instead of using a HABTM relationship because the CharactersEvent  table also has some metadata (role_id).

So here are my models.



//The Event Model

App::uses('AppModel', 'Model');
class Event extends AppModel {
var $chronicleId;
public $validate = array(
'id' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'event_type_id' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'chronicle_id' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'location_id' => array(
'numeric' => array(
'rule' => array('numeric'),
'message' => 'You must supply a location for this event',
),
),
'description' => array(
'notempty' => array(
'rule' => array('notempty'),
'message' => 'You must have a description of your event',
'allowEmpty' => true,
),
),
'confirm_id' => array(
'numeric' => array(
'rule' => array('numeric'),
'required' => false,
),
),
'start_date' => array(
'date' => array(
'rule' => array('numeric'),
'message' => 'Your must enter a valid date (YYYY-MM-DD)',
),
),
'end_date' => array(
'date' => array(
'rule' => array('numeric'),
'message' => 'Your must enter a valid date (YYYY-MM-DD)',
'required' => false,
),
),
'created' => array(
'datetime' => array(
'rule' => array('datetime'),
),
),
'modified' => array(
'datetime' => array(
'rule' => array('datetime'),
),
),
);

public $belongsTo = array(
'EventsType' => array(
'className' => 'EventsType',
'foreignKey' => 'event_type_id',
),
'Chronicle' => array(
'className' => 'Chronicle',
'foreignKey' => 'chronicle_id',
),
'Location' => array(
'className' => 'Location',
'foreignKey' => 'location_id',
),
);
public $hasMany = array(
'Characters' => array(
'className' => 'CharactersEvent',
'foreignKey' => 'event_id',
'dependent' => true,
),
);





//The CharactersEvent Model

App::uses('AppModel', 'Model');
class CharactersEvent extends AppModel {
public $validate = array(
'id' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'event_id' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'character_id' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'role_id' => array(
'numeric' => array(
'rule' => array('numeric'),
),
'character_limit'=>array(
'rule'=>array('characterLimit'),
'message' => 'Your role limit has been reached for this character',
),
'event_limit'=>array(
'rule'=>array('eventLimit'),
'message' => 'Your event limit has been reached for this role',
),
'character_required'=>array(
'rule'=>array('characterRequired'),
'message' => 'You have not met this events role requirments',
),
),
'description' => array(
'notempty' => array(
'rule' => array('notempty'),
'message' => 'You must have a description of your event',
'allowEmpty' => true,
),
),
'created' => array(
'datetime' => array(
'rule' => array('datetime'),
),
),
'modified' => array(
'datetime' => array(
'rule' => array('datetime'),
),
),
);

public $belongsTo = array(
'Event' => array(
'className' => 'Event',
'foreignKey' => 'event_id',
),
'Character' => array(
'className' => 'Character',
'foreignKey' => 'character_id',
),
'Role' => array(
'className' => 'Role',
'foreignKey' => 'role_id',
)
);

public function characterLimit($check) {
$result = false;
$limit = 0;
if(!isset($this->data['Characters']['character_count'])){
$role = $this->Role->find('first', array(
'conditions'=>array('Role.id'=>$this->data['Characters']['role_id']), 
'contain'=>array(), 
'fields'=>array('Role.id', 'Role.character_count'),
));
$limit = $role['Role']['character_count'];
}else{
$limit =  $this->data['Characters']['character_count'];
}

if($limit == -1 || !empty($this->data['Characters']['id']) ){
$result = true;
}else{
$recordCount = $this->find('count', array('conditions'=>array(
'Characters.character_id'=>$this->data['Characters']['character_id'],
'Characters.role_id'=>$this->data['Characters']['role_id'],
)));
$result = $recordCount < $limit;
}
return $result;
}
public function eventLimit($check) {
$result = false;
$limit = 0;
if(!isset($this->data['Characters']['event_count'])){
$role = $this->Role->find('first', array(
'conditions'=>array('Role.id'=>$this->data['Characters']['role_id']), 
'contain'=>array(), 
'fields'=>array('Role.id', 'Role.event_count'),
));
$limit = $role['Role']['event_count'];
}else{
$limit =  $this->data['Characters']['event_count'];
}

if($limit == -1){
$result = true;
}else{
$recordCount = $this->find('count', array('conditions'=>array(
'Characters.character_id'=>$this->data['Characters']['character_id'],
'Characters.role_id'=>$this->data['Characters']['role_id'],
)));
$realCount = 0;
foreach($this->Event->data['Characters'] as $character){
if($character['role_id'] == $this->data['Characters']['role_id']){
$realCount++;
}
}
$result = $recordCount <= $limit;
}
return $result;
}

public function characterRequired($check) {
$result = false;
$required = 0;
if(!isset($this->data['Characters']['required'])){
$role = $this->Role->find('first', array(
'conditions'=>array('Role.id'=>$this->data['Characters']['role_id']), 
'contain'=>array(), 
'fields'=>array('Role.id', 'Role.required'),
));
$required = $role['Role']['required'];
}else{
$required =  $this->data['Characters']['required'];
}

if($required <= 0){
$result = true;
}else{
$characterCount = 0;
foreach($this->Event->data['Characters'] as $character){
if($character['role_id'] == $check['role_id'] && isset($character['character_id'])){
$characterCount++;
}
}
$result = $characterCount >= $required;
}
return $result;
}
}


//The Role Model

App::uses('AppModel', 'Model');
class Role extends AppModel {

public $displayField = 'name';
public $validate = array(
'id' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'events_type_id' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'name' => array(
'notempty' => array(
'rule' => array('notempty'),
),
),
'description' => array(
'notempty' => array(
'rule' => array('notempty'),
),
),
'character_count' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'event_count' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'required' => array(
'numeric' => array(
'rule' => array('numeric'),
),
),
'created' => array(
'datetime' => array(
'rule' => array('datetime'),
),
),
'modified' => array(
'datetime' => array(
'rule' => array('datetime'),
),
),
);

public $hasMany = array(
'CharactersEvent' => array(
'className' => 'CharactersEvent',
'foreignKey' => 'role_id',
'dependent' => false,
),
);

public $belongsTo = array(
'EventsType' => array(
'className' => 'EventsType',
'foreignKey' => 'events_type_id',
),
);
}

//Here is the submission form
<?php echo $this->Form->create('Event');?>
<fieldset>
<?php
//$chronicles = array($event['Chronicle']['id']=>($this->Display->displayChronicleName($event['Chronicle']['Game']['short'], $event['Chronicle']['start'], $event['Chronicle']['end'])));
echo $this->Form->input('id');
echo $this->Form->input('event_type_id', array('options'=>array($event['EventsType']['id']=>$event['EventsType']['name']) ));

if(!empty($this->request->data['Event']['start_date'])){
echo $this->Form->input('start_date', array('type'=>'string', 'label'=>'Start Date (YYYY-MM-DD)'));
}

if(!empty($this->request->data['Event']['end_date'])){
echo $this->Form->input('end_date', array('type'=>'string', 'label'=>'End Date (YYYY-MM-DD)'));

}
echo $this->Ajax->autoComplete('Location.name', '/locations/autoComplete', array('fieldName'=>'Event.location_name', 'fieldIdName'=>'Event.location_id', 'formatResult' => "return data[0];", 'passId'=>true, 'minChars'=>2, 'label'=>__('Location'), 'div'=>true, 'nameFormat'=>array('format'=>'%s (%s)', 'fields'=>array('Location.name', 'ParentLocation.name')), 'conditions'=>array('Location.type_id'=>'3')));
echo $this->Form->input('description');
?>
<div id="characters">
<?php for($i=0; $i<count($this->request->data['Characters']); $i++): ?>
<?php echo $this->Form->input(('Characters.'.$i.'.id'), array('type'=>'hidden')); ?>

<table>
<tr>
<td><?php echo $this->Ajax->autoComplete('Character.name', '/characters/autoComplete', array('fieldName'=>('Characters.'.$i.'.name'), 'fieldIdName'=>('Characters.'.$i.'.character_id'), 'formatResult' => "return data[0];", 'passId'=>true, 'minChars'=>2, 'label'=>__('Character'), 'div'=>true, 'nameFormat'=>array('format'=>'%s (%s)', 'fields'=>array('Character.name', 'Domain.name')), 'conditions'=>array('Character.chronicle_id'=>$chronicleId))); ?></td>
<td><?php echo $this->Form->input(('Characters.'.$i.'.role_id'), array('options'=>$roles)); ?></td>
<td><?php echo $this->Form->input(('Characters.'.$i.'.description'), array('label'=>__('Description'), 'type'=>'text')); ?></td>
<td class="actions">
<?php echo $this->Form->postLink(__('X'), array('action' => 'removeCharacter', $this->request->data['Characters'][$i]['id']), null, __('Are you sure you want to remove %s from this event?', $this->request->data['Characters'][$i]['name'])); ?></li>
</td>
</tr>
</table>
<?php endfor; ?>
</div>
<div class="actions">
<ul>
<li><?php echo $this->Html->link(__('Add Character to Event'), array('action' => 'addCharacter', $event['Event']['id']));?> </li>
</ul>
</div>
</fieldset>
<?php echo $this->Form->end(__('Submit'));?>



//This is the contents of $this->request->data


  array(  	'Event' => array(  		'id' => '1',  		'event_type_id' => '2',  		'start_date' => (int) 735259,  		'location_name' => 'San Francisco',  		'location_id' => '4',  		'description' => 'Super Cool Event Description'  	),  	'Characters' => array(  		(int) 0 => array(  			'id' => '1',  			'name' => 'Phobos',  			'character_id' => '1',  			'role_id' => '7',  			'description' => 'blah'  		),  		(int) 1 => array(  			'id' => '4',  			'name' => 'Deimos',  			'character_id' => '2',  			'role_id' => '7',  			'description' => 'blah'  		),  		(int) 2 => array(  			'id' => '3',  			'name' => 'Roslin Bricker',  			'character_id' => '41',  			'role_id' => '8',  			'description' => 'blah'  		),  		(int) 3 => array(  			'id' => '2',  			'name' => 'Alphonse Heisenberg',  			'character_id' => '26',  			'role_id' => '9',  			'description' => 'blah'  		),  		(int) 4 => array(  			'id' => '13',  			'name' => 'Spencer Marshal',  			'character_id' => '83',  			'role_id' => '9',  			'description' => 'blah'  		)  	)  )
Now, this submission should fail because $this->request->data['Characters'][3] & $this->request->data['Characters'][4] both have a role_id of 9, which is limited to only allowing one character with a role_id of 9. This triggers the CharactersEvent validation function "eventLimit" which loops through $this->Event->data['Characters'] to make sure the role_id's data stays consistent with the role maximum rules.  Also of note, role_id 7 does allow for multiple assignments per event so $this->request->data['Characters'][0] & $this->request->data['Characters'][1] will not fail.

When I submit the form, and have debug statements in it, they report back successful, but then show up a 2nd time before completion with this error:

  Invalid argument supplied for foreach() [APP\Model\CharactersEvent.php, line 188]
which happens to be the for loop of $this->Event->data which during the 2nd pass now is empty.

  • Why is that data now missing?
  • Can I keep that data from being cleared?
  • Is there a better way to access all of the submitted data during a saveAll()?

I know its long,  but I have been stumped by this for a while. Thanks in advance for the help. 
~Michael

--
Like Us on FaceBook https://www.facebook.com/CakePHP
Find us on Twitter http://twitter.com/CakePHP
 
---
You received this message because you are subscribed to the Google Groups "CakePHP" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cake-php+unsubscribe@googlegroups.com.
To post to this group, send email to cake-php@googlegroups.com.
Visit this group at http://groups.google.com/group/cake-php?hl=en.
 
 

No comments: