A little tip about counterScope

One of many cool CakePHP’s features is the Counter Cache.

From the manual: “This function helps you cache the count of related data. Instead of counting the records manually via find(‘count’), the model itself tracks any addition/deleting towards the associated $hasMany model and increases/decreases a dedicated integer field within the parent model table.”

So if you have Forum with Posts, you can keep track of how many posts are there in a given forum by tracking this data in the “forums” table, which certainly speeds things up and takes a little load off your DB.

One feature that is not (yet) very well documented is the ability to provide a counterScope. It essentially allows you to specify a simple condition, which tells the model when to update (or when not to, depending on how you look at it) the counter value.

Using our Post model example, we can specify it like so:

class Post extends AppModel {
        var $name = 'Post';
        var $belongsTo = array('Forum'=>array(
                                              'counterCache'=>true,
                                              'counterScope'=>array('Post.deleted'=>0)));

    }

This means that counter will only be updated for posts that are active: ‘deleted’=>0.

Also, it is implied in this example that you have some admin tool, which lets you do soft deletes of your posts (i.e. set the “deleted” field to 1)…

Something simple, like this:

function delete($id = null) {
   if($id) {
        $this->Post->id = $id;
        $this->Post->saveField('deleted', 1);
   }
}

Guess what? Cake is going to update your counter field in the forums table, when you deactivate a post in this manner.

Brilliant stuff, I tell ya…

m4s0n501
  • http://carads.chsmarket.com TG

    This is nice, didn’t know about that. So essentially this would update Post_count, in the Forums table so you can just simply get the total number of posts from one field.

  • http://teknoid.wordpress.com teknoid

    @TG

    Exactly. No need to query multiple tables.

  • Joel Perras

    Hey, nice post. You know what would be awesome? Adding this info to the book ;-). If you don’t, the Easter Bunny wont bring you any chocolates. EVAR.

  • http://teknoid.wordpress.com teknoid

    @Joel Perras

    I can’t let that happen can I?
    The additional info has been submitted to the book ;)

  • http://goldenstatefruit.com n8manAfter

    The name of the field is lowercase and underscored, just like the convention for the model filenames.

    Example-
    Relationship: CartItem belongsTo Cart
    Updated column: `Cart`.`cart_item_count`

  • http://www.mysiteonline.org/ Brendon Kozlowski

    …now if I could just get CounterCache to work on self-joined tables I’d be in exquisite harmony. Good tip though, teknoid. I wasn’t *entirely* sure what counterScope did – as you said, it wasn’t documented very well (and I couldn’t figure it out from the code; though to be honest I wasn’t looking very hard – I didn’t need it at the time). Thanks for documenting that!

  • http://teknoid.wordpress.com teknoid

    @n8manAfter

    Indeed, I thought that was in the manual…

    @Brendon Kozlowski

    No problemo ;)

  • http://goldenstatefruit.com n8manAfter

    @teknoid

    My post should have been @ TG. It was mostly a correction but also a clarification.

  • http://goldenstatefruit.com n8manAfter

    Thanks for the info btw. I had no idea this existed. You teach me new things with practically every post.

  • http://teknoid.wordpress.com teknoid

    @n8manAfter

    Oh, gotcha.
    Well, good to hear ;)

  • Pingback: CakePHP : signets remarquables du 07/04/2009 au 16/04/2009 | Cherry on the...()

  • Pingback: A little tip about counterScope | Dailytuts.net - Daily tutorial for peoples()

  • Pingback: How to make the counting of records faster without using SQL COUNT keyword in CakePHP using counterScope « Myles Kadusale’s Blog()

  • http://www.bancosul.ro Mihai

    Hi,

    For counter cache i use triggers (using MySql InnoDB tables ). Lets say we have the following situation:

    We have an web app for exchanging CDs.
    So we have an Albums table, an Users table and an album_user relation.
    An user can request/offer/put in library a cd. For that we use a status field
    So, for caching the number of users who requests/offers/library a cd we use three fields: requested_count, offered_count, library_count.

    You have 2 options:
    1. Use Cake’s counter cache on AlbumUser model (it’s not that easy in the given situation)
    2. Make a trigger on album_user table to update corresponding field on albums table on INSERT/UPDATE/DELETE.

    The second option is faster and you will not have data inconsistency when you will delete/add records manually,

    I hope it helps.

    Cheers.

  • http://teknoid.wordpress.com teknoid

    @Mihai

    For #1, you are right… I don’t see how the described feature would be applicable in this situation.
    However, for #2 you should consider looking at the model’s afterSave() and afterDelete() callbacks.

    Personally, I’m not a big fan of triggers… especially when we can rely on ORM to handle this logic quite easily and take care of both hard and soft deletes. (Well, but that’s just me).

    The other approach is to apply the Observer pattern, which I’ve described in another post (although it’s just a more global approach to using the callbacks when all is set and done).

  • http://www.bancosul.ro Mihai

    Yes, i have read the observer pattern post :) , that could be an solution to. I have forgot to mention the use of model’s callback functions.

  • http://agriya odkrot

    really it’s useful,thanks…………………