CakePHP hasMany checkbox - cakephp

I have two tables: Ingredients and Customers. The relationship between them is that Customers hasMany Ingredients. By default when doing the cakebake using the console, the only way to change them is by assigning an ingredient to the customer in the Ingredients page. However, I want to have in Customers page a checkbox list of Ingredients that can be assigned. Is it possible to do this? If yes, how?
edit:
What I have done until now is that I add this code to my add.ctp:
echo $this->Form->input('Ingredient',
array('label'=>'',
'type'=>'select',
'multiple'=>'checkbox',
'options'=>$ingredients));
However, it gives me "Undefined variable: ingredients" error when I tried to open the add view.

You want and need a HABTM relationship. Different customers can access and use the same ingredients. Look at the docs here yours would be very similar to different Posts using same Tags.

If you are getting this error:
"Undefined variable: ingredients"
It sounds like you haven't declared this variable in your controller, and set it so that the view can use it. Without knowing your code, you would probably need do do something like this (please note I am guessing what your application structure looks like and have not tested this code).
CustomersController.php
// The controller action for your view
public function view() {
// Get the ID and name of all your ingredients
$ingredients = $this->Ingredient->find('all', array(
'fields' => array('id', 'name'),
'order' => 'name',
'recursive' => -1
));
// We will use this array to store all the HTML select options
$ingredientOptions = array();
// Loop through all the ingredients and add them to the select
// options in a format that is suitable for CakePHP to use in
// the view to build your HTML select menu.
foreach ($ingredients as $i) {
$ingredient = $i['Ingredient'];
$ingredientOptions[$ingredient['id']] = $ingredient['name'];
}
// Make the variable available to the view
$this->set('ingredients', $ingredientOptions);
}

Related

CakePHP 2.0 - How to remove join tables from containable

