Cake HABTM Query, Order By Rand() - cakephp

I'm aware that Cake HABTM associations are tricky at the best of times, but I seem to be making life even harder for myself...
If I want to return a random Item from the db, I can do it as follows in the Item model:
$random = $this->find('first', array(
'order' => 'rand()'
));
and if I want to find all the Items that are in a certain Category (where Item has a HABTM relationship to Categories), I know I can get a result set through $this->Categories->find.
My question is: how can I combine the two, so I can return a random Item that belongs to a specified Category? Is there any easy way? (If not, I'll gladly take any suggestions for a laborious way, as long as it works ;)
ETA: I can get some of the way with Containable, maybe: say I add the line
'contain' => array('Categories'=>array('conditions'=>array('Categories.id'=>1))),
then the Item results that I don't want come back with an empty Categories array, to distinguish them from "good" items. But really I don't want said Item results to be returned at all...
ETA(2): I can get a workaround going by unsetting my results in the afterFind if the Categories array is empty (thanks to http://nuts-and-bolts-of-cakephp.com/2008/08/06/filtering-results-returned-by-containable-behavior/ for the tip), and then having my random find function not give up until it gets a result:
while (!is_array($item)) {
$item = $this->random($cat);
}
but ugh, could this be any clunkier? Anyway, time for me to stop editing my question, and to go away and sleep on it instead!

Try this:
<?php
$this->Item->bindModel(array('hasOne' => array('ItemsCategory')));
$random = $this->Item->find('all', array(
'order' => 'rand()',
'conditions' => array('ItemsCategory.category_id' => '1')
));
?>

Related

Ways to use array in cakephp

Hello I am having a tought time figuring out how to use arrays in cakephp. right now i have a view with 2 columns, active and startYear. i need to grab the start years for all of the columns in the view and sho i have this code.
public function initialize(array $config)
{
$this->setTable('odb.SchoolYear');
}
controller
public function index()
{
$deleteTable = $this->loadModel('DeletedTranscripts');
$this->$deleteTable->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
));
$this->set('startYear',$deleteTable );
}
once i have the array captured and put into lets say startYear can in input a statement like this into my dropdown list to populate it?
<div class="dropdown-menu">
<a class="dropdown-item" href="#"><?= $delete->startYear; ?></a>
</div>
i have been looking for answers for quite awhile any help would be awesome.
Couple of things:
Loading Tables in CakePHP
For this line:
$deleteTable = $this->loadModel('DeletedTranscripts');
While you can get a table this way, there's really no reason to set the return of loadModel to a variable. This function sets a property of the same name on the Controller, which almost correctly used on the next line. Just use:
$this->loadModel('DeletedTranscripts');
Then you can start referencing this Table with:
$this->DeletedTranscripts
Additionally, if you're in say the DeletedTranscriptsController - the corresponding Table is loaded for you automatically, this call might be unnecessary entirely.
Getting Query Results
Next, you're close on the query part, you've can start to build a new Query with:
$this->DeletedTranscripts->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
));
But note that the find() function does not immediately return results - it's just building a query. You can continue to modify this query with additional functions (like ->where() or ->contain()).
To get results from a query you need to call something like toArray() to get all results or first() to get a single one, like so:
$deletedTranscriptsList = $this->DeletedTranscripts->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
))->toArray();
Sending data to the view
Now that you've got the list, set that so it's available in your view as an array:
$this->set('startYear', $deletedTranscriptsList );
See also:
Using Finders to Load Data
Setting View Variables
I also noticed you've had a few other related questions recently - CakePHP's docs are really good overall, it does cover these systems pretty well. I'd encourage you to read up as much as possible on Controller's & View's.
I'd also maybe suggest running through the CMS Tutorial if you've not done so already, the section covering Controllers might help explain a number of CakePHP concepts related here & has some great working examples.
Hope this helps!

cakephp sum() on single field

