Fat models and how they change how you use the Model class

Published on and tagged with cakephp  model

Thinking about the model also forced me to think about the way I use and write models. This changed quite a bit over time.

At the beginning of my time with CakePHP I used to write “skinny” models, i.e. they only contained association definitions and some validation rules, and nothing else. Everything was done in the controller using the methods provided by the Model class.

In the meantime I follow the “skinny controller, fat model” principle, and so a lot of the logic I previously did in the controller is now done in the model. A side effect of this is that I tend to no longer use certain methods like Model::findAll() in my controllers. For example, if I had to write some functionality to retrieve the 10 most recent posts I would have written it in the past like:

$this->Post->findAll(array('Post.is_published' => true), null, array('Post.published DESC'), 10);

This works fine. But it is “difficult” to read as this snippet doesn’t communicate very well what I try to accomplish. The “problem” is that the “findAll” method is too generic.

By moving this snippet to its own model method we can write code that’s much easier to read and understand:

// in app/models/post.php
function findMostRecent($limit = 10) {
    return $this->findAll(array('Post.is_published' => true), null, array('Post.published DESC'), $limit);
}

// and here how we use it from our controller(s)
$this->Post->findMostRecent();

By writing such custom methods the way you use the functionality provided by the Model class changes. Many of its methods become helper methods you use from within the models, but outside the models they are no longer important.

I think it is a good idea — the next time you use one of the generic Model methods — to ask yourself: “Does this code communicate well what I try to accomplish?” And if the answer is “no”, to write a custom method.

