How to Save HasAndBelongsToMany (habtm) data when empty in CakePHP - cakephp

I have a hasAndBelongsToMany association between Node and NodeTag.
My save works great when at least one is selected, but - if there is a previous association, and they try to save with none selected, my habtm table doesn't get updated, since it's not getting anything passed for NodeTag.
(I'm not sure if this is because I'm using javascript and custom buttons, or if the default CakePHP checkboxes do the same thing).

In my Controller, before the save, I added this:
if(!isset($this->request->data['NodeTag'])) {
$this->request->data['NodeTag'][0] = array();
}
This makes it so if I wasn't sending any NodeTag data, I now pass an empty array, and it updates the habtm table so that this Node no longer has any rows for NodeTags.
Note: Notice the array structure: ['NodeTag'][0] = array();

I was actually using your solution until it dawned on me that it is possible to achieve this with the Formhelper. Setting the hiddenField option to true ('hiddenField' => true), will force the submission of an empty array. Doing that saves you the trouble of checking once more in the controller.
I do not know how you could achieve it with javascript, but the default checkboxes have the functionality.

Related

Setting the id for a checkbox causes Security error in CakePHP

I am using the Security component in my AppController. I need to build a check form input that will allow custom formatting of each item in the list. To accomplish this, I am processing each item in the $options set using a foreach and creating the new element like so:
foreach ($fileTypes as $fileType_key => $fileType_value) {
echo $this->Form->input(
'FilesIncluded.' . $fileType_key,
array(
'type' => 'checkbox',
'value' => $fileType_key,
'label' => false,
'div' => false,
'before' => '<span class="checkbox clearfix"><span class="check">',
'after' => '</span><label for="add-product-check-sub-cat">' . $fileType_value . '</label></span>',
'hiddenField' => false,
)
);
}
There is a few things to note:
I am setting each check box to data[FilesIncluded][{UUID}] (where UUID actually represents the UUID pf the FilesIncluded) instead of data[FilesIncluded][]
FilesIncluded is not part of the form model, so it will appear in $this->request->data as $this->request->data['FilesIncluded'] instead of $this->request->data['Model']['column']
What I am trying to figure out is why this throws an auth security risk? When I change the field name from 'FilesIncluded.' . $fileType_key to something with a counter in it like 'FilesIncluded.' . $count . '.id', it seems to work without throwing any security auth errors. Any ideas how to make this work the way I am expecting it to?
UPDATE:
The other issue is being able to maintain a fixed set of FileTypes. For example, I want to be able to control the HABTM records that can be selected from the checkbox. For example, I will display this list:
http://cl.ly/image/0b1Q3C0d0w1Y
And only when the user selects the records will they be stored as hasMany. Then when it comes time to edit, I want to not only be able to show the same set of records, but then have them associated to the records the user saved.
(Probable) Cause
You're probably getting the Security error, because you're disabling the hiddenField for the checkboxes. The Security components checks if a Submitted Form is valid (i.e. not tampered with) by calculating a checksum, based on the names of the Form fields, when creating the form and comparing this with the data received when submitting the form.
By suppressing the hiddenField, the checkbox will not be present in the posted data if it is not checked (Non-checked checkboxes are never sent in HTML forms). As explained above, CakePHP calculates a checksum based on the fields/inputs it expects and the actual fields (data) it receives. By disabling the hiddenField, the checksum calculated from the posted data will depend on wether a checkbox was checked or not, which will invalidate the posted data
Workarounds
There may be some Workarounds;
Do not suppress the 'hiddenField' This will make sure that the checkboxes will always be present in the posted data. If a checkbox is not checked, the value of the checkbox will be 0 (zero). If the checkbox is checked, its posted value will be the specified value of the checkbox (or 1 if no value is specified)
Exclude your custom inputs from the checksum. You can exclude fields from the checksum via $this->Form->unlockField('fieldname');. CakePHP will ignore those inputs when calculating the Security checksum.
Documentation: FormHelper::unlockField()
Notes
Although these Workarounds may help, I'd suggest to not re-invent the wheel. By changing the names of your inputs, you're no longer following the CakePHP conventions. Sticking to the conventions often saves you a lot of time.
For example, saving related data can be performed with a single Model::saveAssociated() call. If your Model-relations are properly set, for example:
Document->hasMany->UserFiles, then CakePHP will insert/update both the Document and UserFiles data automatically.
Read the documentation here Saving Related Model Data (hasOne, hasMany, belongsTo)

CakePHP - remove data from $this->request->data

I have a edit page for my ExpenseClaim model. The edit page lists all the related Expenses belonging to that ExpenseClaim in a form so users can edit the expenses. I have a X next to each expense so they can delete it if needed. This X just removes the rows fields from the DOM using JS.
When the save button is pressed I'd like any removed rows to be deleted from the Expense table but as $this->request->data isnt changing (the changes are only in the DOM) they don't get deleted. Any edited data does change but not removed data.
I thought about using a postLink but as that creates its own form and I'm already in a form I cant.
So how can I get cake to recognise the changed data? Or am I going about it the wrong way?
Thanks in advance
See this is a database entry and it cannot be deleted like this. And you cannot do it by unset also. The changes has to be reflected in the database.
Better you do an ajax request to the server with the id and delete the row from the database and onSuccess call back remove the element from the dom.
Doing these on a php array is really easy: `unset(array[$key])`.
You can | should | must - it depends on your code - use ajax to trigger these action.

CakePHP - Prevent model data being save in a saveMany call when certain fields are blank

