5

I'm trying to wrap my head around when to use a Model and when to use a Record (or both?).

I have both my Model and my Record. In my Controller I call my Service that creates my new Record instance before running the ->save(); command to save my new entry into the database.

Since my Record can run the validation process from my specified rules() array I'm wondering what I have my Model for?

Also since I'm running the actual database command from within my Service and dont have access to the Record in my Controller, how do I return the validation errors to my Controller for later use in my template variables?

Or do I run the validation on my Model in my Controller as well? Seems like I'll be doing the same thing twice if so.... or what am I not getting here? :P

12

Records and models are similar in that they are both containers for data, but records have the additional functionality/overhead of being able to write and read from the database via the Active Record pattern. There is also not a 1:1 relationship between records and models. A model can define attributes from multiple records.

In practice, records should never leave your service layer, because you don't want them floating up to the controller or template level, for example. If that happened, then people could inadvertently insert/update/overwrite data directly from a template.

From your service, you do all of your Active Record / database related logic, and call populateModel(s) with the resulting data and return the models from your service and let those safely transport your data throughout your layers.

The general pattern for all of this, including validation, looks like this:

class SomeController extends BaseController
{
    public function actionDoSomething()
    {
        $myModel = new MyModel();
        $myModel->myProperty = 1;

        // Validate the model
        if ($myModel->validate())
        {
            // Pass it to the service.
            if (craft()->myService->doSomething($myModel))
            {
                // Success!
                $this->redirectToPostedUrl();
            }
        }

        // Return the model back to the template so the errors can be displayed.
        craft()->urlManager->setRouteVariables(array(
            'model' => $myModel
        ));
    }
}

Your service can grab the info it needs from the model and create or grab any necessary records. It can then, if it needs to, call $record->validate() on any of the records. If a record fails validation in the service, it can add those errors to the original model and return false like:

$myModel->addErrors($record->getErrors);

And those errors will bubble up to the template to be displayed.

  • Rock solid answer. Now I know which direction to go. Thanks Brad. :) – naboovalley Oct 8 '14 at 5:20
  • 3
    This is the type of example that should be on the documentation page. Great question and great answer! – Jason McCallister Nov 1 '14 at 8:17
  • I agree, this is a great answer and is a good example of what should be in the documentation. I'm still curious about the defineAttributes() method. Both the model and record have it and with your method we are forced to repeat a lengthy method definition in both our model and record. Am I missing something? Any suggestions? – coffe4u Dec 4 '14 at 4:39
  • If they're identical, then I wouldn't consider a copy/paste lengthy. :) Repetitive, perhaps. But this way, you get the flexibility of having a model map to multiple records and custom model attribute validation that might go beyond the more simplistic needs of a record. – Brad Bell Dec 4 '14 at 4:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.