1

I'm currently attaching a virtual field in beforeFind() because I need to insert the current user's id into the query. Calling find on a related model and containing this model includes statically defined virtual fields but not the one in beforeFind().. Calling find directly on the model includes the dynamically attached virtual field.

Here is my beforeFind callback:

public function beforeFind($query = array()) {
    $user_id = $this->getCurrentUser()['id'];
    $this->virtualFields = array_merge($this->virtualFields, array( 
        'cost_for_user' => sprintf('CASE WHEN Inventory.user_id = %s THEN Inventory.cost ELSE Inventory.cost_for_team END', $user_id),
    ));
    return $query;
}

Since cost_for_user is dynamically attached in beforeFind I can't copy the virtual fields over at runtime with like suggested in the cookbook. Is there a better callback for dynamically attaching virtual fields so there are included in contain results?

Devin Crossman
  • 7,454
  • 11
  • 64
  • 102

1 Answers1

2

If it's just about the inclusion of virtual fields in the find results, then you could for example use a separate method that initializes them, and call this method from within the currently queried models beforeFind() callback, something like:

public function beforeFind($query = array())
{
    if(!parent::beforeFind($query))
    {
        return false;
    }

    $this->RelatedModel->setupVirtualFields();
    return true;
}
public function setupVirtualFields()
{
    $user_id = $this->getCurrentUser()['id'];
    $this->virtualFields = array_merge($this->virtualFields, array
    ( 
        'cost_for_user' => sprintf('CASE WHEN Inventory.user_id = %s THEN Inventory.cost ELSE Inventory.cost_for_team END', $user_id),
    ));
}

You could also make this a little more generic by applying it to all models and associations, for example in the AppModel::beforeFind() callback or using a behavior:

public function beforeFind($query)
{
    if(!parent::beforeFind($query))
    {
        return false;
    }

    $this->_setupVirtualFields($this);
    foreach(array_keys($this->getAssociated()) as $modelName)
    {
        $this->_setupVirtualFields($this->{$modelName});
    }

    return true;
}

protected function _setupVirtualFields(Model $model)
{
    $method = 'setupVirtualFields';
    if(method_exists($model, $method) && is_callable(array($model, $method)))
    {
        $model->setupVirtualFields();
    }
}
ndm
  • 59,784
  • 9
  • 71
  • 110
  • This worked perfectly thanks.. Couple questions though. Shouldn't beforeFind() return the $query and what's the point of checking parent::beforeFind($query) from AppModel? – Devin Crossman Oct 23 '13 at 16:44
  • @DevinCrossman You're welcome. `Model::beforeFind()` needs to return `$query` only in case `$query` was modified and these modifications should actually be used, returning `true` means to continue with the original query data. Invoking `parent::beforeFind()` should in most cases not be necessary on `AppModel`, assuming that it inherits _directly_ from `Model`. However personally I'm always invoking the parent to make sure everything continues to work even when parent implementations change (whether it's directly in the parent method or indirectly by extending different classes). – ndm Oct 23 '13 at 18:53