CakePHP HABTM Form Submission - cakephp

I have two tables, questions and tags, that have a HABTM relationship. When adding a question, I want to be able to specify a tag for the question (this would just be the first tag, with ability to add more tags later). The tags are pulled from their table. How can I configure my application so that when a question is added and a tag is specified, the join is reflected in the join table (questions_tags)?
Here is my question add action code :
function add() {
$tags = $this->Question->Tag->find('all');
$this->set('tags',$tags);
if (!empty($this->data)) {
$this->Question->create();
if ($this->Question->save($this->data)) {
$this->Session->setFlash(__('The question has been saved', true));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The question could not be saved. Please, try again.', true));
}
}
$users = $this->Question->User->find('list');
$tags = $this->Question->Tag->find('list');
$this->set(compact('users', 'tags'));
}
and here is my question add view code:
<?php
echo $this->Form->create('Question');
echo $this->Form->input('user_id',array('type' => 'hidden', 'value' => $this->Session->read('Auth.User.id')));
echo $this->Form->input('title');
echo $this->Form->input('details',array('type' => 'textarea'));
echo $this->Form->input('tag_id');
echo $this->Form->end(__('Submit', true));
?>

First make sure that your models are set up the right way. The fact that a user initially just adds one tag to your question does not change the fact that you should have a HABTM relation between the Question model and the Tag model (because you want the possibility add more tags later).
If your $this->data array is build according to the following schema:
$this->data = array(
'Question' => array(
'name' => 'Trick question'
),
'Tag' => array(
'Tag' => array(1,2,3)
)
);
Then a $this->Question->save() will save the Question data as well as the related Tag data (in this case Question 'Trick Question' with Tags with the id 1, 2 and 3).
Maybe take one step back and bake your Models, Views and Controllers for these two models (again) and see what Cake makes out of it. If I'm correct you'll just need a $this->Form->input('Tag') somewhere in your form (and if that not fills in the right data automatically you want to fill the options parameter with the result of $this->Question->Tag->find('list')).

if you have a single tag for a question, it's not HABTM. it has to be one to one or one to many relation.
in your questions model you can define property belongsTo:
class Question extends AppModel {
var $name = 'Question';
var $belongsTo = array(
'Tag' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id'
)
);
}
Something like this.
here is a link describing how to set HABTM
HABTM

Related

cake populate a dropdown from model

