13

I'm building an app which is architected as a Rails server app providing RESTful api's to the client. The Rails server uses RABL. The client is an Angular JS client performing standard $http calls (gets, puts, etc).

Occasionally my Rails server will produce an error (let's say validation error attached to the object) or even no error in which case I would want to display something to the user - either the errror e.g., "The record did not save because..." or "The record was updated successfully".

I'm trying to map out a pattern on both the Rails side and the Angular/client side to handle this.

As for Rails:

  • I can certainly pass back a node in each of my RABL files to contain error arrays
  • I can also return different RABL by checking in the controller before returning
  • Most suggest using http codes (which makes sense) as per here (although there doesn't seem to be a consistent usages of the codes for something like a validation error).

As for Angular:

I guess I'm hoping that I don't have to reinvent the wheel here and someone can point me to a pattern that's currently used and suggested (and localized).

Community
  • 1
  • 1
Arthur Frankel
  • 4,695
  • 6
  • 35
  • 56

2 Answers2

12

I went ahead and implemented what I thought needed to be done. Thanks for digger69 for some help with this.

On the Rails side, I went with using an http status code. As per here I agreed with using a 400 http status code for error validation.

In my controllers I now have something like the following:

def create
  my_obj = MyObj.build_with_params(params)
  if my_obj.save
    respond_with(my_obj) # regular RABL response
  else
    respond_with_errors(my_obj.errors)
  end
end

In my application_controller.rb I defined a common method respond_with_errors

# respond back to the client an http 400 status plus the errors array
def respond_with_errors(errors)
  render :json => {:errors => errors}, :status => :bad_request
end

Note that the :bad_request symbol is already defined for Rails as per here

On the client side I needed to intercept http calls (not only for validation but for authentication failures too (and probably more). Here is an example of my code in Angular (thanks to this post for the help with that):

var interceptor = ['$rootScope', '$q', function (scope, $q) {
  function success(response) {
    return response;
  }
  function error(response) {
    var status = response.status;

    if (status == 401) { // unauthorized - redirect to login again
      window.location = "/";
    } else if (status == 400) { // validation error display errors
      alert(JSON.stringify(response.data.errors)); // here really we need to format this but just showing as alert.
    } else {
      // otherwise reject other status codes
      return $q.reject(response);
    }
  }
  return function (promise) {
    return promise.then(success, error);
  }
}];
$httpProvider.responseInterceptors.push(interceptor);

I now can be consistent with my rails code and deal with success returns from http calls on the client. I'm sure I have some more to do, but I think this gives a localized solution.

Community
  • 1
  • 1
Arthur Frankel
  • 4,695
  • 6
  • 35
  • 56
  • I really like this architecture however there's one thing that I would suggest to improve it. If you send back the original object in the data payload then the client will reset it to the state that it's on in the server - so if you check a box and it fails to save then it will be unchecked again when the failed request comes back. Angular does this automatically. So I would suggest not *just* sending the errors back but the original object + an error message which you then display That way the client will have a consistent version of the server data + the error message – Peter Nixey Jun 03 '14 at 16:53
  • Sorry, just to be clear, the bound data in the page will be automatically updated when you're using $resource - obviously that may not the the case if you're directly using the $http service – Peter Nixey Jun 03 '14 at 16:54
1

Use an HTTP response interceptor. I am currently using that successfully in an application.

http://docs.angularjs.org/api/ng.$http

From the documentation:

$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
  return function(promise) {
    return promise.then(function(response) {
      // do something on success
    }, function(response) {
      // do something on error
      if (canRecover(response)) {
        return responseOrNewPromise
      }
      return $q.reject(response);
    });
  }
});

$httpProvider.responseInterceptors.push('myHttpInterceptor');

In my case I created a feedback service, which displays either success or error messages globally. An other option would be to broadcast the responses on the rootscope.

  • Thank you for commenting - I had just implemented something similar on the angular side and had to implement some code in Rails to deal with RABL responses. See my response. – Arthur Frankel Apr 11 '13 at 21:21