Looking for paradigm to use for generic error handling in Angular from a JSON response from Rails - ruby-on-rails

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 suppose I can write a response interceptor but not sure how that would fully get flushed out.
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).

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.

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.

Related

Get Response from CREATE_STREAM

We upload a document from SAPUI5 to our SAP System using the CREATE_STREAM Method of the oData Service in ABAP. The creation of the document works fine.
What we would like to achieve is to get the response back to SAPUI5. Especially when there is an error during the creation of the document in the backend.
In Frontend we use the uploadSet Control.
...oUploadSet.uploadItem(oItem);
In the Backend we create a message with
...lo_message_container->add_message( iv_msg_type = /iwbep/cl_cos_logger=>error
iv_msg_number = '018'
iv_msg_id = lv_msg_id
iv_add_to_response_header = abap_true
)....
We can find the created message in the error protocol of our gateway server (/IWFND/ERROR_LOG). But how can this message be retrieved in SAPUI5 and used in the MessageManger Control?
We tried the onUploadCompleted Control but we can't find any response data there.
Can somebody explain how the response or a message header from the CREAT_STREAM method can be used in SAPUI5?
The "new" UploadSet control is kinda half-baked imo. The response will get lost in some internal method. This internal method will then trigger onUploadCompleted and you get nothing but useless information.
Lucky for us we can easily overwrite this internal stuff. UploadSet has an aggregation Uploader. We have to provide our own Uploader. Problem solved. Here is the line that needs to be modified.
sap.ui.define([
"sap/m/upload/Uploader",
...
], function (Uploader, ...) {
return Uploader.extend("my.custom.control.Uploader", {
uploadItem: function (oItem, aHeaders) {
// beginning of the method. take it from the official sources
oXhr.onreadystatechange = function () {
const oHandler = that._mRequestHandlers[oItem.getId()];
if (this.readyState === window.XMLHttpRequest.DONE && !oHandler.aborted) {
// we need to return the xhr object. it contains the response!
that.fireUploadCompleted({ item: oItem, xhr: oXhr });
}
};
// .. rest of the method
}
});
});
Use it like this
<mvc:View xmlns:custom="my.custom.control" ....>
<UploadSet items="....">
.....
<uploader>
<custom:Uploader uploadUrl="......"
uploadCompleted=".onUploadCompleted"
uploadStarted=".onUploadStarted" />
</uploader>
</UploadSet>
Edit: Your own uploader also means implementing your own event handlers (uploadAborted, uploadCompleted, uploadProgressed, uploadStarted). See the official documentation for more information about the events.

Zapier - Invalid API Response

