I decided to convert a small inhouse project from Ember Data 0.13 to 1.0.0 Beta. Below is an overview of my findings (the changes needed, some sample code and some walls I hit). I hope this info is useful for others. Do not hesitate to post any additional info as a comment to this post.
I will focus in this post on the CRUD actions of simple entities (not containing related or embedded data). In a later post I will come back on some specifics related to embedded records.
Please read the Ember data transition guide before starting any migration: https://github.com/emberjs/data/blob/master/TRANSITION.md
1. Use the latest master of Ember Data 1.0.0
During the migration I have encountered certain bugs and these have in the meantime been solved on the master version. So I recommend to use http://builds.emberjs.com/ember-data-latest.js for now (is in any case still beta) !
2. Pluralization out of the box
In 0.13, specific pluralization (e.g. category > categories) needed to be set via DS.RESTAdapter.configure(āpluralsā, { ā¦ }). This is no longer needed. There is now a default āsmartā pluralization taking care of this.
See: https://github.com/emberjs/data/commit/9325a1dea594b8ff752886eb7a9d752785282e07
3. RESTAdapter endpoint customization
In 0.13, a specific endpoint (host) or namespace could be set via DS.RESTAdapter.reopen({ ā¦}). This is still possible (in the master version but not in the beta 1.0.0 version), but please note that āurlā is changed to āhostā. Correct code is:
DS.RESTAdapter.reopen({
host: "http://api.company.com";
});
4. Per type serializers and per type adapters
As of version 1.0.0, serializers and adapters are per model type. If you have specific serializer needs (e.g. your idās are returned as _id), then you should specify for each model type a specific serializer. I have created a custom serializer and then specified for each model type this serializer. See below:
App.Serializer = DS.RESTSerializer.extend({
//Custom serializer used for all models
normalize: function (type, property, hash) {
// normalize the `_id`
var json = { id: hash._id };
delete hash._id;
// normalize the underscored properties
for (var prop in hash) {
json[prop.camelize()] = hash[prop];
}
// delegate to any type-specific normalizations
return this._super(type, property, json);
}
});
//Extend each model with the custom serializer
App.AuthorSerializer = App.Serializer.extend();
App.CategorySerializer = App.Serializer.extend();
5. Reading/finding data
Straightforward code change required for fetching data.
Retrieving all records:
App.AuthorsRoute = Ember.Route.extend({
model: function () {
return this.store.find('author');
}
});
Retreiving a single record:
App.AuthorsEditRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('author', params.author_id);
}
});
6. Creating a new record and saving it
Creation of record:
App.AuthorsNewRoute = Ember.Route.extend({
model: function() {
return this.store.createRecord('author');
}
});
Save record (in controller):
Please note that there are no more transactions. You can (need) to refactor all your code that is used to rollback transactions, to recover from becameError or becameInvalid state, etc.
All data actions now have promises and you can thus easily use these to determine whether or not a record is correctly saved (or for example to retry saving records in case of failure). In the code below, I save a record and I catch validation errors (422 response) and other errors (e.g. REST adaper not running).
App.AuthorsNewController = Ember.ObjectController.extend({
actions: {
save: function () {
var self = this;
var author = self.get('model');
author.save().then(
function () {
//Succesful save, thus transition to edit route
self.transitionToRoute('authors.edit', author);
},
function (error) {
if (error.status == 422) {
//422: validation error
//Put json responsetext into validationErrors obj
self.set('validationErrors', jQuery.parseJSON(error.responseText));
} else {
console.log("Validation error occured - " + error.responseText);
alert("An error occured - REST API not available - Please try again");
}
}
);
}
In the code above, I convert - in case of a 422 error - the responseText into an object and use it to display the validation errors on screen (via handlebars in the hbs).
7. Reverting changes
In previous versions you could use transaction.rollback(). There is now a similar way; you can use record.rollback(). Make sure to use the master version of Ember Data as this did not work in 1.0.0 beta 1. Here is how to rollback an edit of a model (e.g. press the cancel button after having modified a field in a form).
cancel: function () {
var author = this.get('model');
author.rollback();
},
8. Deleting a record
Things become more complicated now. To delete a record, you first need to delete it from the store and then āsaveā. The save invokes the call to the REST API. And in case of an error, you can always rollback so that the record becomes available again (and thus client model in sync with server). See code below:
deleteAuthor: function () {
if (confirm("Are you sure you want to delete the selected author ? Click OK to continue.")) {
var self = this;
var author = self.get('model');
//deletes record from store
author.deleteRecord();
//persist change
author.save().then(
function () {
//delete succesful, go back to overview
self.transitionToRoute('authors.index');
},
function (error) {
//Not succsefull, rollback the delete action
author.rollback();
alert("An error occured - Please try again");
}
);
}
}
9. Use Ember Inspector (data view)
As of version 1.0.0, you will no longer receive inFlight errors or errors due to the state being dirty, invalid, etc. However, this does not mean that you cannot mess up things. If you create a record and you browse to another route without saving the record, then you end-up with differences in records at the client (host records with no id) and the server. The same applies to deleting records.
I therefore recommend to keep at all times the Ember Inspector open so that you can verify that the models are as you expect them to be.
Below is a code snipped on how to recover from a route transition in case the data is dirty (changed and not yet saved).
willTransition: function (transition) {
var model = this.get('currentModel');
if (model && model.get('isDirty')) {
if (confirm("You have unsaved changes. Click OK to stay on the current page. Click cancel to discard these changes and move to the requested page.")) {
//Stay on same page and continue editing
transition.abort();
} else {
//Rollback modifications
var author = this.get('currentModel');
author.rollback();
// Bubble the `willTransition` event so that parent routes can decide whether or not to abort.
return true;
}
} else {
// Bubble the `willTransition` event so that parent routes can decide whether or not to abort.
return true;
}
}
10. The good news
All in all I had to do a lot of changes. And so far I am only working with very simple data (having no relationships or embedded records).
Thanks to the new version, I could reduce the code in a specific controller (as an example I took my authorsController) from 220 lines to 90 lines. There is no longer a need to manually keep statusses such as āstartEditingā, āstopEditingā and then depending on these rolling back or committing transactions. In a specific route (as an example I took my authorsRoute), I could reduce from 130 to 70 lines.
The code is thus (in my specific case) reduced to approx. 50% and that is good !
The good news is also that during the migration I got prompt feedback and responses to the questions I had (via stackoverflow and twitter). Many thanks to the people who helped me !
Marc