I have searched high and low on how to just get a total of a field called points. I just need one total figure but the best I can get is a list of records from the Points table with the associated records from the Members.
$totalPoints = $this->Member->Point->find('all', array(
array('fields' => array('sum(Point.points) AS Point.ctotal'))));
Why not using virtualFields as documented and suggested by the docs?
http://book.cakephp.org/2.0/en/models/virtual-fields.html
$this->Member->Point->virtualFields['total'] = 'SUM(Point.points)';
$totalPoints = $this->Member->Point->find('all', array('fields' => array('total')));
This is way cleaner.
Also note the double array you got there in your $options array (...find('all', array(array(...). And how I used only a single/flat array.
This is the reason why your SUM() call as fields does not work.
mark's answer above is right. I just want to add that you can do this:
$totalPoints = $this->Member->Point->find('first', array(
array('fields' => array('sum(Point.points) AS Point__ctotal'))));
$totalPoints will be have this:
$totalPoints['Point']['ctotal']
Another clean way without outputting an array
$this->Member->Point->virtualFields = array('total' => 'SUM(Point.points)');
$total = $this->Member->Point->field('total');

Cakephp pagination with random order?

Ok, I have looked and looked but cannot seem to find anything on this anywhere. I have a display of results that are paginated beautifully, but they currently display in ascending order. I'd like for them to display in random order. Here is my current controller code:
public function condos() {
$this->paginate['Unit']=array(
'limit'=>9,
'contain'=>array(
'User'=>array(
'id', 'user_name', 'area_code', 'exchange', 'sln', 'email'),
'Complex'=>array('id','complex_name', 'realname', 'photo1','photo2','photo3','photo4','photo5', 'complex_website')
),
'conditions'=>array(
'Unit.type'=>array('condo', 'rentalco'),
'Unit.active'=>1)
);
$data = $this->paginate('Unit');
$this->set('allcondos', $data);
}
For anyone else finding this - the actual answer is to generate a seed (a float between 0 and 1), and save it to the session before the RAND() sort is necessary (in the controller's beforeFilter()). Then:
$this->paginate['order'] = sprintf('RAND(%f), $this->Session->read('seed'));
This preserves the RAND() seed between calls to the paginator, and preserves the overall order of the results between requests.
This seems to work pretty well on CakePHP 2 for me:
$this->paginate = array(
'order' => 'RAND()'
);
This is using the MySQL RAND() function, which Cake just passes on to the database.
EDIT: Now that I think about it, this is not a good idea, because the order is not maintained between pages. I can't think of a good way off the top of my head to randomize the order and maintain continuity between pages. Maybe if you were to shuffle the items on the page with JavaScript?

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

Select from a table where associated table fieldname = 'x' in Cake PHP

In CakePHP, I have two tables, Countries & Networks. They have a HABTM relationship and are joined by countries_networks.
I'm trying to get all countries from the countries table where the 'name' field in Networks = 'o2'
I've realised I can't do this using a basic find(), so I've been experimenting with the containable behaviour. I have managed to restrict the returned data, but it looks as though 'containable' doesn't exactly work as I want. Heres my code:
$countries = $this->Country->find('all', array('contain' => array(
'Network' => array(
'conditions' => array('Network.name =' => "o2"),
)
)));
This query however returns ALL countries, and the Network.name if its 'o2'. What I really need to do is return ONLY the countries that have a Network.name of 'o2', and no others.
Can anyone help? Thanks.
"=' =>"
What is it? there is no needed to use "=" symbol after "Network.name"
Your query returns you exactly what your ask.
Try to select from Network.
$countries = $this->Country->Network->find('first', array(
'conditions' => array('Network.name' => "o2"),
'contain' => array('Country')
));
$countries = $countries['Country'];
You should be able to do something like this:
$this->Country->hasAndBelongsToMany['Network']['conditions'] = array('Network.name'=>'o2');
$myCountries = $this->Country->find('all');
I haven't tested this, but it should get you pretty close to where you need to be.
Also, bear in mind that changing the hasAndBelongsToMany array like this will affect all subsequent queries against that model during the current page load (but it will be reset the next time the model is instantiated).
I'm on my way out the door, so sorry for the brief explanation.
I think what you want is a HABTM relationship, but to be able to filter based on the associated model's data. To do this, check out the "Containable" behavior in Cake's manual. Pretty sure that's what you're after.

Resources