I'm integrating a CRM with Facebook lead Ads using Zapier, and I can create a lead in Facebook and create it in the CRM without any issues.
After a successful post (i.e. successfully creating the lead), I'm curious what I should be returning, I would have thought
return Ok();
would have been enough. Instead, I get an error message saying:
- Got a non-object result, expected an object from create ()
What happened (You are seeing this because you are an admin):
Executing creates.ZapLead.operation.perform with bundle
Invalid API Response:
- Got a non-object result, expected an object from create ()
What should I be returning?
Code which makes the post is:
perform: (z, bundle) => {
const promise = z.request({
url: 'https://{{bundle.authData.subdomain}}.ngrok.io/api/zapier/create/lead/' + bundle.inputData.lead_type + '/' + bundle.inputData.core_customerTypeId,
method: 'POST',
body: JSON.stringify({
fields: bundle.inputData
}),
headers: {
'content-type': 'application/json'
}
});
// return promise.then((response) => JSON.parse(response.content));
return promise.then((response) => {
if (response.status != 200) {
throw new Error(`Unexpected status code ${response.status}`);
}
});
Any ideas?
Thanks,
David here, from the Zapier Platform team.
While your answer is technically correct, I wanted to add some context about the error and why your solution works.
Zapier expects a javascript object (basically anything valid and wrapped in {}) to come out of a create function. That's why JSON.parse(response.content) works, it's returning whatever the server said. Your code throws an error if it's not a 200, but doesn't return anything if it is a 200. Since undefined is not of type Object, the error you're seeing is thrown.
While {"Success":"Success","Attempt":null,"Id":null,"RequestId":null} is totally a valid response (it's an object!), it's more useful for the end-user to return data about the new lead that was created. That way, it can be used downstream for other actions.
​Let me know if you've got any other questions!
As a side note, we're very open to how to make that error message more clear; it's one devs struggle with a lot.
I think I found it. Looks like I need to return this if successful:
{"Success":"Success","Attempt":null,"Id":null,"RequestId":null}

Server Validation in angularjs

i'm using ruby on rails in my backend and angularjs in my client side.
here's a scenario i input same email in my form so in my console response a 422 (Unprocessable Entity) and said 'email was use'. it means my validation was good and working properly in my backend.
my question is how can u get that response and put that in my view so user can see that email was already in use.
If you are using angular's $http service, getting the status code of the response is simple, just access res.status from one of your factories/services:
$http.get(`/route/${id}/foo.json`).then(function(res){
callback(res)
}).catch( (error) => { console.log(error) })
If you want to get that into a controller, I suggest using a callback function like this:
someFactory.get($scope.id, function(data){
console.log("data.status");
})

Angular ngResource handle 404 api error

i have a Rails API
Ex. example.com/v1/portfolio/{{ id }}
and i'm using AngularJS ngResource to handle the data with get.
myAppServices.factory('Portfolio', ['$resource', function($resource){
return $resource('api/v1/portfolios/:id/', { id:'#id'}, {
query: {
method:'GET',
params:{id:'#id'},
isArray:true
}
});
}]);
How can i handle the Not found 404 Error if the API does not have an Entry
Ex. example.com/v1/portfolio/blabla
i mean how can i make it 404 like when u enter http://www.google.com/sdfa check the network status code you'll find it 404.
For more information am using Prerender.io service to Render the website for Search Engines.
You can use $promise like this:
var resource = $resource('api/v1/portfolios/:id/');
resource.get({id: 123}).$promise.then(function(todo) {
// success
}, function(errResponse) {
// handle 404 here
});
You should use the prerender-status-code meta tag to return a 404 to the crawler. The documentation is here: https://prerender.io/documentation/best-practices
Basically, just add to the of the page if the page should return a 404 to crawlers. Prerender.io will detect that status code and return that to the crawler instead of a 200.
Feel free to send an email to support#prerender.io if you have any other questions about that. We're happy to help!

Devise doing redirect with Angular.js 1.2.1

I made a project with angular 1.0.8 and devise last version, on Rails 4, all worked great, devise was sending respond 401 when I did request to the api without been logged in.
the interceptor was getting the 401 and handle it the way I wanted.
but then decide to make the upgrade to the last stable version 1.2.1 and all worked great (only needed to change the interceptor, instead of return a function, returned an object), but for some reason devise now does a redirect to the sign_up page instead of send the 401 respond, not sure whats going on because I test with a controller responding with a status: :unauthorized, and my interceptor did caugth it just fine. someone already had this problem when migrating to 1.2.1? as I said the only code I change was the interceptor it self (it works fine with my 401 responds).
all I get is a 302 Found status code.
the interceptor code:
.config(function($httpProvider){
var interceptor = function($q, $location, $rootScope) {
return {
'responseError': function(rejection) {
if (rejection.status == 401) {
$rootScope.$broadcast('event:unauthorized');
console.log('got you trying to do something illegal...')
return rejection;
}
return $q.reject(rejection);
}
};
};
$httpProvider.interceptors.push(interceptor);
})
UPDATE
So after a while trying I realize to get a 401 using angularjs 1.2.1 and Devise I need to:
Add the .json format to all the services url(that needed authentication, eg: api/events/:id.json).
Make sure the config.navigational_formats = [] on config/initilizers/devise.rb didn't had the :json in its array.
And make sure the config.http_authenticatable_on_xhr = true was set to true.
But I still have the question, why on Angular.js 1.0.8 this worked even with navigational_formats array having the :json and services dont having .json?
Its the way its now the right way?

Resources