views:

38

answers:

2

Hello.

My Problem: Linked to my Employees table I've got an Address table containing a virtual field called full_name (I guess you can imagine by yourself what it does). I added the Containable Behaviour and this function

function beforeFind() {
    $this->contain('Address.full_name');
}

to my Employees model, so that I don't have to call $this->contain(..) in every single controller action (I need the full_name field in pretty every action). BUT id doesn't work then if the controller action does just a $this->Employee->find('all') (or read(..). Contrary, it works if

  • The controller action uses $this->paginate(); instead
  • $this->Employee->contain('Address.full_name'); gets called before the $this->Employee->find('all'); call. I can't imagine the cause for this because after this explicit contain(..) call, contain(..) gets called again by the Model callback function beforeFind(), as a debug proofed which I inserted into the cake/libs/models/behaviours/containable.php:contain() function *cough*.

I don't know what to do now, anyone else knows?

A: 

1) Your Model beforeFind() should accept a param $queryData and return true if the find() should be executed, or false if it should abort beforeFind. Generally the beforeFind($queryData) method will modify the $queryData array and return it.

function beforeFind($queryData) {
   // Modify $queryData here if you want
   return $queryData
}

2) Trying to maintain a persistant $contain is a bit strange. Containable obviously contains assocations so that extra/additional information is fetched. Your virtual field should be returned in a normal find() operation. If you want to restrict the fields which are returned you should define those either in the Model or in the Model association

var $belongsTo = array(
    'Employee' => array(
         'className' => 'Employee',
         'fields' => array('Employee.id', 'Employee.full_name'),
         'conditions' => array()
    )
);

Containable will rebind associations quickly for you, but you should instead define default fields/conditions through the Model association ($belongsTo, $hasMany, $hasOne etc)

Another alternative is to actually create Model methods which reflect the data you are trying to fetch, a very basic example:

function activeEmployees() {
     $contain = array('Address');
     $conditions array('Employee.started' => '2010-09-01');
     $this->find('all', array('conditions' => $conditions, 'contain' => $contain));
}

And then call these convienience methods from the Controller just like you would a find

$this->Employee->activeEmployees();

You could also choose to pass a param to Employee::activeEmployees(); which is an array of additional $conditions or $contain options which are merged with the standard options.

Martz
+1  A: 

As far as I recall, a contain() statement only works once, for the query operation immediately following it. Subsequent queries will require their own contain() statement e.g.

$this->Employee->contain('Address.full_name');
$this->Employee->find('all'); //first find
// returns all of Employee + Address.full_name

$this->Employee->find('all'); //second find
// returns all of Employee + all of Address + all of any other associated tables.

I don't recommend using contain() in beforeFind() as it is intended to modify specific returns. It soon becomes second nature to use it before each query where you will then have fine control of the data returned.

If you have a widespread requirement for a limited return, you can set that up in the associations on the model.

Leo
Thanks. I see now that my approach to use contain() in the beforeFInd callback function was , erm, a strage idea.
joni