Ember 1.0 – rise from the ashes

We have been moving our explorer2 app to use the released version of Ember (1.0) and the accompanying Ember Data (1.0.0 beta 3). There have been lots of changes to Ember Data which you can find here.

Ember changes
Ember itself has not changed too much between RC and release but here are some things we noticed

1) Refer to routes inside quotes in linkTo blocks

Previously we had

{{#linkTo post.comment comment}}{{comment.text}}{{/linkTo}} 

which is now

{{#linkTo 'post.comment' comment}}{{comment.text}}{{/linkTo}}

2) Define hasMany etc relationships as ‘async’ in models
If the child models are loaded as something like “comment_ids”:[1,2] in the server json response then

comments: DS.hasMany('comment')

should now be

comments: DS.hasMany('comment', { async: true })

3) hasMany etc relationships are now simple strings rather than referring to the model type

 DS.hasMany('App.Comment')

is now

 DS.hasMany('comment')

4) controller actions now inside ‘actions hash’.
Inside your controller put your actions inside a block like this:

actions: {
  someAction: function(){},
  anotherAction: function(){}
}

Ember Data Changes
There are some really important Ember Data changes, here are some highlights

1) Per model adapters.

Each model can have its own adapter to find data etc. This is really useful if you create your model from ‘non-standard’ json or assign your own ids.
Here is an example from explorer2 where we grab some json from a server and create the model on the fly.

App.CompoundAdapter = DS.Adapter.extend({
  find: function(store, type, id) {
    // return a promise inside of which is the callback which either resolves with the retrieved compound data or rejects with the status
    var promise = new Ember.RSVP.Promise(function(resolve, reject){
      var searcher = new Openphacts.CompoundSearch(ldaBaseUrl, appID, appKey);
      var pathwaysSearcher = new Openphacts.PathwaySearch(ldaBaseUrl, appID, appKey);
      // get the compound details  
	  var callback=function(success, status, response){  
        if (success) {
	    var compoundResult = searcher.parseCompoundResponse(response);
            compoundResult['pathways'] = [];
            resolve(compoundResult);
        } else {
            reject(status);
        }
      }
      searcher.fetchCompound('http://www.conceptwiki.org/concept/' + id, null, callback);
    });
    return promise;
  }
});

2) Promises and chaining.

Ember data makes heavy use of Promises (https://github.com/tildeio/rsvp.js). When finding models you resolve or reject from within the promise and can even chain them together if you need to do something else once you have found/created a model. The adapter example above demonstrates a simple promise. Here is an example of a promise chain where we find a model and then do something with it after it is found.

me.get('store').find('compound', url.split('/').pop()).then(function(compound) {
  thisCompound.get('structure').pushObject(compound);
});

3) Find uses the store, not the model
Before we would have

App.Post.find(1)

now we have

store.find('post', 1)

Similarly when creating we do

store.createRecord('post', {title: 'a great post'})

4) onCreate, transactions gone – use promises
previously we would listen to the ‘onCreate’ action of a model and then do something with it. Now do

post.save().then(function(post){
  //do something
});

5) Underscored keys needs different serializer

If your relationships are sent as underscored values in the json, for example:

{"comment":{"id":4,"text":"comment 3","post_id":1}}

Then you need to tell Ember Data by creating an ApplicationSerializer with

App.ApplicationSerializer = DS.ActiveModelSerializer.extend({});

Otherwise your json will look like

{"comment":{"id":4,"text":"comment 3",post:1}}

which in our case breaks the default rails behaviour.

6) Add child model to parent after save
Inside the promise when saving a child model you need to add it to the parent or it will not be shown when transitioning. In this example we save a comment for a post, add it to the posts array of comments and then transition to the index for that post. You could also reload the parent model instead, not sure what the preferred/correct behaviour should be.

comment.save().then(function(comment){
        //post.reload(); //could do this instead
        post.get('comments').pushObject(comment);
        me.get('target').transitionTo('post');
      });

Overall, quite a lot of changes but I think they make Ember easier to use and hopefully make it stable (for a little while at least). You can find an updated simple blog app with the latest changes here. The non rails version using fixture adapter is here with jsfiddle here.