cakephp: find statement with 'contain' - cakephp

the following User model function is from MilesJones forum plugin. Can someone tell me on what is the use of 'contain' in the find stmt. I couldn't find any example with contain in the cakephp cookbook. Any helps is appreciated.
public function getProfile($id) {
return $this->find('first', array(
'conditions' => array('User.id' => $id),
'contain' => array(
'Access' => array('AccessLevel'),
'Moderator' => array('ForumCategory')
)
));
}

By default when a find statement executes cake pulls all the data from the model on which the find function is executing plus all the data from the models that are associated with the model. Most of the time you don't need that extra data, Cake has containable behaviour for exactly that purpose. You can specify which associated model's data you want in your result.
In the above example find statement will fetch the first record from the User model plus associated data from Access and Moderator models.
Here is the link from cakephp book http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html

Here is cakephp documentation about contain

Related

CakePHP conditions clause on associations when using Model::find()

I just confused because of a find() result. This is my configurations:
First, users can have different User.role values: student, admin, and some others.
// Book
public $belongsTo = array(
'Student' => array(
'className' => 'User',
'foreignKey' => 'student_id'
'conditions' => array('User.role' => 'student')
);
);
When I chain Models like $this->Book->Student->find('list'); I was expecting to get only users whose role are 'student', but instead, it gets all users. What is going on here, what is conditions for on association definition, where can it and cannot be used. Any lead would help, thanks.
PS: I am aware that I could put conditions on find(), that's not the issue
There is a difference between associated data and accessing an associated model object. If you access $this->Book->Student you're accessing the Student model and work in it's scope. The conditions in the defined associations work only in the context of the accessed object.
So if you do a find on the Book and list the students for that book:
$this->Book->find('first', array('contain' => array('Student'));
Your code will work correctly. It will find the book plus the first user who has the role stundent. BUT your association is wrong then: It should be hasMany. because why would you filter a book by role if the book just belongsTo one student?
If you want to filter users by their role you can implement a query param that is checked in beforeFind(), pseudocode: if isset roleFilter then add contention to filter by that role from roleFilter.
Or, if you don't need to paginate just create a getStudents() method in the user model that will return a find('list') that has the conditions.
Or Student extends User and put the filter in the beforeFind() and use that model instead of the User model in your Book association.
If you want to filter on model level or per model I think the last one is a good option. Don't forget to set $useTable and $name or the inherited model will cause problems.
you have miss , inside your model.
try this:
public $belongsTo = array(
'Student' => array(
'className' => 'User',
'foreignKey' => 'student_id', //<------ miss
'conditions' => array('User.role' => 'student')
);
);
Yoi can debug your query to check what is the real query that you make.
Personally I have never use this approach, I prefer to use foreign key with another table for examples Rolesand User.role_id.
Is better for me to use this approach to have more flexibility inside your app.
After I prefer to use a conditions where inside controller to check well the query, because in your way every query you search always for student role not for the other and can be a problem for the rest of role, because inside controller you see a find without conditions but it doesn't take right value because in your model there is a particular conditions.
For me the good way is to create a new table, use foreign key and where conditions inside action of the controller to view well what are you doing.
For default all relations are "left join", you must set the parameter "type" with "inner" value
// Book
public $belongsTo = array(
'Student' => array(
'className' => 'User',
'foreignKey' => 'student_id'
'conditions' => array('Student.role' => 'student'), // <-- Fix That (field name)
'type' => 'inner', // <-- add that
);
);

how to use cakedc/search plugin for searching across 3 different tables with 1 search bar?

I am using CakePHP2.4 and the search plugin https://github.com/CakeDC/search
I have the following
Employee hasOne EmployeeProfile
Employee hasMany Qualification
So I have a single search bar.
the search bar will search using LIKE through the following fields
Employee.name
EmployeeProfile.email
Qualification.title
how do I configure the model Employee->filterArgs for this search?
This is a cross-posting of the original issue here
The documentation includes an example.
'username' => array('type' => 'like', 'field' => array('User.username', 'UserInfo.first_name')),
You just have to make sure the models you're calling are available in your find() call. In the example the find will do a like on the users table username and on the user_infos first_name field at the same time.
I'd like to expand on this as I've been trying to setup a search on a hasMany relationship for a few hours and couldn't find anything. Mark mentionned "custom bindModel (as hasOne) for hasMany relationship (Qualification)". Here's how it's done :
$this->Employee->bindModel(array(
'hasOne' => array(
'Qualification' => array(
'foreignKey' => false,
'conditions' => array('Employee.id = Qualification.employee_id')
)
)
), false);
Just bind it before your paginate and add Qualification.title in your field list in your filterArgs

Complex find statement, don't know how to write multiple values for one condition

I am trying to build a paginated find call to my Unit model. I need the condition to be that it looks for unit.type of condo and rentalco, house and rentalco, but NOT rentalco and hotel. Additionally, the way I have my code worded, cake only returns unit types that are rentalco.
public function view($type=null) {
$this->set('title', 'All '.$type.' in and near Gulf Shores');
$this->set('featured', $this->Unit->getFeatured());
$this->paginate['Unit']=array(
'limit'=>9,
'order' => 'RAND()',
'contain'=>array(
'User'=>array('id'),
'Location',
'Complex',
'Image'
),
'conditions'=>array(
'Unit.type'=>array($type, 'rentalco'),
'Unit.active'=>1)
);
$data = $this->paginate('Unit');
$this->set('allaccommodations', $data);
$this->set('type', $type);
}
UPDATE I figured out why my find statement wasn't working (just had been passing the word condos instead of condo into my browser bar....derp derp); however, I would still love to know how I can tell cake to NOT allow a find with both type hotel and rentalco.
You are looking for the NOT. It would be something like:
'conditions' => array(
'NOT' => array('Unit.type' => array('hotel', 'rentalco')),
),
To be more specific, I would need to see your model schema.

Retrieve posts with their latest comment in CakePHP

Say I have a model Post and a model Comment related as follows:
Post hasMany Comment
Comment belongsTo Post
How do use find('all') to retrieve every Post with its associated latest Comment?
I have tried defining a hasOne relationship in Post as:
var $hasOne = array('LatestComment' => array('className' => 'Comment', 'order' => 'LatestComment.created DESC'));
But when I do a Post->find('all') it returns every Post multiple times, once per each Comment, with LatestComment set to the different Comments.
You can add 'limit' => 1 to your array of parameters to only return one comment.
Alternatively, instead of defining another relationship, you can simply limit the number of comments returned when you perform the find, using the Containable behaviour.
$this->Post->find('all',array(
'contain' => array(
'Comment' => array(
'order' => 'Comment.created DESC',
'limit' => 1
)
)
);
This is useful if you want to filter any related sets without defining relationships - by author, or within a date range, for example.
Make sure that you add the Containable behaviour to any model that you reference.
To remove the duplicates you want to use: GROUP BY in your find all query. I believe Cake has a 'group' => option as well.

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'
));

Resources