23 comments baked

  • Dave Spurr

    Coming from a different MVC background I always found the fat controllers, that people using Cake (or Rails) preferable, quite strange. To me you get cleaner code, clearer encapsulation and greater reuse by moving as much as you can down to your models.

    So this was a great post read, to see that I’m not alone in thinking this.

  • Thomas

    Isnt that the normal way like it was intended to use a model? It create a more abstract way of handling model-related data, which makes the model more than just a connection to the database

  • Kalileo

    Fully agree, Daniel, I went the same route. In my early cake days, with “basic models and fat controllers”. Now the models are fat, the controllers are just skinny glue to get the model stuff to the views. As a side effect a lot of these new model methods could get refactored, or even better – generalized, and added to AppModel. Another sie effect was that now I do not need to use requestAction() anymore, because now the methods needed are available to any controller whose Model has an association to the Model needed, and these fat models are able to serve the data as it is needed, to any other associated Model’s controller.

    So it is one step more, first move the business logic to the model, but then generalize it and make it available to all Models as a custom AppModel method … and now it’s AppModel who’s getting fat.

    Still, overall it results in a significant overall reduction in code lines, and clear code.

    Kalileo

  • Yevgeny Tomenko

    One perfect feature of fat models that i see is how easy to write good and full tests for models.
    This main reason why i prefer fat models.

  • Dave Golding

    Another thing that I constantly ask myself is If I had to reinstall this application on a completely different server setup after a year or two, would I still be able to understand what’s happening? Maximizing the model class cuts down on fishing for needles in the haystack. You’ve made a great point here, one that I hope gets emphasized in more documentation.

  • henrique

    totally agree with thomas…

    “Isnt that the normal way like it was intended to use a model?”

  • Jelle

    How would you guys handle save action?

    Lets say a certain model can have a certain number of states. Draft, Ready, Sent out, Archived.

    Each state has multiple data fields.

    Draft state has the fields title and description
    Ready as a date and user_id for example
    Sent out has a receiver_email
    and archived has a user_id who archived the thing.

    So each state needs to do a model save to add the field data + set the state field +1.

    Would you add all these things to the data array from the controller and call the Model’s save() method? Or would you create a special method for example saveDraft(), saveReady() and append the fields in those methods?

    The same question can be applied to saving child models in 1 action. For example you have a Article model that has multiple Picture models in the data array that need to be saved. Would you save the Article in the controller and then save the associated pictures in the controller or would you create a special method in the article model and do the save logic from within the model?

  • leveille

    I am in the throws of my first cake application and I’ve come to realize that have a fat controller just doesn’t feel right. I can only speak for myself of course, but part of the issue for me was that I started the project by trying to mimic the controllers that cake had baked for me. As we all know, cake bakes fat”ter” controllers and skinnier models. It’s only natural for someone relatively new to MVC, and totally new to cake, to believe that this is the approach to take.

    With some time and a little experience I think most people will tend to agree with your assessment.

  • Abba Bryant

    @ Jelle

    The answer to your first question would be to create a save function that switched its logic on a passed ‘state’ parameter.

    An example call.. $this->Model->saveState( ‘draft’ ); or $this->Model->saveState( ‘Ready’ );

    The second answer is similiar. If you can guarantee the Article model hasOne / hasMany pictures then there is no reason to not have an Article->attachPicture( … ) method. A similiar Article->attachPictures( … ) method could simply loop over the data calling the Article->attachPicture method repeatedly.

    Another good point to address in the fat models discussion is the use of callbacks. An example : The User->afterSave( ) callback to automatically create a Profile with the correct association key and default info. Then you don’t even need a ProfilesController->add method since the one profile you would create per user is handled by the parent model.

    There are so many good ways to reduce controller code. Someone should write a comprehensive ‘cookbook’ about the cake model functionality.

  • dr. Hannibal Lecter

    Excellent post!

    Something like this should be put into the official Cake manual..

    Cheers,
    dr. H.

  • Howard

    Heuristics / Rules of Thumb like this are always welcome reminders – nice post

  • Fran Iglesias

    Hi,

    sorry if I can not explain better my question…

    I would like to know how do you accomplish paginated results with the fat models approach. I mean, for example, a post model method in a CMS to retrieve all published posts

    Post->findPublished($page)

    without using Controller::paginate(). I consider that Controller::paginate() breaks the fat models approach as it requires set the conditions for the query in the action code, not in the model.

    Thanks

  • Anonymous

    Fran Iglesias,

    Your controller:pageinate can be in either the model or the controller. You should do what you think is best. I’m a fan of a fatter model. Anything that helps the model become more portable is a-o-k with me to be put in the model. Paging really isn’t very model specific and you can really generalize a paging, so the controller (in my view) should be the place to put paging methods. The beauty of it all is you don’t *have* to put everything in the model or the controller. You do what you think is best for the project. Put some thought into each method and where it would best suit the final product. Also, the most important question you could ask yourself about your project, is future phases and how will this impact future modifications.
    Good luck

  • Fran Iglesias

    Thanks Anonymous, I see our point and it clarifies me a bit.

    My quest is to find a way to separate “pagination logic” (an put it in the controller) from “bussines logic” (an put it in the model), and pass necesary data to limit the model results.

    In my current project model’s find methos have to deal with complex conditions based on user profile, object status, dates… So model encapsulation makes sense.

  • cakebaker

    @all: Thanks for your comments!

    @Thomas, henrique: If you look at the design of the model class I doubt it was intended to be used in that way, at least it doesn’t force you to write fat models.

    @Kalileo: Yes, refactoring is always a good thing to do. When I refactor stuff I usually end up with some private methods, and the AppModels stay rather skinny. But this is probably application-specific.

    @Yevgeny: Yes, that’s true, model code is much easier to test than controller code.

    @Dave: Yes, that’s something you always have to ask yourself when writing code (not only when writing models).

    @Jelle: To me your scenario sounds like it would make sense to have special methods like saveAsDraft(), saveAsSent(), etc. But it could also make sense to create models for your states, it depends on your requirements.

    @Abba: Hm, I wouldn’t create the Profile in the afterSave() callback function. I would rather create it at the place where the User is saved, so that you see those two “actions” are connected.

  • Tim Daldini

    I tried to do this but sometimes it creates “dependencies”. Suppose you need to call your findMostRecent 4 times in your application, possibly in different controllers.

    Later you notice you need to change 2 of those calls. One should use an extra condition, and another should fetch different fields…what do you do in such case? You could add extra optional field and condition arguments but in the end I think it would make things possibly harder to read because you might customize several of model methods in different ways as you develop your application…and you sometimes need to remember why and how you changed things.

    Because it is sometimes hard to foresee how your application will change (and also, I hate switching between multiple files…) I mostly don’t use this technique. I personally think using standard model methods are very readable once you are familiar with them.

    But I guess that’s just me…

  • Abba Bryant

    Yeah the profile is a bad example. A better example is a node based cms – when creating a parent node there may be required child nodes of different types. These might depend on data in the submitted data array. In this case a bit of logic in the afterSave callback ( which ideally should then delegate to private function calls ) to associate the correct nodes with different types of parent nodes.

    Bleh, my original point is that amidst all of this Fat Models discussion there has been very little posted on the usage of the controller / model callbacks system outside of the few behavior authors.

  • Stephen Orr

    I’m having problems with this approach, and I’d like to know if it’s just me that’s doing something wrong.

    I have an ecommerce site built using Cake, and it’s gotten to the point where I’m fine-tuning the admin backend, which I’ve built as a plugin.

    I’m trying to fatten up my models now to remove some of the complexity from my controllers – but I’m unable to call any of the functions I’m writing – the call just gets passed directly through to MySQL and so fails.

    As an example, I’m trying to do:
    $this->Product->findMostPopular();

    Instead of calling the Product::findMostPopular() method, it’s directly executing findMostPopular as SQL.

    Could this be because I’m referring to main app models from my plugin?

  • Stephen Orr

    As a follow-up to my last comment, it looks like I was correct. Referring to calling functions within main app models from a plugin – it doesn’t work.

    I just proved this by creating a controller in my main application which calls the function. This works, instead of passing through to the SQL.

  • cakebaker

    @Tim: Good point. Adding optional parameters to the method is one possible solution. Another solution could be to write separate methods for the special cases. But as so often in software development there is no best solution…

    Yes, it is difficult to foresee how an application will change over time. So you always have to write code that’s clean and easy to change (I know, it is easier said than done, and unfortunately you often learn only at the time when you have to make a change that your solution wasn’t that good…).

    @Abba: Ah, yes, that’s a better example. And it’s true that there is not much talk about callbacks. I guess it is because they aren’t used often. If I look at my own code, the only callback I often use is Controller::beforeFilter(), I almost never use the other callbacks.

    @Stephen: Strange, sounds like a bug to me. Maybe it helps as a workaround to manually instantiate your model like $this>Product = new Product(); ?

  • David Golding Design Blog -

    […] took Daniel Hofstetter’s advice to look for opportunities to put functions in the model rather than in the controller. Not only did […]

  • Abba Bryant

    My general rule for whether or not to place the logic in a callback is whether or not I am working with models referenced through ( or creating on the fly ) relationships in the current model.

    If the answer is no, or it is such that I need to make use of a model loaded through the Controller’s uses array then I place the logic in the controller. The cutoff point for me is that if I ever would need to App::import a model into another model then I should be moving the logic to a controller.

    @Tim Daldini I generally write my custom model functions to accept the same options array as the internal function I would be using anyways ( the array passed to find, read, etc ) and simply set the defaults I generally need in the function declaration. Then you can simply call it for the default common needs, or pass an options array in. Depending on your needs sometimes processing the options is more code than fetching, tweaking and returning the data but it does leave things flexible.

  • CakePHP コントローラに処理を書かずにモデルにメソッドを追加しよう! | Sun Limited Mt.

    […] CakePHP だけではなくフレームワーク全般に当てはまることだと思います。 Fat models and how they change how you use the Model class – cakebaker […]

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License