Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ember Data model's errors property (DS.Errors) not populating

I'm using Ember Data and I can't seem to get the model's 'errors' property to populate with the error messages from my REST API. I'm pretty much following the example at this guide:

http://emberjs.com/api/data/classes/DS.Errors.html

My app looks like this:

    window.App = Ember.Application.create();

    App.User = DS.Model.extend({
        username: DS.attr('string'),
        email: DS.attr('string')
    });

    App.ApplicationRoute = Ember.Route.extend({
        model: function () {
            return this.store.createRecord('user', {
                username: 'mike',
                email: 'invalidEmail'
            });
        },

        actions: {
            save: function () {
                this.modelFor(this.routeName).save();
            }
        }
    });

And my API returns this:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 125

{
  "errors": {
    "username": ["Username is taken!"],
    "email": ["Email is invalid."]
  }
}

After I call save() on the model, here is what I see on the user model:

user.get('isError') // true
user.get('errors.messages') // []

Even though the model is registering the isError property correctly, I can't seem to get the error messages to populate. How can I get this to work? I'm working on the latest beta build of Ember Data version 1.0.0-beta.8.2a68c63a

like image 460
Johnny Oshika Avatar asked Jun 04 '14 00:06

Johnny Oshika


2 Answers

The docs are definitely lacking in this area, the errors aren't populated unless you're using the active model adapter.

Here's an example of it working, also check out Ember: error.messages does not show server errors on save where I say the same thing

http://jsbin.com/motuvaye/24/edit

You can fairly easily implement it on the RESTAdapter by overriding ajaxError and copying how the active model adapter does it.

App.ApplicationAdapter = DS.RESTAdapter.extend({

  ajaxError: function(jqXHR) {


    var error = this._super(jqXHR);

    if (jqXHR && jqXHR.status === 422) {
      var response = Ember.$.parseJSON(jqXHR.responseText),
          errors = {};

      if (response.errors !== undefined) {
        var jsonErrors = response.errors;

        Ember.EnumerableUtils.forEach(Ember.keys(jsonErrors), function(key) {

          errors[Ember.String.camelize(key)] = jsonErrors[key];
        });
      }
      return new DS.InvalidError(errors);
    } else {
      return error;
    }
  }
});

http://jsbin.com/motuvaye/27/edit

https://github.com/emberjs/data/blob/v1.0.0-beta.8/packages/activemodel-adapter/lib/system/active_model_adapter.js#L102

like image 69
Kingpin2k Avatar answered Sep 28 '22 01:09

Kingpin2k


I've had a long and very frustrating experience with Ember Data's errors.messages property, so I thought I'd summarize all of my findings here in case anyone else tries to use this feature.

1) Documentation is out of date

As @kingpin2k mentioned in his answer, the documentation at http://emberjs.com/api/data/classes/DS.Errors.html is out of date. The example they provide on that page only works if you're using DS.ActiveModelAdapter. If you're using the default DS.RESTAdapter, then you need to do something like this. Note that I prefer this simpler approach instead of just copying ActiveModelAdapter's ajaxError implementation:

App.ApplicationAdapter = DS.RESTAdapter.extend({
    ajaxError: function (jqXHR) {

        this._super(jqXHR);

        var response = Ember.$.parseJSON(jqXHR.responseText);
        if (response.errors)
            return new DS.InvalidError(response.errors);
        else
            return new DS.InvalidError({ summary: 'Error connecting to the server.' });
    }
});

2) You must supply a reject callback

This is very strange, but when you call save() on your model, you need to provide a reject callback, otherwise, you'll get an uncaught 'backend rejected the commit' exception and JavaScript will stop executing. I have no idea why this is the case.

Example without reject callback. This will result in an exception:

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

Example with reject callback. Everything will work well:

    user.save().then(function (model) {
        // do something
    }, function (error) {
        // must supply reject callback, otherwise Ember will throw a 'backend rejected the commit' error.
    });

3) By default, only the error properties that are part of the model will be registered in errors.messages. For example, if this is your model:

App.User = DS.Model.extend({
    firstName: DS.attr('string'),
    lastName: DS.attr('string')
});

...and if this is your error payload:

{
    "errors": {
        "firstName":"is required",
        "summary":"something went wrong"
    }
}

Then summary will not appear in user.get('errors.messages'). The source of this problem can be found in the adapterDidInvalidate method of Ember Data. It uses this.eachAttribute and this.eachRelationship to restrict the registration of error messages to only those that are part of the model.

  adapterDidInvalidate: function(errors) {
    var recordErrors = get(this, 'errors');
    function addError(name) {
      if (errors[name]) {
        recordErrors.add(name, errors[name]);
      }
    }

    this.eachAttribute(addError);
    this.eachRelationship(addError);
  }

There's a discussion about this issue here: https://github.com/emberjs/data/issues/1877

Until the Ember team fixes this, you can work around this problem by creating a custom base model that overrides the default adapterDidInvalidate implementation, and all of your other models inherit from it:

Base model:

App.Model = DS.Model.extend({
    adapterDidInvalidate: function (errors) {
        var recordErrors = this.get('errors');
        Ember.keys(errors).forEach(function (key) {
            recordErrors.add(key, errors[key]);
        });
    }
});

User model:

App.User = App.Model.extend({
    firstName: DS.attr('string'),
    lastName: DS.attr('string')
});

4) If you return DS.InvalidError from the adapter's ajaxError (the one we overrode above), then your model will be stuck in 'isSaving' state and you won't be able to get out of it.

This problem is also the case if you're using DS.ActiveModelAdapter.

For example:

    user.deleteRecord();
    user.save().then(function (model) {
        // do something
    }, function (error) {

    });

When the server responds with an error, the model's isSaving state is true and I can't figure out to reset this without reloading the page.

Update: 2014-10-30 For anyone who's struggling with DS.Errors, here's a great blog post that summarizes this well: http://alexspeller.com/server-side-validations-with-ember-data-and-ds-errors/

like image 40
Johnny Oshika Avatar answered Sep 28 '22 02:09

Johnny Oshika