I have scenario where I have 3 different models data being saved from one form, by means of the saveAll pass through method in CakePHP 2.x
The models on the form are:
Project
ProjectImage
ProjectAttachment
1 and 2 are saving perfectly, but I am having problems with the ProjectAttachment fields. If you look at the following image, the fields in question are at the bottom right, highlighted by a red frame: http://img.skitch.com/20120417-bnarwihc9mqm1b49cjy2bp13cf.jpg
Here is the situation:
I have named the fields as follows: *ProjectAttachment.0.project_id*, ProjectAttachment.0.name .... *ProjectAttachment.6.project_id* etc where the numbers are relative to the already present amount of attachments the user has added, as there is no limit. So if the user has ALREADY added 6 documents, the field would be called ProjectAttachment.7.id and so on. This is because of the naming convention when using the saveAll method.
I have made use of two hidden fields, one to store the user ID, the other to store the project ID.
The problem is that if the user does not fill in the Document Title and select a file to upload, the form fails! If I remove all validation rules, the form no longer fails BUT a blank ProjectAttachment record is inserted.
I suspect the problem may also be that the project_id and user_id fields (that are hidden) already have values in them?
I gave it some thought, and came up with a simple concept: In my controller, before the saveAll call, I would check to see if a blank Document Title field was submitted, and if so, I would completely eliminate the relevant array entry from the $this->request->data['ProjectAttachment'] array, but this did not seem to work.
To clarify, I am not looking for validation rules. If the user decides they only want to upload images and not touch the ProjectAttachment form, them the saveAll operation must not fail, it must simple not attempt to save the blank fields in the form, if this is at all possible.
Any suggestions?
Kind regards,
Simon
Well it seems that the solution was far simpler than I had initially thought! I was partially on the right track, and I had to unset the variable ProjectArray key in the $this->request->data array if I had encountered any blank fields.
I did this as follows:
if(!empty($this->request->data['ProjectAttachment'])){
foreach($this->request->data['ProjectAttachment'] as $key => $value){
if(is_array($value) && array_key_exists('upload_file', $value)){
if($value['upload_file']['name'] == ''){ // Blank document file
unset($this->request->data['ProjectAttachment']);
}
}
}
}
I hope someone finds this somehow useful in some form or another.
Kind regards,
Simon

Prevent Backbone.js model from validating when first added to collection

Is there a way to suppress model validation in Backbone.js when a new model is first created?
In my app, I have a collection with an arbitrary number of models, which are represented as list items. The user can click a button on each item, which inserts a new empty item beneath the current item. The empty item is failing validation, obviously, because I don't want an empty item being saved later.
There's no way for me to know what sensible defaults might be when I'm creating the new item, so prepopulating the new model with valid data doesn't seem like an option.
Any suggestions?
Update: While working on a tangentially related problem, I realized that I was using Backbone.js version 0.9.0. When this version was released, other people had the same problem I was having, and they complained in this issue on GitHub.
Jeremy modified validation in 0.9.1 to fix this. Adding a (temporarily) empty model to a collection is a valid real-world usecase. You could handle the new, empty model in the view, but if you're managing a list of items like I am, that forces you to have a collection of item views (including the empty one) in addition to your collection of must-be-valid models. That's a really clunky workaround for an otherwise straightforward scenario. Glad this got fixed.
You're not supposed to add invalid models :)
Digging a bit in Backbone source code (0.9.1 at least) showed that the mechanism can be circumvented by passing options to your add method:
var Mod=Backbone.Model.extend({
validate: function(attrs,opts) {
if (opts.init) return;
return "invalid";
}
});
var Col=Backbone.Collection.extend({
model:Mod
});
var c=new Col();
c.add({},{init:true});
console.log(c.length);
A Fiddle: http://jsfiddle.net/jZeYB/
Warning : it may break things down the line.
Do you need to add the model to the collection right away? I presume that validation fails because you add it to the collection immediately.
Instead, when the button is pressed you could just create the view and blank model. When the model validates you add it to the collection. You would need a submit button/mechanism on the new row to add it to the collection (which invokes validation automatically).

cakephp habtm relationships with input rather than select box?

I'm creating a form in cakephp that has a typical HABTM relationship. Let's say that I'm developing an order form with a coupon code. From a logic point of view, I need to accept a text-entry for the coupon code, so the incoming data would not be the proper primary key, but rather a different field.
I would then need to validate that data, and retrieve the proper primary key id, and then update the orders_coupons table with that coupon_id, and order_id.
When using a select box the value would always be the proper coupon_id, but where is the appropriate location for me to put the logic to handle this? Should I modify the data using beforeSave?
your question isn't very clear b/c it sounds like you can both specify the coupon via a select box or in just a free form text box.
My inclination is to add a new method to the Model that would update the record with the "human readable key". So the function would first read the coupon_id from the DB, and then do the update.
As you said, you'll just have to look up the coupon id...
// assuming $data looks like:
// array('Order' => array(...), 'Coupon' => array('code' => ...))
$coupon_id = $this->Order->Coupon->field('id', array('code' => $data['Coupon']['code']));
if (!$coupon_id) {
// user put in a non-existing coupon code, punish him!
} else {
$data['Order']['coupon_id'] = $coupon_id;
$this->Order->save($data);
}
If I get this correct, this is pretty much the same as tagging?! (There is a text input box for habtm items and the string is submitted to the controller without the ids).
If so, I would recommend to split the processing. Submit the data to the controller and then pass the coupon-string to a proper function in the coupon-model, that gets the coupon-ids (saves new items) and returns them back to the controller to save the complete habtm-data.

Resources