Working with models

Since there is so much to talk about it here, I will cut right to the chase.

We all know about "Fat Models, Slim Controllers". So basically, as much as possible of the code should be in the model. For a good reason: If you need that code in another controller, you can now easily access it. Having it inside a controller does not give you this option. Thats another principle: "Don’t repeat yourself".

What if I use the same model in different contexts?

We sometimes need the "User" (class name!) Model to be more than just that. Imagine a forum with posts. We could have "User", "LastUser", "FirstUser" etc. For all those we have custom belongsTo relations in our models.

If we had something like this in our code, we could get in trouble:

function beforeValidate() {
    if (isset($this->data['User']['title'])) {
        $this->data['User']['title'] = ucfirst($this->data['User']['title']);	
    }
    return true;
}

So always use $this->alias:

function beforeValidate() {
    parent::beforeValidate();
    if (isset($this->data[$this->alias]['title'])) {
        $this->data[$this->alias]['title'] = ucfirst($this->data[$this->alias]['title']);
    }
    return true;
}

Now we can set up any additional relation without breaking anything:

var $belongsTo = array(
    'LastUser' => array(
        'className' 	=> 'User',
        'foreignKey'	=> 'last_user_id',
        'fields' => array('id', 'username')
    )
);

$this->alias is now "LastUser" for this relation.

Always try to outsource code, that repeats itself, into behaviors or the AppModel callbacks.
So if the "ucfirst()" code snippet from above is used in pretty much all models, you can either use beforeValidate() of the AppModel:

function beforeValidate() {
    parent::beforeValidate();
    if (isset($this->data[$this->alias][$this->displayField])) {
        $this->data[$this->alias][$this->displayField] = ucfirst($this->data[$this->alias][$this->displayField]);
    }
    return true;
}

or you can create a behavior:

class DoFancyBehavior extends ModelBehavior {

    function setup(Model $Model, $config = array()) {
        $this->config[$Model->alias] = $this->default;
        $this->config[$Model->alias] = array_merge($this->config[$Model->alias], $config);
        // [...]
        $this->Model = $Model;
    }

    function beforeValidate(Model $Model) {
        $this->_doFancyUcfirst($Model->data);
        return true;
    }
    
    function _doFancyUcfirst(&$data) {				
        if (isset($data[$this->Model->alias][$this->Model->displayField])) { 
            $data[$this->Model->alias][$this->Model->displayField] = ucfirst($data[$this->Model->alias][$this->Model->displayField]);
        }
    }

}

With the behavior you can define which models will and which won’t "behave" fancy.
But of course the same would be possible for the AppModel method using an internal variable ($this->behaveFancy: true/false) and checking on it in our callback.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.