Building Calliope – Sproutcore data sources (Part I)

If you don’t know what Calliope is, please read this other post before continuing on.

In this post I’m covering the problems I encountered while implementing data access in the client-side of Calliope using Sproutcore data sources. If you are starting now to have a look at Sproutcore way of handling data, I suggest you have a look at these pages in the official documentation:

The official documentation is well written, but it uses very simplistic examples that fall short in real word applications (even a simple one like Calliope) and it is missing the “big picture” of how things work. These, I think, are the main reasons why I – and I am not the only one – had such a hard time figuring out how to use the data store effectively.

With this post I hope to fill some of the gaps of the official documentation, by showing what problems I faced and how I resolved them.

For Calliope I have defined 5 Sproutcore models:

  • Player
  • Game
  • Turn
  • TurnLetter
  • Action

Since definitions are really simple I won’t paste them here, for a general description of the fields involved have a look at the data model post.

I think at the models as they were divided into two kinds: globals and locals. I put Player and Game into the “globals” definition, since they are located at the root level – that is, they have no parent model. For games this is not really precise: if you read the post about the design of services you could object that games are children of players; this is essentially true, but since a player can only see his/her games I will threat them as global entities.

Turn, TurnLetter and Action are instead viewed as local entities, with a game as the parent.

This logical differentiation between global and local entities is represented in the program in the structure of the data sources: I coded a first data source to feed the data store with global data, and a second one – cascaded to the first – to handle local data related to a specified game. In the following I am going to cover the design of both data sources and how they are used to support the execution of the game.

Global data source

The global data source is implemented in a very simple way. There is essentially one local query defined for each global entity; those queries are defined as follows:

Calliope.Player.ALL_PLAYERS = SC.Query.local(Calliope.Player);
Calliope.Game.ALL_GAMES = SC.Query.local(Calliope.Game);

These queries are used to retrieve the whole list of players and games from the server, by issuing a find on the data store, like this:

Calliope.store.find(Calliope.Player.ALL_PLAYERS);

When this call is done for the first time in the life of the application, Sproutcore automatically calls the method fetch of the data source associated with the store (the global data source), passing in the query. That method essentially does a check on the query: if it matches one of the two defined above it calls the remote service for games (or players), saving the result into the store. The code related to the players is:

Calliope.DataSource = SC.DataSource.extend({
  fetch: function(store, query) {
    if (Calliope.Player.ALL_PLAYERS === query) {
      Calliope.session.createRequest({
        address: "/players"
      }).notify(this, this._didFetchAllPlayers, query, store)
      .send();
    }
    return YES;
  }

  _didFetchAllPlayers: function(response, query, store) {
    var body = undefined;
    if (SC.ok(response)) {
      body = response.get("body");
    }
    if (SC.ok(response) && SC.ok(body)) {
      // load the players into the store
      store.loadRecords(Calliope.Player, body);
      // notify store that we handled the fetch
      store.dataSourceDidFetchQuery(query);
    } else {
      // handle error case
      store.dataSourceDidErrorQuery(query, response);
    }
  }
});

Games case is handled in a very similar way.

So, the call to find loads in the store all the records of players and games. After doing that, issuing a find to the store using a record type and id let us retrieve instantly the record we are looking for. Here is a simple example (assuming that player “foo” has id 1):

var playerFoo = Calliope.store.find(Calliope.Player, 1);

And this is about everything you have to know about my usage of global stores: the calls to find on the queries of player and games are performed as long as the user logs in, and then the retrieved data is used throughout the rest of the application.

End of Part I

If you read this far please take the time to leave a comment to let me know what you liked, what you didn’t, and what you would do differently. Thank you.

Go to Part II »

« Go back to the index

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s