Modeling record counts


#1

Hi all,

I’m looking for some guidance on how to go about modeling record counts/totals at a global level within my application, not quite sure the best, most efficient way to go about it within ember-data.

A good example to illustrate what I’m looking for is to take an e-mail client that has a sidebar with links to the main boxes/folders, e.g. Inbox, Spam, Drafts, Sent, ect, and next to each one is a badge with the total number of messages in that folder. Now given a Message model, of which there may be thousands of instances contained within each of these different folders, you obviously don’t want to have to fetch all of them from the server, in order to get the necessary total meta data in which to render out each folder’s count. Also, any filtering/searching that the user does within each folder should not affect these “global” totals.

Given these requirements, and assuming I have complete control over the backend API, how would you go about retreiving and displaying this count information? My first thought was maybe to have a Count model, that was fetched separately from the main records, which had an attribute for each of the “folders” (i.e. folder1Count, folder2Count, ect.). However I’m not quite sure this is the best possible solution, as it seems I would have to make sure this Count model was always being manually reloaded/refreshed throughout my application code, whenever a change occurred that might change the values.


#2

I would probably go about doing the count model, or a model for storing random metadata for your app. So, it could be reused for multiple purposes (like the order of menus, custom text from the server, etc).

In regard to manually updating your count; yes, I think that’s exactly what you would have to do. In this case, I think you could do 2 things:

  1. Extend your adapter to auto refresh your counts everytime you do a post or put request, you can probably filter and let this run on specific models.
  2. Add this functionality to a service and call a function to refresh your counts all over your code.

I would say both have pros and cons that vary in how complex your app is or will be in the future. Good luck and let us know what did you end up using for future reference!


#3

Personally, I’d go for something like /api/messages?aggregate=count (or, if you prefer, count=true), as it seems the most RESTful option to me. It’s quite easy to have Ember pass in an additional query param: this.store.findAll('message', { aggregate: 'count' }).

Then your API could return the metadata and an empty array of objects.


#4

Thanks for the input guys! I do like Fryie’s suggestion for keeping the API a bit cleaner and more RESTful, though having a generic “Metadata” model for various bits of data does seem like it would be useful too.


#5

I took a look at http://jsonapi.org/ and found two examples of supplying counts, both were within meta. The first was with a count attribute under the meta root, and the second was a total within paginated collections (via an extension).


#6

I decided on going with the separate Count model, which contains a numeric “count” attribute for each of the categories/folders, and it seems to be working out quite well so far.

In order to keep my application code DRY, and avoid cases where I may forget to manually refresh the Count model, I’ve implemented create/update/delete event listeners on the main record model which fire off a findRecord for the Count model whenever a create, update, or delete is persisted to the server. Continuing my example from the initial post, this is essentially what I did:

export default DS.Model.extend({
    //... attributes defined here

    //update the count model whenever a model is created, updated, or deleted
    updateCounts: function() {
        this.store.findRecord('count', 1, {reload: true});
    },

    onCreate: Ember.on('didCreate', function() {
        this.updateCounts();
    }),

    onUpdate: Ember.on('didUpdate', function() {
        this.updateCounts();
    }),

    onDelete: Ember.on('didDelete', function() {
        this.updateCounts();
    })
});