I am trying to populate a dropdpwn with some options. I am trying to have a dropdown with name title and country list for my input form.
For example:
Titles I need 'Mr', 'Mrs', 'Miss'
I have tried to ways:
Model
// Built a list of search options (unless you have this list somewhere else)
public function __construct($id = false, $table = null, $ds = null) {
$this->titles = array(
0 => __('Mr', true),
1 => __('Mrs', true),
2 => __('Miss', true));
parent::__construct($id, $table, $ds);
}
Controller
public function add() {
if ($this->request->is('post')) {
$this->Member->create();
if ($this->Member->save($this->request->data)) {
$this->Session->setFlash(__('The member has been saved'));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The member could not be saved. Please, try again.'));
}
}
}
View
<?php echo $this->Form->input('title',array('class' => 'span10', 'options' => $titles )); ?>
I get the error Undefined variable: titles
I have also tried in the model
public $validate = array(
'member_no' => array(
'notempty' => array(
'rule' => array('notempty'),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'title' => array(
'titlesValid' => array(
'rule' => array('multiple', array(
'in' => array('Mr', 'Mrs', 'Miss'),
'min' => 1
)),
What I am missing and what would be the best solution for a longer list such as countries, it is only on form so i didnt think I would nees a title and countries table and link ids?
I find really weird that you have to create a variable in a construct of a model for a dropdown. Specially if you're only going to use it once.
But first things first. You're getting that error because you haven't set the variable to the view. In your controller, there needs to be something like this:
public function add() {
$this->set('titles', $your_titles_array);
if ($this->request->is('post')) {
//etc
Now, please please get the dropdown array away from there. There are two possible nicer places to put that. I'm not going to get picky and say you need to have this values as a table in you database. If you say it's just for one place and you want to have it hardcode, so be it.
One option is to put it in one model, like
class YourModel extends AppModel {
//all your other definitions
public function getTitles() {
return array(
0 => __('Mr', true),
1 => __('Mrs', true),
2 => __('Miss', true));
}
}
And in the controller do
public function add() {
$this->set('titles', $this->YourModel->getTitles());
if ($this->request->is('post')) {
//etc
But, I don't think you have an appropriate model to add that function. Where did you plan to add it? User model maybe? It can't be like Post model, for example, that wouldn't make any sense... So, give it a thought, if there's a logical place in a model you can put that function, then go ahead.
Otherwise, and if it's just for one form in one place just that one time, why not hardcode it to the view or the controller?
For larger lists, my recommendation is to do it in a model like I showed (but I insist, put it in the model where is logical to have it). Well, I rather have a table and reference the values, but it's not mandatory (though give it a thought, is really not that much work, is it? for larger lists, I mean).

Cakephp HABTM: View generating drop down instead of multi value selectbox

I am trying to work with HABTM association between Profiles and Qualifications tables.
Model: Profile.php
App::uses('AppModel', 'Model');
class Profile extends AppModel {
public $hasAndBelongsToMany = array(
'Qualification' => array(
'className' => 'Qualification',
'joinTable' => 'profile_qualifications',
'foreignKey' => 'profile_id',
'associationForeignKey' => 'qualification_id',
'unique' => 'keepExisting'
)
);
}
Model: Qualification.php
App::uses('AppModel', 'Model');
class Qualification extends AppModel {
public $hasAndBelongsToMany = array(
'Profile' => array(
'className' => 'Profile',
'joinTable' => 'profile_qualifications',
'foreignKey' => 'qualification_id',
'associationForeignKey' => 'profile_id',
'unique' => 'keepExisting',
)
);
}
Controller: ProfilesController.php
App::uses('AppController', 'Controller');
class ProfilesController extends AppController {
public function edit() {
$this->Profile->id = $this->Auth->user('profile_id');
if ($this->request->is('post') || $this->request->is('put')) {
if ($this->Profile->save($this->request->data)) {
$this->Session->setFlash(__('The profile has been saved'));
$this->redirect(array('action' => 'view'));
} else {
$this->Session->setFlash(__('The profile could not be saved. Please, try again.'));
}
} else {
$this->request->data = $this->Profile->read(null, $this->Auth->user('profile_id'));
}
$salutations = $this->Profile->Salutation->find('list', array('fields' => array('Salutation.id', 'Salutation.abbr_name')));
$qualifications = $this->Profile->Qualification->find('list', array('fields' => array('Qualification.id', 'Qualification.abbr_name')));
$this->set(compact('salutations', 'qualifications'));
}
}
Vew: edit.ctp
<div class="profiles form">
<?php echo $this->Form->create('Profile'); ?>
<fieldset>
<legend><?php echo __('My Profile'); ?></legend>
<?php
echo $this->Form->input('salutation_id');
echo $this->Form->input('first_name');
echo $this->Form->input('middle_name');
echo $this->Form->input('last_name');
echo $this->Form->input('qualification'); /* gives drop down not multi select */
echo $this->Form->input('bio');
echo $this->Form->input('email');
echo $this->Form->input('mobile');
echo $this->Form->input('phone');
?>
</fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>
The edit view thus generated contains drop down to select a single value at a time for Qualifications attribute.
I want to know how can I generate a view with multi value selection box for qualifications ?
Moreover, what is the mistake in my code right now ?
First time poster, long time user.
I stumbled across this question today, and ended up using the subsequent solution which indeed does work quite nicely. However, I left myself wondering "why wouldn't CakePHP pickup on the HABTM relationship properly?" Especially considering (at least in my case) that the majority of the files had been baked in the cake console.
If we dive a little deeper into the issue, we discover that the problem is actually quite simple. A closer look at this snippet in the PostsController.php reveals how Cake builds in the HABTM relationship to the function, and uses $this->set() in order to pass it to the view (IMPORTANT: using lower-case plural versions "salutations"):
$salutations = $this->Profile->Salutation->find('list', array('fields' => array('Salutation.id', 'Salutation.abbr_name')));
$qualifications = $this->Profile->Qualification->find('list', array('fields' => array('Qualification.id', 'Qualification.abbr_name')));
$this->set(compact('salutations', 'qualifications'));
According to the Cake Cook Book, in order to take advantage of this HABTM in the front end when using the form helper is to specify the variable in singular form & title case (ie: "Salutation")
Snippet from the cook book:
Assuming that User hasAndBelongsToMany Group. In your controller, set a camelCase plural variable (group -> groups in this case, or ExtraFunkyModel -> extraFunkyModels) with the select options. In the controller action you would put the following:
$this->set('groups', $this->User->Group->find('list'));
And in the view a multiple select can be created with this simple code:
echo $this->Form->input('Group');
Should solve your issue without any necessary field tweaking.
Cheers!
Bake on.
Perhaps you need further configuration of your input:
echo $this->Form->input('Qualification',array(
'label' => 'Qualifications',
'type' => 'select',
'multiple' => true, // or 'checkbox' if you want a set of checkboxes
'options' => $qualifications,
'selected' => $html->value('Qualification.Qualification'),
));
I've used this blog post whenever I've come up against HABTM associations. It seems to me that a set of checkboxes maybe desired by default over a select input - maybe someone with greater CakePHP insight can chime in here?
Change
echo $this->Form->input('qualification');
to
echo $this->Form->input('qualification', array(
'multiple' => true
));
The CakePHP manual has more information on the form helper input options.

CakePHP Save newly created ID in Parent ID field of self assoc model

I have a parent/child self association table where when the id = parent_id, that id is it's own parent. However I'm having trouble saving data into my table from the add action/view
From add.ctp view - when adding a new record, I select a parent_id from the drop down box and enter a name.
<?php
echo $this->Form->input('parent_id', array('empty' => 'No Parent'));
echo $this->Form->input('name');
?>
If user selects "No Parent" this means I would like the parent_id = id where id is the unique ID automatically created in DB at time it is saved.
This is what is passed into $this->request->data when 'No Parent' is selected.
array(
'Item' => array(
'parent_id' => '',
'name' => 'testname'
)
)
I have tried to set the parent_id = id in the beforeSave but since id does not yet exist, there is nothing to assign parent_id to. I have also tried calling the "parent" model save first and saveAll in the controller but those don't work either.
Controller
public function add() {
if ($this->request->is('post')) {
$this->Item->create();
//have tried calling parent model in self association first but
//if ($this->Item->ParentItem->save($this->request->data)) {
if ($this->Item->saveAll($this->request->data)) {
$this->Session->setFlash(__('The item has been saved'));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The item could not be saved. Please, try again.'));
}
}
Item.php / Model relationship
public $belongsTo = array(
'ParentItem' => array(
'className' => 'Item',
'foreignKey' => 'parent_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
public $hasMany = array(
'ChildItem' => array(
'className' => 'Item',
'foreignKey' => 'parent_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
)
);
How can I take an ID that is just created/saved and save that to another field, in this case parent_id?
UPDATE:
I have been working on this some more and I have used getInsertId() to get the last inserted Id and I am trying to save that into the parent_id, but there is something that prevents this. I have removed all model validation to make sure it wasn't that. But is there something in Cake (or my association setup) that does not allow parent_id = id (i.e. a row is it's own parent?
This is the latest code in my add action... This saves a row to the DB, but w/o a parent_id. I then try to edit using the add action and set the parent_id = id, but even the edit wont allow a save.
public function add() {
if ($this->request->is('post')) {
$this->Item->create();
if ($this->Item->save($this->request->data)) {
$last_id = $this->Item->getInsertId();
$this->Item->saveField('parent_id', $last_id);
$this->Session->setFlash(__('The item has been saved'));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The item could not be saved. Please, try again.'));
}
}
I have also tried calling $this->Item->ParentItem->save(), saveAll, 'deep' => true, but still nothing is allowing me to update the parent_id column. A row ge
Thanks in advance
In Your model of association You can set foregin_key to parent_id, and will be automatic filled
So the problem I was having... trying to set a parent_id = id (making id it's own parent basically) in a self join model is due to this piece of code in my Model file
public $actsAs = array('Tree');
After reading through the Tree Behavior again, I realized that $this->Model->save (or saveField) does not really work well with Tree structures and updating parent IDs. Should use the behaviors functions instead form my understanding. Also the TreeBehavior expects some parent_ids to be null (at least top level parent_ids), so if I were to leave this as a tree, the ids with a null parent_id would be considered the parent.
http://book.cakephp.org/2.0/en/core-libraries/behaviors/tree.html
"The parent field must be able to have a NULL value! It might seem to work if you just give the top elements a parent value of zero, but reordering the tree (and possible other operations) will fail."
So I need to decide if I want to use tree behavior or have simple parent/child relationship without using Tree... think I will do the later as I don't have a need for multilevel tree, just a parent and one level of children.
Thanks for replies.

Validation problems with multiple checkboxes (HABTM) on CakePHP form

Short version
I have some HABTM checkboxes on a form. Validation is working correctly (at least one checkbox needs to be checked for validation to pass) but the CakePHP error message divs aren't being generated as they should be.
Long Version
I have a from which allows users to fill in their name and email address and then choose from a list of brochures (checkboxes) they'd like to receive.
The form looks like this:
<?php
echo $this->Form->create('Request',array('action' => 'index'));
echo $this->Form->input('id');
echo $this->Form->input('name');
echo $this->Form->input('email');
echo $this->Form->input('Brochure',array(
'label' => __('Information Required:',true),
'type' => 'select',
'multiple' => 'checkbox',
'options' => $list,
'selected' => $this->Html->value('Brochure.Brochure'),
));
echo $this->Form->submit('Submit');
echo $this->Form->end();
?>
In my controller, $list is set as like this:
$this->Request->Brochure->find('list',array('fields'=>array('id','name')));
After reading the 2nd answer (posted by user448164) in HABTM form validation in CakePHP on Stack Overflow, I set my Request model up like this:
<?php
class Request extends AppModel {
var $name = 'Request';
function beforeValidate() {
foreach($this->hasAndBelongsToMany as $k=>$v) {
if(isset($this->data[$k][$k]))
{
$this->data[$this->alias][$k] = $this->data[$k][$k];
}
}
}
var $validate = array(
'name' => array(
'rule' => 'notEmpty',
'message' => 'Please enter your full name'
),
'email' => array(
'rule' => 'email',
'message' => 'Please enter a valid email address'
),
'Brochure' => array(
'rule' => array('multiple', array('min' => 1)),
'message' => 'Please select 1'
),
);
?>
This actually works 99% well. If none of the checkboxes are checked, validation fails as it should do. However, the only problem is that Cake isn't setting the "error" class on the <div>, nor is it creating the <div class="error-message">Please select 1</div> as it should.
For name and email, there is no problem - the error divs are being created properly.
So, to clarify, validation is working for my HABTM checkboxes. The only problem is that the error divs aren't being generated.
I'm posting this here as this is actually a much better question than the related question you found.
I was banging my head against a wall trying to handle the same problem of getting the validation error to show up in the page. I'm using CakePHP v1.2 and I hit a similar problem although I have actually split out the HABTM into the individual tables i.e. Request->BrochuesRequest->Brochure. This is because I can't have it deleting and re-adding the joining table rows at will.
Firstly I think the accepted answer from your linked question assumes that you are doing a save / saveAll when the beforeValidate call is triggered, however I was doing it through a validates call. The difference is that you need to call the Request->set method first. It was an article by Jonathan Snook on Multiple Validation Sets pointed me to that issue.
The second issue is actually getting the error message to appear was down to the $field value you use when calling invalidate. For ages I was including the model as well as the field assuming that this was how it matched the invalidate call to the input, i.e. you have $form->input('BrochuresRequest.brochures_id') so you need $this->BrochuresRequest->invalidate('BrochuresRequest.brochures_id').
However that is wrong you just want $this->BrochuresRequest->invalidate('brochures_id').
<?php
// requests/add view
echo $form->input('BrochuresRequest.brochures_id', array('multiple' => true));
// requests_controller
function add() {
if (!empty($this->data)) {
$this->Request->create();
// critical set to have $this->data
// for beforeValidate when calling validates
$this->Request->set($this->data);
if ($this->Request->validates()) {
$this->Request->saveAll($this->data);
}
}
}
// request model
function beforeValidate() {
if (count($this->data['BrochuresRequest']['brochures_id']) < 1) {
$this->invalidate('non_existent_field'); // fake validation error on Project
// must be brochures_id and not BrochuresRequest.brochures_id
$this->BrochuresRequest->invalidate('brochures_id', 'Please select 1');
return false;
}
return true;
}
?>
A few of the other things that I picked up on the way through:
You don't need a separate $form->error in the view
I couldn't for the life of me get the 'multiple' validation rule to work in the model
The accepted answer checks for an isset but I believe that this isn't required and masked the problem of there being no $this->data being passed through.
The beforeValidate should return false if you want it to prevent any save action.

Edit of self referencing HABTM in cakephp works, sometimes

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) {}
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)
));

Resources