I'm using Containable in an action like this:
public function index()
{
$this->User->recursive = -1;
$this->User->Behaviors->load('Containable');
if ($this->RequestHandler->accepts('xml'))
{
$this->set('users', array("Users" => array("UserEntry" => $this->User->find('all',
array(
'fields' => array('User.id','User.username', 'User.email', 'User.created', 'User.modified'),
'contain' => array(
'Group' => array(
'fields' => array('Group.id','Group.name','Group.created'),
)
)
)
))));
}
else if ($this->RequestHandler->accepts('json'))
{
}
else if ($this->RequestHandler->accepts('html'))
{
$this->set('users', $this->paginate());
}
}
It gets all of the data I need, but there is one thing that I can't figure out. There is a HABTM relationship between users and groups with a join table users_groups. I'm serializing the output of find('all') into Xml for a REST Api in the view. The problem is that the data contains an extra 'GroupsUser' array nested in my 'Groups' array. The users of the Api do not need to know about the join table information so I would like to remove it. The current output looks like this:
index.ctp
<?php
//debug($users);
$xml = Xml::build($users, array('return' => 'domdocument'));
echo $xml->saveXML();
?>
output of index.ctp -> http://www.pastie.org/2789367
See the GroupsUser tag nested in the Group tag? That is what I want to remove. If there is not a nice easy way to do this I will either build the xml by hand using some loops in the view or create my own find method in the the model and use unset() on GroupsUser. Both of those solutions are not ideal, so I'm hoping someone here has a better one. :)
I you are positive that everything is being done in a join (only belongsTo associations) you may use the containable component with autofields in false something like this
$this->Post->Behaviors->attach('Containable', array('autoFields' => false));
(this is to attach it dynamiclly in the controller part.
if your find has hasMany association, you will have 2 queries instead of so cake needs to fetch for this fields to do the join. You may also use linkable component for this case, that put all in joins instead of a lot of queries giving you chance to select only the fields you want.
here is a link for the linkable component

sort on array read

In my controller I have
var $uses = array('User','Customer');
then I am using read to call the users
$loggedOutCustomer = $this->User->read(null, $this->Auth->user('id'));
this gives me users and cutsomers that I can use but I want to do a sort on customers name. How can i do that in cake?
You can set the default order in your User model.
If Customer belongsTo User, User hasMany Customer.
In your User model, under the hasMany variable, find the Customer record and add Customer.name to the order array key.
Also, please remove the cakephp version tag that you are not using.
You can also add your sorting options when you're using the find() method of your model.
This can also be defined in the pagination array
Another option would be to use the Containable behavior
Give this a try
$loggedOutCustomer = $this->User->find('first', array('conditions' => array('User.id' => $this->Auth->user('id')), 'order' => 'Customer.name ASC'));

CakePHP AutoComplete Question

I am working on a book review application and I am using autoComplete to search for titles in when creating a review. The review model has an associated book_id field and the relationship is setup as the review hasOne book and a book hasMany reviews.
I am trying to pass the Book.id (into the book_id field), but I want to display Book.name for the user to select from. With the default setup (accomplished via CakePHP's tutorial), I can only pass Book.name. Is it possible to display the name and pass the id?
Also, I am passing it via the following code in the create() action of the review controller:
$this->data['Review']['book_id'] = $this->data['Book']['id'];
Is that the proper way to do it in CakePHP? I know in Ruby on Rails, it is automatic, but I can't seem to make it work automagically in CakePHP. Finally, I am not using the generator because it is not available in my shared hosting environment... so if this is the wrong way, what do I need other than associates in my models to make it happen automatically?
Thanks for the help and I promise this is my question for awhile...
UPDATE- I tried the following, but it is not working. Any ideas why?
function autoComplete() {
$this->set('books', $this->Book->find('all', array(
'conditions' => array(
'Book.name LIKE' => $this->data['Book']['name'].'%'
),
'fields' => array('id','name')
)));
$this->layout = 'ajax';
}
The problem is that when I use the code above in the controller, the form submits, but it doesn't save the record... No errors are also thrown, which is weird.
UPDATE2:
I have determine that the reason this isn't working is because the array types are different and you can't change the array type with the autoComplete helper. As a workaround, I tried the follow, but it isn't working. Can anyone offer guidance why?
function create() {
if($this->Review->create($this->data) && $this->Review->validates()) {
$this->data['Review']['user_id'] = $this->Session->read('Auth.User.id');
$this->Book->find('first', array('fields' => array('Book.id'), 'conditions' => array('Book.name' => $this->data['Book']['name'])));
$this->data['Review']['book_id'] = $this->Book->id;
$this->Review->save($this->data);
$this->redirect(array('action' => 'index'));
} else {
$errors = $this->Review->invalidFields();
}
}
FINAL UPDATE:
Ok, I found that the helper only takes the find(all) type or array and that the "id" field wasn't passing because it only applied to the autoComplete's LI list being generated. So, I used the observeField to obtain the information and then do a database lookup and tried to create a hidden field on the fly with the ID, but that didn't work. Finally, the observeField would only take the characters that I put in instead of what I clicked, due to an apparent Scriptaculous limitation. So, I ended up going to a dropdown box solution for now and may eventually look into something else. Thanks for all of the help anyway!
First of all, $this->data will only contain ['Book']['id'] if the field exists in the form (even if it's hidden).
To select something by name and return the id, use the list variant of the find method, viz:
$selectList = $this->Book->find('list', array(
'fields' => array(
'id',
'name'
)));
$this->set('selectList', $selectList);
In the view, you can now use $selectList for the options in the select element:
echo $form->input('Book.id', array('type' => 'hidden'));
echo $form->input('template_id', array(
'options' => $selectList,
'type' => 'select'
));

CakePHP: Can I ignore a field when reading the Model from the DB?

In one of my models, I have a "LONGTEXT" field that has a big dump of a bunch of stuff that I never care to read, and it slows things down, since I'm moving much more data between the DB and the web app.
Is there a way to specify in the model that I want CakePHP to simply ignore that field, and never read it or do anything with it?
I really want to avoid the hassle of creating a separate table and a separate model, only for this field.
Thanks!
Daniel
As #SpawnCxy said, you'll need to use the 'fields' => array(...) option in a find to limit the data you want to retrieve. If you don't want to do this every time you write a find, you can add something like this to your models beforeFind() callback, which will automatically populate the fields options with all fields except the longtext field:
function beforeFind($query) {
if (!isset($query['fields'])) {
foreach ($this->_schema as $field => $foo) {
if ($field == 'longtextfield') {
continue;
}
$query['fields'][] = $this->alias . '.' . $field;
}
}
return $query;
}
Regarding comment:
That's true… The easiest way in this case is probably to unset the field from the schema.
unset($this->Model->_schema['longtextfield']);
I haven't tested it, but this should prevent the field from being included in the query. If you want to make this switchable for each query, you could move it to another variable like $Model->_schemaInactiveFields and move it back when needed. You could even make a Behavior for this.
The parameter fields may help you.It doesn't ignore fields but specifies fields you want:
array(
'conditions' => array('Model.field' => $thisValue), //array of conditions
'fields' => array('Model.field1', 'Model.field2'), //list columns you want
)
You can get more information of retrieving data in the cookbook .
Another idea:
Define your special query in the model:
function myfind($type,$params)
{
$params['fields'] = array('Model.field1','Model.field2',...);
return $this->find($type,$params);
}
Then use it in the controller
$this->Model->myfind($type,$params);
Also try containable behaviour will strip out all unwanted fields and works on model associations as well.
Containable
class Post extends AppModel { <br>
var $actsAs = array('Containable'); <br>
}
where Post is your model?
You can add a beforeFilter function in your Table and add a select to the query
Excample:
public function beforeFind(Event $event, Query $query){
$protected = $this->newEntity()->hidden;
$tableSchema = $event->subject()->schema();
$fields = $tableSchema->columns();
foreach($fields as $key => $name){
if(in_array($name,$protected)){
unset($fields[$key]);
}
}
$query->select($fields);
return $event;
}
In this excample I took the hidden fields from the ModelClass to exclude from result.
Took it from my answer to a simular question here : Hidden fields are still listed from database in cakephp 3

CakePHP HABTM: Editing one item casuses HABTM row to get recreated, destroys extra data

I'm having trouble with my HABTM relationship in CakePHP.
I have two models like so: Department HABTM Location. One large company has many buildings, and each building provides a limited number of services. Each building also has its own webpage, so in addition to the HABTM relationship itself, each HABTM row also has a url field where the user can visit to find additional information about the service they're interested and how it operates at the building they're interested in.
I've set up the models like so:
<?php
class Location extends AppModel {
var $name = 'Location';
var $hasAndBelongsToMany = array(
'Department' => array(
'with' => 'DepartmentsLocation',
'unique' => true
)
);
}
?>
<?php
class Department extends AppModel {
var $name = 'Department';
var $hasAndBelongsToMany = array(
'Location' => array(
'with' => 'DepartmentsLocation',
'unique' => true
)
);
}
?>
<?php
class DepartmentsLocation extends AppModel {
var $name = 'DepartmentsLocation';
var $belongsTo = array(
'Department',
'Location'
);
// I'm pretty sure this method is unrelated. It's not being called when this error
// occurs. Its purpose is to prevent having two HABTM rows with the same location
// and department.
function beforeSave() {
// kill any existing rows with same associations
$this->log(__FILE__ . ": killing existing HABTM rows", LOG_DEBUG);
$result = $this->find('all', array("conditions" =>
array("location_id" => $this->data['DepartmentsLocation']['location_id'],
"department_id" => $this->data['DepartmentsLocation']['department_id'])));
foreach($result as $row) {
$this->delete($row['DepartmentsLocation']['id']);
}
return true;
}
}
?>
The controllers are completely uninteresting.
The problem:
If I edit the name of a Location, all of the DepartmentsLocations that were linked to that Location are re-created with empty URLs. Since the models specify that unique is true, this also causes all of the newer rows to overwrite the older rows, which essentially destroys all of the URLs.
I would like to know two things:
Can I stop this? If so, how?
And, on a less technical and more whiney note: Why does this even happen? It seems bizarre to me that editing a field through Cake should cause so much trouble, when I can easily go through phpMyAdmin, edit the Location name there, and get exactly the result I would expect. Why does CakePHP touch the HABTM data when I'm just editing a field on a row? It's not even a foreign key!
From the CookBook the 1st problem is:
By default when saving a
HasAndBelongsToMany relationship, Cake
will delete all rows on the join table
before saving new ones.
I am not quite sure why Cake is trying to save the HABTM data even though you don't have a foreign key in your data, but there is an easy solution for that. Simply destroy the association for the save call:
$this->Location->unbindModel(
array('hasAndBelongsToMany' => array('Department'))
);
I'm thinking of one reason why this might be happening. When you retrieve Location, you also retrieve locations_departments data. And when you do a save($this->data) it looks for models in the array and saves them.
A way to solve this is setting the recursive attribute (of a model) to -1 or 0 (try, I'm not sure, just print out the data to see what comes out). You can set it in the model: var $recursive = -1; or in the controller method (action): $this->ModelName->recursive = -1;
More about recursive: http://book.cakephp.org/view/439/recursive
It's really similar to what harpax suggested, just if you don't need that data, tell it to Cake, so that it won't fetch it.
Trouble is that when saving your Location, you gave the save method an array containing all the DepartmentsLocations too. Thus CakePHP destroys everything and try to recreate it.
This is a common mistake with cake since it will often pull far too many results for you.
Be sure to pass only the data that needs to be saved, or better to fetch only the datas you need.

Resources