views:

48

answers:

3

Hello. I want to receive model data by find(all), but the user should get only a restricted set of table fields. That's easy:

$ret = $this->find('all',array('fields'=>array(
    'Employee.id','Employee.address_id'
)));

But this model (Employees model) also has a belongsTo association:

var $belongsTo = array(
    'Address' => array(
        'className' => 'Address',
        'foreignKey' => 'address_id',
        'fields' => array('Address.full_name')
    )
);

I want the Address.full_name field to appear in my fetched data too. But it doesn't work with the find() call above, and it throws an error (SQL Error: 1054: Unknown column 'Address.full_name' in 'field list') when trying this:

'fields'=>array('Employee.id','Employee.address_id','Address.full_name')

Anyone knows how to solve this?

EDIT: I totally forgot that Address.full_name is a virtual field. Looking at the Cakephp-produced SQL, it's obvious why it doesn't work:

SELECT
    `Employee`.`id`, `Employee`.`address_id`, `Address`.`full_name`
FROM
    `employees` AS `Employee`
    LEFT JOIN `addresses` AS `Address`
        ON (`Employee`.`address_id` = `Address`.`id`)
WHERE 1 = 1

In the address model, full_name is defined like this:

var $virtualFields = array(
    'full_name' => 'CONCAT_WS(" ", Address.firstname, Address.surname)'
);

So then, the question is: Is it a CakePHP bug that it's not able to include (foreign model's) virtual fields within a fieldlist supplied to find()?

A: 

I would use the Containable behavior in this case.

Make sure you have the Containable behavior loaded in your Employee model first:

var $actsAs = array('Containable');

Then, when you're trying to get your data, do it like this:

$params = array('fields' => array('Employee.id', 'Employee.address_id'), 
    'contain' => array('Address' => array('fields' => array('Address.full_name')));
$ret = $this->find('all', $params);

More on the containable behavior here: http://book.cakephp.org/view/1323/Containable

mtnorthrop
I've used the containable behavior before until I realized that it's unnecessary, because I need the contained data in every controller action. Instead of calling the containable behavior every time before a find() or paginate() call, I now do it th "classical" way by just setting the relations in the model statically and setting model->recursive = 0.So, in conclusion, it should work without the containable behavior too. Additionally, I've already had the same issue before with the containable behavior, so combining containable and find('all',array('fields'=>..)) does'nt work, too.
joni
Did you have recursive set to -1 before? This would explain your problem, as Cake's ORM doesn't perform any JOINs at that mode, and thus would not parse the belongsTo association while building the datasource query.
Daniel Wright
Regrettably, this is not the problem, as var `$recursive = 1;` is already set in my model.
joni
You may want to step through your code, looking for `Employee.recursive` getting set to `-1`, because I tried reproducing your issue, and the *only* way I could repro the error was to set `Employee.recursive = -1`. Any other setting caused things to work as expected.
Daniel Wright
A: 

SQL Error: 1054: Unknown column 'Address.full_name' in 'field list')

This error gives you a clue that something is amiss with either your column name call (could it be fullname rather than full_name) or, more likely your Model definition. Employee belongsTo an Address but does the Address haveOne or haveMany Employees?

Leo
`Address.full_name` is the right field name. When I omit it in the field list, there's no SQL error, but the related Address data does not get fetched. Are you sure that it's necessary that there is a haveMany/One in the referenced model (Address in this case)? Because, without the `fields` parameter, find('all) fetches the related data without a problem..
joni
+1  A: 

Unfortunately, you cannot use virtual fields the way you wish to. From Limitations of Virtual Fields in the Cake documentation:

The implementation of virtualFields in 1.3 has a few limitations. First you cannot use virtualFields on associated models for conditions, order, or fields arrays. Doing so will generally result in an SQL error as the fields are not replaced by the ORM. This is because it difficult to estimate the depth at which an associated model might be found.

It looks like you'll have to use the Containable behaviour.

Daniel Wright
At the risk of editorialising, I find this sort of thing frustratingly common with Cake. They'll implement what seems like a pretty neat feature, but then put severe limitations on their use, to the point where any application of a given complexity cannot employ them.
Daniel Wright
I share your opinion here... As it looks, I don't have another choice than using the containable behavior "statically". Any idea how to do this? If possible, I don't want to call contain() every time by hand, but as I tried, contain() in the beforeFInd() callback func doesn't work..
joni
I'll try to use the workaround described in the cookbook here http://book.cakephp.org/view/1608/Virtual-fields#Limitations-of-virtualFields-1642
joni
Why ever, it works when I omit the Address.full_name in the belongsTo fields declaration and add the virtual field on-the-run by adding this to the Employee model beforeFind callback func: ` $this->virtualFields['full_name'] = $this->Address->virtualFields['full_name']; `
joni