4

I have a rails app serving json to an ember frontend.

I am trying to display validation errors from on a form on the client.

Rails is returning this json:

{"errors":{"hometown":["is too long (maximum is 64 characters)"]}}

In my handlebars template for the current route I am attempting to iterate through the errors but I don't get any output for the errors section:

<div class="form-group">
  <label>Hometown</label>
  {{#each errors.hometown}}
    {{this}}
  {{/each}}
  {{input type="text" class="form-control" valueBinding="effectiveUser.hometown" disabled=entryNotAllowed size="50"}}
</div> 

I also updated my RESTadapter based on this blog: https://marcqualie.com/2014/04/model-errors-in-emberjs to include:

  ajaxError: function(jqXHR) {
    var error = this._super(jqXHR);
    if (jqXHR && jqXHR.status === 422) {
      var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"];
      return new DS.InvalidError(jsonErrors);
    } else {
      return error;
    }
  }

I still really dont understand the context of what this errors object is and why my view has access to it that I but several different sources seem to say this setup should work. Any insight would be appreciated.

1
  • Have you tried rendering the errors the way they demonstrate in that blog post? {{error.message}} instead of {{this}} in the loop. Commented Dec 31, 2014 at 0:37

3 Answers 3

3

Recent Ember Data Beta's since 1.0 Beta 12 pass the errors to the model. The store processes InvalidError results for you and places the errors on the model.

I submitted a PR #2392 which has been merged allowing your model serializers the opportunity to extract errors from the error payload prior to being set on the model. The default implementation ensures that error payload keys are now correctly mapped to attribute keys whereas previously this was not the case.

Ember Data will still only apply errors to the model which have a corresponding attribute defined. This has been a source of frustration since often there are errors (eg "base") that apply to the model as a whole. This is covered in the outstanding issue #1984, with the following workaround until it is merged.

DS.Model.reopen({
    adapterDidInvalidate: function(errors) {
      var recordErrors = this.get('errors');
      for (var key in errors) {
        if (!errors.hasOwnProperty(key)) continue;
        recordErrors.add(key, errors[key]);
      }
    }
});

Your template code should also follow the example in DS.Errors:

{{#each message in errors.messages}}
  <div class="error">
    {{message}}
  </div>
{{/each}}

You may wish to use the errorsFor method to target a specific attribute.

You may also want to use a helper or component that extracts an attribute specific model error and marks it up appropriately. Some example code on how to do this is available in the ember-forms add-on.

I also just discovered a tutorial on Ember Data server side error handling which may be useful, just ignore the RESTAdapter bit since it's out of date now.

Sign up to request clarification or add additional context in comments.

3 Comments

If only the ember doc could be as clear as your reply ... anyway playing with the promise error return works in all cases, it also allow you to check for other kind of errors :)
Am I right that InvalidErrors will be created only if ActiveModelAdapter is used? And in other cases (RESTAdapter) I need to produce them by myself, right? Why not to place 422 handling into RESTAdapter.ajaxError?
@sedovav It is handled independent of the adapter (JSONAdapter from memory) but for the ActiveModel adapter case my PR also included a specific change the since the ActiveModel adapter overrides the default behavior. I don't use the ActiveModel adapter in my applications.
2

Well as far as i understand ember it's normal this isn't working :

To save your model in your controller/route/view you are performing a save()operation which returns a promise. If a promise is rejected a function to handle this rejection can be executed with a reason as a parameter. In your case the DS.InvalidError object will become this reason.

myModel.save().then(function(value){
  //success
},function(reason){
  //fail
});

so in your case (but depends where you are handeling action i will supose in controller) something like that should do the trick;

actions: {
  submitForm : function(){
    this.set("errors",null);
    var ctx=this;
    myModel.save().then( function(){
      //display a succes msg ?
    }, function(errors){
      ctx.set("errors",errors);
    });
  }                              
}

1 Comment

This ended up working perfectly. It still looks to me like there might be a way to access the errors without setting them on a controller but this gets the job done. I think the blog post distracted me from thinking about the simpler way of handling this through the promise handler on save. I also just deleted my custom ajaxError handler which was totally unnecessary.
0

I have implemented the ajaxError method like show below.

App.ApplicationAdapter = DS.RESTAdapter.extend({
  ajaxError: function(jqXHR){
  error = this._super(jqXHR);

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

    if (typeof response.errors !== 'undefined') {
      jsonErrors = response.errors;
      Ember.keys(jsonErrors).forEach(function(key) {
        errors[Ember.String.camelize(key)] = jsonErrors[key]
      });
    }
    if (typeof response.message !== 'undefined') {
      errors['Message'] = response.message;
    }

    return new DS.InvalidError(errors)
  } else {
    return error
  }
  }

});

Using the previous code I'm able to get errors on my template.

{{#each message in errors.messages}}
  <strong>{{message}}</strong><br />
{{/each}} 

2 Comments

This seems to be functionally the same as what I already had. I believe that this is possible but I don't even understand why it would work. When you say "errors.messages" in your template what is the "errors" object you are referencing and where did it come from?
The errors is a DS.Errors object.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.