cakephp habtm relationships with input rather than select box? - cakephp

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.

Related

CakePHP How can I save many records in a link table at once for user - map - attributes?

I have three tables/models:
User(id)
Map(id, user_id, attribute_id)
Attribute (id, name)
Map belongsTo the others, the others hasMany Map.
I'd like the user (via user controller and user view) to create many links to associations at once. How can I do this, assuming that the user forms (add/edit) have 10 attribute fields, all linking to the same table?
I'd need to save up to 10 records in Map in one go. To start with, I'm unsure what the field should be - create('Attribute.name')? Also, cake outputs the same input name to each input as they point to the same field - what's the best way to fix this?
I have already read the relevant documentation, but didn't get much from it.
Thanks!
You can use saveAssociated() method. Prepare an input array according to the instructions given in the link and directly use $this->User->saveAssociated($your_array, array('deep' => true));

CakePHP actsAs Translate and $Model::find()

I have attached the Translate behavior to one of my models and I have some shortcomings regarding this:
1) If I don't save data in all fields passed as params when attaching the behavior to the model, $Model::find() method doesn't get the inserted rows.
public $actsAs = array(
'Translate' => array(
'title' => 'title_Translation',
'description' => 'description_Translation',
'description_long' => 'description_long_Translation'
)
);
Ex: if i pass to $Model::save() method only a value for 'title', the data is saved, even in the i18n table, but the $Model::find() doesn't get anything. I must pass data for all the fields.
Can I force it to retrieve those records ?
2) How can I get all the records in the admin side of the application (regardless of the language in which a record is saved) in order to list them so the user can alter it (edit data, save data in multiple languages)? Right now, I can only get the records that correspond to the current language (read from Configure or set explicitly)..
Thank you!
I kind of solved it, I copied the TranslateBehavior to app/Model/Behavior (just to avoid problems on future upgrades and keep the original one just in case) then I changed the _addJoin(...) method of the behavior, just changed the join type from INNER to LEFT on line 255 (I use cake 2.2.3).
Now if a record exist it is always retrieved, even if translated fields are missing.
Don't see any drawbacks besides the need to check if the translation field is empty.
OK, I might be a bit late, but anyway...
1) Cake uses an INNER JOIN when fetching a row and it's associated translations, so basically there's no easy way around this. You have to make sure you save every translatable field, every time - even if you just save it as blank. The only alternative would be to go hacking round the core to make it use a left join rather than an inner join - but don't do that.
2) The cookbook explains how to fetch all records here: http://book.cakephp.org/2.0/en/core-libraries/behaviors/translate.html#retrieve-all-translation-records-for-a-field
Now, probably most of the time you want to get just one translation, so you don't want to modify the definition of your $actsAs['Translate'] array in your model. So what I did, was set up a method in AppModel.php which modifies the $actsAs['Translate'] array on the fly:
/*
* See http://book.cakephp.org/2.0/en/core-libraries/behaviors/translate.html#using-the-bindtranslation-method
* This is for making it so we fetch all translations, as opposed to just that of the current locale.
* Used for eg. editing (multiple translations) via the admin interface.
*/
public function bindAllTranslations(){
$translatableFields = $this->actsAs['Translate'];
$keyValueFields = array();
foreach($translatableFields as $field){
$keyValueFields[$field] = $field.'Translation';
}
$this->bindTranslation($keyValueFields,false); // false means it will be changed for all future DB transactions in this page request - and won't be reset after the next transaction.
}
So, if it's an admin method (or any other situation you want all translations) you call that code before doing a find:
$this->MyModel->bindAllTranslations();
$this->MyModel->find('all');
Hope that helps!
Not exactly sure if it will help in your case, but you can also use
array to set locale before you call find()
$this->YourModel->locale = array("ENG", "GER", "JAP");
This way you will always get all records even if they don't have all possible translations.
Thanks a lot eleonzx, I'm having this problem since a decade, and with your simple answer I can now move forward ! So thanks again.
And maybe this code can help a lot of people :
in my AppController beforeFilter method I call _setLanguage
private function _setLanguage() {
if($this->Session->read('Config.language')){
$locale = $this->Session->read('Config.language');
$this->{$this->modelClass}->setLocale($locale);
}else{
$this->{$this->modelClass}->Behaviors->disable('Translate');
}
}
With the else condition I disable the Translate Behavior on the fly to get the original contents if there is no locale set in the session (I use basic links to switch between languages).

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

