Specifying order parameter through a belongsTo table in CakePHP - cakephp

Let's say I've got two tables: cities and countries. The City model belongsTo a Country. Now, when I display lists of cities, I want them to be ordered by country name and then city name.
I tried this in my City model class:
var $order = array('Country.name' => 'asc', 'City.name' => 'asc');
The sort order works correctly for my index page, but I get errors in several places where the model has been asked not to load associated models. Do I have to change the order parameters when I change which tables are loaded, or is there a smarter way to do this?
For now, I think I'll make the default order definition be City.name and then just change it to Country.name and City.name on the index page. That way it's safe by default, and I shouldn't get unexpected errors.
Update: As Rob suggested you can specify order parameters in the model associations. The query builder applies them like this:
If there is an order field on the main model, apply it first.
Walk through all the associated models in the order they appear. If the association includes an order parameter, add it to the end of the list.
To implement my example, I would do it like this:
class City extends AppModel {
var $belongsTo = array(
'Country' => array(
'order' => array('Country.name' => 'asc', 'City.name' => 'asc')));
}
One caution: if you turn off City.recursive, then the cities will be unsorted. However, I'm usually retrieving only one record when I've turned off recursion.

Have you tried defining the order directly in your association? I've never needed to do this, so I can't swear it will do what you're after, but:
class City extends AppModel {
$belongsTo = array(
'Country' => array(
'Order' => 'Country.name'
)
);
}
You could do something similar for your Country model, of course.

this is untested, but the idea is
in the country model you can do
hasMany = array('City');
function beforeFind()
{
foreach($this->hasMany as $model)
{
$this->order[] = $model['className'].'.'.'ASC';
}
}
not worth doing IMO. ur way is fine.

Related

How to sort data of bind table in cakephp 2.0

I need the data from various table. I bind them by using cakephp's $hasMany variable. The data is fetched successfully. But I need to sort the result coming from $hasMany table.
for eg. I have two tables
Survey
Questions
Now Survey table contains the data related to Survey like title, id, purpose and Questions table contains question for related survey. I bind questions table with survey in survey model. Now I have a field in Questions table with name ordering. I need to fetch data in that order.
How can I fetch it in that way?
Please help me.
If you want to sort the data directly when it is fetched from db
You can define default order when adding relations between tables in your models. In your Survey model:
var $hasMany = array(
'Question' => array(
'order' => 'ordering DESC'
)
);
See http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasmany.
You can also define custom ordering when retrieving data from your controller in your conditional array, http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#find:
$conditions = array(
'order' => array('Question.ordering DESC')
);
If you are paginating your result with the paginator component, you can setup it like in following example book.cakephp.org/2.0/en/core-libraries/components/pagination.html#query-setup:
public $paginate = array(
'order' => array(
'Question.ordering' => 'desc'
)
);
If you want to sort the data presented in the view
Here you can use the pagination helper (together with the pagination component in the controller) as:
echo $this->Paginator->sort('Question.ordering');
see book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#creating-sort-links
Sorry for removing 'http://' on the two last links, but I am not allowed to post more than two links (<10 rep).
In CakePHP you can sort by associated tables. but you need to sort by column, you should use something like this in your view:
<?php echo $this->Paginator->sort('Question.ordering'); ?>

Can a virtualfield be based upon linked data in cakephp?

Is it possible for a virtualFields var to be the sum of a field from a linked table?
For example, in, say, an Invoice model, could you have
public $virtualFields = array(
'invoiceNett' => 'SUM(InvoiceLine.nett)'
);
but obviously only SUMming the lines that belong to that invoice?
Thanks.
== Using CakePHP 2.0
You could use the afterFind callback to get the sum. This avoids storing the calculated values​​, which should be avoided when possible.
function afterFind($results)
{
foreach($results as &$result)
{
/*
Use something like:
$this->InvoiceLine->find('all', array('fields' => array('SUM(InvoiceLine.nett) as total'),
'conditions' => array('invoice_id' => $result['Invoice']['id'])));
*/
}
unset($result);
}
As far as I know, the best way to do that would be to have an actual total field, and update it anytime the data is saved (likely with a afterSave callback method).
So - anytime an InvoiceLine is saved, you run some code to update it's associated Invoice with a new total.
//InvoiceLine model
public function beforeSave() {
//code to update Invoice's "total" field
}
Theoretically, yes, if that linked table is an associate that is joined (belongsTo and hasOne).
However this would be a poor idea because if you decide not to include that table you would generate a SQL error.
You'd be better off having a separate function grab the data or creating a virtual field that was a nested SQL query.
Defining your virtual field in the respective Model would make more sense.
If not done so, you will be breaking the MVC pattern.
You can use that virtual field from other related models.
If you don't want using them in all related models, you can always use the field
attribute whilst defining relations.
public $hasMany = array(
'IwantVirtualField' => array(
'className' => 'MyModel',
...
)
);
In a model where you don't want virtual field
public $belongsTo = array(
'IwantVirtualField' => array(
'className' => 'MyModel1',
'fields' => array('MyModel1.id', 'MyModel1.name')
...
)
);

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 multiple select validation

I am using CakePHP and i have something like:
PRODUCT -------> PRODUCT_CATEGORY <---------- CATEGORY
so one product can have 'n' categories and viceversa.
The problem is that i would like to validate the products so that the have at least one category. Since I am using the Form assistant and the validate functions of CakePHP y have arrived to this:
class Product extends AppModel {
var $name = 'Product';
var $validate = array(
'category_id' => array(
'rule' => array('multiple', array('min' => 1)),
'message' => 'You have to choose at least one category'
)
);
}
But it doesn't work, any ideas?
I think you should not validate against category_id, instead use Category (the name of your model).
If this still doesn't work, you should be ablo to find a solution in this question on SO: HABTM form validation in CakePHP
or have a look on this article:
http://nuts-and-bolts-of-cakephp.com/2008/10/16/how-to-validate-habtm-data/
have you tried the NOTEMPTY rule? im assuming the list of categories is in a checkbox format, rite.. by default the category_id if empty. the only logic i can think is, if nothing checked, then it throw the error message.
correct me if im wrong.. :)

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