Свойство ошибок модели Ember Data (DS.Errors) не заполняется

Я использую Ember Data, и я не могу заставить свойство "errors" модели заполнить сообщения об ошибках из моего REST API. Я в значительной степени следую примеру этого руководства:

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

Мое приложение выглядит следующим образом:

    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();
            }
        }
    });

И мой API возвращает это:

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."]
  }
}

После вызова save() на модели, вот что я вижу в пользовательской модели:

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

Несмотря на то, что модель правильно регистрирует свойство isError, я не могу получить сообщения об ошибках. Как я могу заставить это работать? Я работаю над последней бета-версией Ember Data версии 1.0.0-beta.8.2a68c63a

Ответ 1

Документы, безусловно, отсутствуют в этой области, ошибки не заполняются, если вы не используете активный адаптер модели.

Здесь пример его работы, также проверьте Ember: error.messages не показывает ошибки сервера при сохранении, где я говорю то же самое

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

Вы можете довольно легко реализовать его в RESTAdapter, переопределив ajaxError и скопировав, как это делает активный адаптер модели.

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

Ответ 2

У меня был длинный и очень неприятный опыт с Ember Data errors.messages, поэтому я подумал, что обобщил все мои выводы здесь, если кто-то еще попытается использовать эту функцию.

1) Документация устарела

Как упоминалось в его ответе @kingpin2k, документация на http://emberjs.com/api/data/classes/DS.Errors.html устарела. Пример, который они предоставляют на этой странице, работает только в том случае, если вы используете DS.ActiveModelAdapter. Если вы используете стандартный DS.RESTAdapter, вам нужно сделать что-то вроде этого. Обратите внимание, что я предпочитаю этот более простой подход вместо простого копирования реализации ActiveModelAdapter ajaxError:

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) Вы должны предоставить обратный вызов отклонения

Это очень странно, но когда вы вызываете save() на своей модели, вам нужно предоставить обратный вызов отклонения, иначе вы получите исключенное из бэкэнда исключение исключения commit, и JavaScript прекратит выполнение. Я понятия не имею, почему это так.

Пример без отклонения вызова. Это приведет к исключению:

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

Пример с обратным вызовом reject. Все будет хорошо работать:

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

3) По умолчанию в error.messages будут регистрироваться только свойства ошибки, которые являются частью модели. Например, если это ваша модель:

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

... и если это ваша полезная нагрузка:

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

Тогда сводка не появится в user.get('errors.messages'). Источник этой проблемы можно найти в методе adapterDidInvalidate для Ember Data. Он использует this.eachAttribute и this.eachRelationship, чтобы ограничить регистрацию сообщений об ошибках только теми, которые являются частью модели.

  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);
  }

Здесь обсуждается эта проблема: https://github.com/emberjs/data/issues/1877

Пока команда Ember не исправляет это, вы можете обойти эту проблему, создав пользовательскую базовую модель, которая переопределяет реализацию по умолчанию adapterDidInvalidate, и все ваши другие модели наследуют от нее:

Базовая модель:

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

Модель пользователя:

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

4) Если вы вернете DS.InvalidError из адаптера ajaxError (тот, который мы перевернули выше), тогда ваша модель застрянет в состоянии isSaving, и вы не сможете выйти из нее.

Эта проблема также имеет место, если вы используете DS.ActiveModelAdapter.

Например:

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

    });

Когда сервер отвечает с ошибкой, модель isSaving имеет значение true, и я не могу определить reset без перезагрузки страницы.

Обновление: 2014-10-30 Для тех, кто борется с DS.Errors, здесь отличная запись в блоге, которая суммирует это хорошо: http://alexspeller.com/server-side-validations-with-ember-data-and-ds-errors/

Ответ 3

ОБНОВЛЕНИЕ: Ember Data 2.x

Вышеприведенный ответ по-прежнему имеет значение и в целом довольно полезен, но теперь устарел для Ember Data 2.x(v2.5.1 на момент написания этой статьи). Вот несколько замечаний при работе с новыми версиями Ember Data:

  • DS.RESTAdapter больше не имеет функции ajaxError в 2.x. Теперь это обрабатывается RESTAdapter.handleResponse(). Вы можете переопределить этот метод, если требуется специальная обработка или форматирование ошибок. RESTAdapter.handleResponse исходный код
  • Документация для DS.Errors и DS.Model.errors (которая является экземпляром DS.Errors) в настоящее время немного вводит в заблуждение. Он ТОЛЬКО работает, когда ошибки в ответе соответствуют спецификации объекта ошибки JSON API. Это означает, что он не будет вообще полезен или полезен, если ваши объекты ошибок API следуют в любом другом формате. К сожалению, это поведение в настоящее время не может быть переопределено, как и многие другие вещи в Ember Data, поскольку это поведение является дескриптором в частных API внутри класса Ember InternalModel в DS.Model.
  • DS.InvalidError будет использоваться, только если код состояния ответа 422 по умолчанию. Если ваш API использует другой код состояния для представления ошибок для недействительных запросов, вы можете переопределить RESTAdapter.isInvalid(), чтобы настроить, какие коды состояния (или другие части ответа об ошибке) должны быть отмечены как InvalidError.
  • В качестве альтернативы вы можете переопределить isInvalid(), чтобы всегда возвращать false, чтобы Ember Data всегда создавал более общий DS.AdapterError вместо. Эта ошибка затем устанавливается на DS.Model.adapterError и может быть использована при необходимости.
  • DS.AdapterError.errors содержат все, что было возвращено в ключе errors ответа API.