Getting related data using Wizard Component

The short version of this question is:
How can I take data that only exists in an array (ie: not saved in a model yet) and relate it to a value in a $this->find('list') array from a model?
The long version of this question is:
I'm using CakePHP and the Wizard Component for a 3-step application form.
The 3 form steps are contact, course and details.
After these steps, there is a 'review' step which I want to display all of the submissions from the previous 3 form steps for the user to check one last time before pressing submit. For the most part, this works very well. I just need to do the following in the controller:
function _prepareReview() {
$contact = $this->Wizard->read('contact');
$course = $this->Wizard->read('course');
$details = $this->Wizard->read('details');
$this->set(compact('contact','course','details'));
}
Then, in review.ctp I can reference things like $contact['Contact']['firstname']; to get the person's firstname etc.
However, the problem is getting data from 'related' models. For example, there's a "Nationality" field which is just an ID. In the 'details' step, I use find('list') to get a list of all nationalities from the Nationality model as a dropdown menu which displays properly and then Cake saves the corresponding ID as it should do.
But, when I come to the 'review' step in the Wizard Component, I only get the actual ID from the Wizard Component's array. I couldn't really expect to get anything else.
I can't see any obvious way to access $details[Detail][Nationality][name] (or something like this) from the context of the Wizard Component because setting recursive doesn't work because the data isn't actually in the model at this stage, it's just an array of form data.
So, in other words, I have data in an array (NOT coming from a model, but from a form subsmission) as follows:
Array
(
[Details] => Array
(
[firstname] => Test
[nationality_id] => 3
)
)
Then I have the following coming from a $this->Detail->Nationality->find('list') which looks like this:
Array
(
[0] => American
[1] => Australian
[2] => British
[3] => Canadian
)
So how can I get $details['Details']['nationality_id']; from the Wizard Component to display 'Canadian' instead of '3' for example? How to I make the relationship when only one of the arrays is coming from a model? I only need this to momentarily confirm all of the data to the user. The id '3' will, of course, be written to the Model once the user presses submit, and this is already working as it should do.
Or is this a completely crazy way of doing things and I should look at a totally different approach such as saving the data first? I don't really want to save it until the user clicks the final submit, though.
I can see what you are getting at here - CakePHP isn't automatically querying these related models for you (as you aren't pulling from the database) but you can't help but think you are missing out on some of the framework's free functionality.
If you were still using FormHelper::input() it would automatically select the correct option (given you perform a Model::find('list') and passed the options list to the view first), but I'm assuming you wish for the review screen to be free of form inputs (disabled or not).
The most straightforward approach would be to simply perform the same Model::find('list') calls you do for each step in the wizard, set the data from each to the view, and print out the appropriate value manually:
// controller
$nationalities = $this->Review->Details->Nationality->find('list');
$this->set(compact(/*..., */ 'nationalities'));
// view
<?php echo $nationalities['Nationality'][$details['Detail']['nationality_id']]; ?>
outputs 'Canadian' (the value for $nationalities['Nationality'][3])
It might be possible to get CakePHP to do it for you by calling DboSource::queryAssociation() just right - if your up for the challenge - but is probably overkill for this particular problem.
// model
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$data = $db->queryAssociation($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet, $recursive, $stack) {

cakephp pass foreign key to add method in controller

i have two tables persons and numbers. persons has a hasMany association with numbers, while numbers has a belongsTo association with persons. i also have a view method in the persons controller which displays the information about that person (including all the numbers associated to him). i also want to add an link to add a new number to that person. i was successful in passing the primary key of the person to the add method, but cant include it to the form in the add's view. i tried placing it in $this->data and adding a hidden field to the view, and using set() and assigning it as a default value to a hidden field in the view, but to no avail. any thoughts are appreciated.
I prefer passing the person ID in the GET request itself.
In your numbers controller.
function add($personId = null) {
//code
$this->set('personId', $personId);
}
In your view, modify your form helper to post to a different URL with the person ID on the end
echo $this->Form->create('Number', array('url' => '/admin/number/add' . $personId));
Now when the form posts the data, the $personId is passed to the method so you don't need to access it via the data variable.

Resources