Best practices: error handling Ruby(Rails) to iOS(AFNetworking) - ios

New to RoR, not new to iOS.
So I'm using AFNetworking to handle all of my api calls. For example:
[[TJAPIClient shared] GET:#"api/taxonomies"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSMutableArray *taxonomies = [NSMutableArray arrayWithCapacity:[responseObject[#"taxonomies"] count]];
[responseObject[#"taxonomies"] each:^(NSDictionary *taxonomyInfo)
{
TJTaxonomy *taxonomy = [TJTaxonomy modelWithRemoteDictionary:taxonomyInfo];
[taxonomies addObject:taxonomy];
}];
if (block) block(taxonomies, nil);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Error fetching taxonomies: %#", error);
[UIAlertView showAlertViewWithTitle:#"Server Error"
message:error.localizedDescription
cancelButtonTitle:nil
otherButtonTitles:nil
handler:nil];
if (block) block(nil, error);
}];
And on RoR, nothing fancy:
def create
#user = User.new(user_params)
respond_to do |f|
if #user.save
f.html {}
f.json { render json: #user }
else
f.html {}
f.json { render json: #user.errors.full_messages }
end
end
end
As it stands, the code that is written above returns errors through the success block and the data looks like this:
{
errors = (
#"Email can't be blank",
#"Password can't be blank"
)
}
So all I want to know is how I can send these errors through the failure block, as well as send the respective errors as an NSError object. If that's not possible, then perhaps some best practices for this kind of thing. Again, I'm very new to the RoR world (loving it, though!).
Thank you!

You'll have to check the responseObject of the success callback block if you want to handle those errors.
if (responseObject[#"errors"])
NSLog(#"Oh noes!");
}
The error block is a passthrough1 for NSURLSessionTaskDelegate and will be when URLSession:task:didCompleteWithError: gets called with an error.
NSURLSessionTaskDelegate
Server errors are not reported through the error parameter. The only errors your delegate receives through the error parameter are client-side errors, such as being unable to resolve the hostname or connect to the host.
Well, it will also get called on any serialization errors from the response serializer.
You can take a look at AFURLSessionManagerTaskDelegate's URLSession:task:didCompleteWithError: method to see how it works.

Related

(POST Request) Parse JSON elements from a URL response in Objective-C

I am having trouble with parsing/returning JSON from a URL response. Here is an example
lets say I submit this to a server [POST not GET]
firstname=first&lastname=last&age=99
and the response from the server is this
{
"person":{
"firstname":"first",
"lastname":"last",
"info":{
"age":"99"
}
}
}
how would I retrieve this information (certain elements)
lets say I JUST want the persons age so the return string should be just "99"
or how do I JUST return the lastname or JUST the firstname, another thing how would I pass the returned element into the next POST request without the user having to type it again?
if anyone can find an example that would be fantastic :)
Thank You!
lets say the name of this data is json (you called it response). This is a dictionary. What this means is that it has key/value pairs. To access it do the following:
To get any of this information in the dictionary, all you need is a one line of code!!
To get detail of a person's first name,
[response valueForKeyPath:#"person.firstname"];
To get last name :
[response valueForKeyPath:#"person.lastname"];
To get age :
[response valueForKeyPath:#"person.info.age"];
Hmm... If it were me, I would just get the NSDictionary, then look inside the NSDictionary.
To get age:
You would want to get { "firstname":"first", "lastname":"last", "info":{ "age":"99" } }, so do:
[responseObject objectForKey:#"person"];
After you do that, you would want to get { "age":"99" }. To do that, you should use
[[responseObject objectForKey:#"person"]objectForKey:#"info"];
After that, 1 last step to get the value for age:
[[[responseObject objectForKey:#"person"]objectForKey:#"info"]objectForKey:#"age"];
And then, you have age.
To get firstname
Just find the object for key firstname by doing:
[[responseObject objectForKey:#"person"]objectForKey:#"firstname"];
To get lastname
[[responseObject objectForKey:#"person"]objectForKey:#"lastname"];
... The rest should follow the same rule.
How to pass it back to a POST request
Well, the POST request takes in an id parameters. This is where you would put the dictionary. To do this correctly without having to deal with any asynchrony, you would have to make the POST request inside the GET request. For example:
[manager GET:<your GET url>
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[manager POST:<your POST url>
parameters:responseObject
success:^(AFHTTPRequestOperation *operation, id responseObject) { NSLog(#"Success!"); }
failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(#"Error: %#", error); }];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(#"Error: %#", error); }];
Well, that's pretty much it. Hope this helped :)

Seems impossible to delete a subscription in CloudKit? `-deleteSubscriptionWithID` always returns true

I'm hoping there's an experienced CloudKit guru out there, but based off my google search queries, I'm not sure if you exist. I think this may be a bug with Apple, but I can't be sure :\
I can save a subscription to my CKDatabase fine, no problems at all.
[publicDatabase saveSubscription:subscription completionHandler:^(CKSubscription *subscription, NSError *error) {
if (error)
{
//No big deal, don't do anything.
}
else
{
[[NSUserDefaults standardUserDefaults] setObject:[subscription subscriptionID] forKey:#"SUBSCRIPTION"];
}
}];
Whenever I change a field in my record, I get a push notification, and everything is happy.
My problem is removing this subscription.
I have tried calling -deleteSubscriptionWithID:completionHandler:
As you can see in the above code snippet, I save off the subscription ID (Have also confirmed it to be the correct subscription ID by calling -fetchAllSubscriptionsWithCompletionHandler:
I passed the subscriptionID in that message, like so:
[publicDatabase deleteSubscriptionWithID:[[NSUserDefaults standardUserDefaults] objectForKey:#"SUBSCRIPTION"] completionHandler:^(NSString * _Nullable subscriptionID, NSError * _Nullable error) {
if( error ) {
NSLog(#"ERROR: %#", [error description] );
}
else
{
NSLog(#"SUCCESS: %#", subscriptionID);
}
}];
But it doesn't delete my subscription:
And no matter what I pass as the subscriptionID, there is no error and I see "SUCCESS" upon "deleting".
...so yeah. Clearly that isn't going to work.
If I manually delete the subscription through the Cloudkit Dashboard, my -fetch call properly notices that and returns an empty array:
So at this point I'm certain that I'm either deleting a subscription incorrectly in code, or it's broken and (not likely) nobody has asked on SO or any other forum that I can find?
I have also tried using a CKModifySubscriptionsOperation
CKModifySubscriptionsOperation *deleteSub = [[CKModifySubscriptionsOperation alloc] initWithSubscriptionsToSave:nil subscriptionIDsToDelete:#[[[NSUserDefaults standardUserDefaults] objectForKey:#"SUBSCRIPTION"]]];
[publicDatabase addOperation:deleteSub];
No results :(
I delete subscriptions using the database.deleteSubscriptionWithID function.
If you want to make sure that the ID is correct you could also first fetch all of them using database.fetchAllSubscriptionsWithCompletionHandler({subscriptions, error in
Then in the completion handler check if it's a valid subscription using: if let subscription: CKSubscription = subscriptionObject
And then delete one or more using: database.deleteSubscriptionWithID(subscription.subscriptionID, completionHandler: {subscriptionId, error in
Here you can see code how I delete all subscriptions:
https://github.com/evermeer/EVCloudKitDao/blob/1bfa936cb46c5a2ca75f080d90a3c02e925b7e56/AppMessage/AppMessage/CloudKit/EVCloudKitDao.swift#L897-897

Store credit card with Stripe and Parse. has no method each error

Hello stackoverflow community
Im desperately trying to create a new customer and credit card in Stripe out of an iOS app. Im luckily getting the token.
However, when running the following code for creating a credit card with its customer, I get the error "has no method '_each'" in Parse Cloud Code:
E2015-09-24T21:19:45.502Z]v10 Ran cloud function saveCardInformation with:
Input: {"cardToken":"tok_16oh81JJfrimOSDHs6YSw4v5","objectId":"asdfdf"}
Result: TypeError: Object [object Object] has no method '_each'
at request (stripe.js:58:11)
at post (stripe.js:117:12)
at Object.module.exports.Customers.create (stripe.js:239:16)
at main.js:62:22
I execute the following Parse cloud code:
//Parse Cloud code for creating new Stripe Customer and new Credit Card
var Stripe = require('stripe');
Stripe.initialize('mykey');
Parse.Cloud.define("saveCardInformation", function(request, response) {
Stripe.Customers.create({
source: request.params.cardToken,
},{
success: function(httpResponse) {
response.success("Customer successfully created!");
},
error: function(httpResponse) {
response.error(httpResponse.message);
}
});
In the corresponding iOS app, I have the following code:
STPCard *stpcard = [[STPCard alloc] init];
stpcard.number = #"4568785465487897";
stpcard.expMonth = 5;
stpcard.expYear = 2017;
stpcard.cvc = #"255";
NSLog(#"card created");
[[STPAPIClient sharedClient] createTokenWithCard:stpcard
completion:^(STPToken *token, NSError *error) {
if (error) {
NSLog(#"error, no token created");
} else {
NSLog(#"Token from callback recieved");
[self createBackendChargeWithToken:token];
}
}];
Up to here it works.
The following method is causing troubles
- (void)createBackendChargeWithToken:(STPToken *)token
{
NSDictionary *productInfo = #{#"cardToken": token.tokenId,
#"objectId": #"asdfdf"};
[PFCloud callFunctionInBackground:#"saveCardInformation"
withParameters:productInfo
block:^(id object, NSError *error) {
if (error) {
NSLog(#"error,");
return ;
}
[[[UIAlertView alloc]
initWithTitle:NSLocalizedString(#"Success",
#"Success")
message:nil
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK","OK"
otherButtonTitles:nil] show];
}];
}
Thank you very much for an answer and guidance!
Revert your cloud code version to 1.5.0 or earlier.
Explained here : Parse + Stripe iOS main.js

How to run unit tests on code that has failing assertions in Xcode?

In Xcode i am running tests on creating users based on ID. When a wrong ID is set the test should fail. Though this test fails since the method it tests has assertions in itself:
[[Apiclient sharedClient] findAndCreateUserWithID:nil success:^(Player *player) {
STFail(#"should not be able to create player with no ID");
} failure:^(NSError *error) {
}];
the method called:
- (void)findAndCreateUserWithID:(NSNumber *)ID success:(void (^)(Player *))createdPlayer failure:(void (^)(NSError *error))failure
{
NSParameterAssert(ID);
Will fail the test when parameter ID is nil. I know this is a pretty stupid example since it will always fail, but there are more assertions allready in code that are more useful. Whats the best practice on running Xcode unit tests which test code that allready has assertions?
As of late 2014, if you're using the new testing framework XCTest, you'll want to use XCTAssertThrowsSpecificNamed instead of the older STAssertThrowsSpecificNamed method:
void (^expressionBlock)() = ^{
// do whatever you want here where you'd expect an NSParameterAssertion to be thrown.
};
XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException);
NSParameterAssert throws an NSInternalInconsistencyException (source) when its assertion fails. You can test that this happens with the STAssertThrowsSpecificNamed macro. For example:
void (^expressionBlock)() = ^{
[[Apiclient sharedClient] findAndCreateUserWithID:nil success:^(Player *player) {
} failure:^(NSError *error) {
}];
};
STAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException, nil);
I use an expression block there to make it easier to put that much code in the macro.

Having problems integrating with SimpleUPC API on iOS

So I'm relatively new to this and I'm running into a problem that has me pretty stumped. SimpleUPC provides a pretty simple API but they have the following format for a JSON request:
{
"auth":"Your API Key",
"method":"MethodName",
"params": {
"paramName":"paramValue",
"paramName2":"paramValue2",
},
"returnFormat":"optional"
}
I also download their Ruby sample which I have verified does work from the command line.
# Sample API calls using JSON...
host = "api.simpleupc.com"
path = "/v1.php"
# An example query for FetchProductByUPC method
request = {
"auth" => 'Your-API-Key',
"method" => 'FetchProductByUPC',
"params" => {"upc" => '041383096013'}
}
json = request.to_json()
response = Net::HTTP.start(host) { |http|
http.post(path, json, initheader = {'Content-Type' =>'text/json'})
}
output = response.body
puts output
Ok so far so good. But here's my code that is trying to do the same thing but I am getting errors complaining about missing parameters.
NSDictionary *requestDict = #{#"auth": kSimpleAPIKey, #"method": #"FetchProductByUPC", #"params":#{#"upc": code}};
[[SimpleUPCClient sharedClient] getPath:#""
parameters:requestDict
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Response: %#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[TMReportingHandler handleError:error fatal:NO];
}];
I know this has got to be something simple I'm doing wrong but I can't figure it out. Anyone?
Wow, I knew it was something simple. Turns out it was expecting a POST instead of a GET...I switched it and it worked immediately.

Resources