What's the best way to get a simple list of stats with Ember Data?


#1

I’m new to Ember, and especially Ember Data, so forgive me if this is obvious.

I would like to display some basic live stats in my app such as activeUsers, usersOnline, etc. What’s the best way to represent these stats on the API and fetch them with Ember data?

I’d prefer to represent them all within a single object accessible via GET /statistics, then just grab that object as the model like:

    model: function(){
        return this.store.find('statistics');
    }

with this to tell ember that statistics is uncountable:

var inflector = Ember.Inflector.inflector;
inflector.uncountable('statistics');

But, Ember now tells me I need to use findAll (because there’s no unique identifier for the stats data object).

So I represent the data on the server like this:

{
  "statistics": [
    {
      "activeManagers": 4207,
      "activeTeams": 4840
    }
  ]
}

And change the model hook to this:

    model: function(){
        return this.store.findAll('statistics');
    }

Ember complains that there needs to be an ID on each item, so I add an arbitrary id: 1 on the object:

{
  "statistics": [
    {
      "id": 1,
      "activeManagers": 4207,
      "activeTeams": 4840
    }
  ]
}

Now I get back a list of items (albeit only 1 item long), but I can’t work out how to get hold of the first item either in the model / ember data promise hook, or in the template.

I could always change the API to respond from GET /statistics/1, and just use find instead of findAll. But that seems weird since there’s no other records with any other ids, so the whole /1 thing doesn’t feel right. Now I’m just starting to doubt my entire existence and feel like there’s got to be a more elegant way to do something that should be so simple.

So here I am, asking/begging for help from the Ember gurus out there. Am I doing this all wrong?


#2

I should note I’m using the default REST adapter with a JSON REST API.


#3

In terms of getting the first object in the model hook:

model: function() {
    return this.store.findAll('statistics').then(function (all) {
        return all.objectAt(0);
    });
}

Which will mean the model for this route is the first object that was returned by the server.

In terms of ways as to how to handle single instance models… I’ve done the same as you in that I’ve assigned an id and normally just find all then return the first in the collection.

I’d be interested in hearing what other people have done. You’re right that it feels a bit odd to have to assign an id to something that doesn’t need an id.


#4

Thanks @lindyhopchris !

Your code wasn’t working at first, but that was my fault. Due to a new deprecation notice, referenced here.

DEPRECATION: The default behavior of shouldBackgroundReloadAll will change in Ember Data 2.0 to always return false when there is at least one “statistics” record in the store. If you would like to preserve the current behavior please override shouldReloadAll in your adapter:application and return true.

… I had then added the suggested override of shouldReloadAll to the adapter - but I had it returning false just to see what effect it had - and had forgotten about it. This was super dumb and cost me a bunch of time. It was causing the returned DS.RecordArray to still have isUpdating set to true and the array was still empty after the promise resolved. Switching shouldReloadAll back to return true as instructed fixed this.

Anyway, moving on. Two things.

  1. On topic: I was wondering if it might be more elegant to have the server represent each statistic as it’s own key-value record. Using findAll would then receive a group of individual statistics as a group of records. So then the question then becomes how to access each of those stats within the template for display? I assume I could create a new DS.RecordArray in the promise hook, iterate over each stat, and add them to the new array and then just return that instead. What do you think?
  2. Off topic: In reference to the above deprecation, and for the future and my own understanding of things, how would one force Ember data to reload data from the server when calling findAll if there was already some cached data in the store meaning shouldBackgroundReloadAll would return false?

#5

Ok, I’ve decided to go with representing each stat as it’s own record.

The API response looks like this:

{
    "statistics": [
        {
            "id": "activeManagers",
            "value": "4236"
        },
        {
            "id": "activeTeams",
            "value": "4861"
        },
    ]
}

And the model hook looks like this:

  model: function() {
    return this.store.findAll('statistic').then(function(all) {
      
      var Stats = Ember.Object.create();
      
      all.forEach(function(item) {
        Stats.set(item.get('id'), item.get('value'));
      });
      
      return Stats;
    });
  },

This has a few benefits.

  1. The model for statistic is a lot more portable, only having to define id and value which then supports an indeterminate number of statistics. Whereas before I would’ve had to update the model for every new statistic added.

  2. Not that I’d need to, but I can then request individual stats from the API via GET /statistics/:id

  3. Again not that I’d need to but I would be able to update stats via POST /statistics/:id

  4. Beautiful, beautiful elegance. Well, more than the other way… I concede it seems like a little bit of overkill by using separate records for each stat, but it’s the best approach I’ve seen so far. Any alternates are welcome.


#6

I would personally use a standard ajax call to get statistics, and wrap the logic in a service. Your statistics are not really domain entities, and no operations would be performed on them apart from reads.