RestKit: How to handle failure result with different mapping - restkit

I'm fetching a user object from a server. If the request succeeds I get:
{ user = {
User = { id = 1; }
} }
On failure I get:
{ user = false; }
I have a mapping that works fine in the success case: [mappingProvider setMapping:userMapping forKeyPath:#"user.User"];
But in the failure case, I get the following exception: 'NSUnknownKeyException', reason: '[<__NSCFBoolean 0x1c58f78> valueForUndefinedKey:]: this class is not key value coding-compliant for the key User.' I also tried setting userMapping as a relationship for keypath User on a mapping to generate an NSDictionary from the user keypath, no joy there either.
What is the correct way to deal with this? It seems like I need two different mappings for the user keypath, and to select which one based on whether on whether the object wants to be decoded to an NSBoolean...

I would suggest RKDynamicObjectMapping. Either of one of these two flavors should work for you:
- (void)setObjectMapping:(RKObjectMapping *)objectMapping whenValueOfKeyPath:(NSString *)keyPath isEqualTo:(id)value;
or
+ (RKDynamicObjectMapping *)dynamicMappingUsingBlock:(void(^)(RKDynamicObjectMapping *dynamicMapping))block;

The server is probably (and hopefully) returning an http error code such as 400 etc.. so probably Restkit is trying to map the response using the error mapping.
Perhaps what you need to do is dynamic object mapping as #Paul says..but applied to the error mapping. I here leave you a simple error mapping so you know what I'm talking about:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping mapKeyPath:#"msg" toAttribute:#"errorMessage"];
[[[RKObjectManager sharedManager] mappingProvider] setErrorMapping:errorMapping];

Related

Enabling nested any queries on OData V4 endpoints with WebAPI

I'm trying to build a nested any query like this ...
~/Api/Calendar?$filter=Roles/any(r:r/User/any(u:u/Name eq 'Joe
Bloggs'))
if I remove the inner any clause leaving me with ...
~/Api/Calendar?$filter=Roles/any(r:r/User/any())
... then the endpoint returns ...
{
"error": {
"code": "",
"message": "The query specified in the URI is not valid. The Any/All nesting limit of '1' has been exceeded. 'MaxAnyAllExpressionDepth' can be configured on ODataQuerySettings or EnableQueryAttribute."
}
}
... which I think lends some light on the problem but I actually have here.
So far I have tried to raise this limit with this during my context initialisation but it doesn't appear to be working ....
config.AddODataQueryFilter(new EnableQueryAttribute { MaxAnyAllExpressionDepth = 3 });
does anyone have any ideas how I can do this globally (i don't want to have to go to every get action on every controller and set the depth.
UPDATE:
So it turns out where I inherit from my own baseEntityController, on the actions I had the EnableQuery attribute which superceeded my global config change hense the reason my changes were not respected.
Simply removing the attribute from the actions themselves has all controllers that inherit from my base working with this new nested any and all limit, but i seem to now have the side effect that expands don't work any more ...
var query = new EnableQueryAttribute {
MaxExpansionDepth = 8,
PageSize = 100,
MaxAnyAllExpressionDepth = 3,
AllowedFunctions = System.Web.OData.Query.AllowedFunctions.All,
AllowedLogicalOperators = System.Web.OData.Query.AllowedLogicalOperators.All,
AllowedQueryOptions = System.Web.OData.Query.AllowedQueryOptions.All,
AllowedArithmeticOperators = System.Web.OData.Query.AllowedArithmeticOperators.All,
MaxTop = 1000
};
config.AddODataQueryFilter(query);
.. as you can see I tried throwing lots of extras in there but it's not having any of it!
The simplest way I found to do this and have everything work is to apply the attribute on the base controller actions, it therefore applied everything correctly to the actions on that controller or any of it's derived types.
It wasn't my ideal but I couldn't find a way to get a global fix for this to work as part of initialising the odata context.
Hopefully this will help someone out there.

NSManagedObjectContext save unsuccessful, but returns nil error

I am developing an OS X Application that uses a single-threaded Core Data model without nested contexts.
I am creating objects on the main thread in the defaultContext and try to save them after creation, but the save fails without returning an error. I have not overwritten any methods in my CoreData objects, but I am using the latest version of MagicalRecord.
The code that fails:
// pseudocode for createOrFetchWithData:inContext:
// fetch object from value in objectDict
// if(!object) create project in context
// [object importValuesForKeysWithObject:objectData] // MR method
// return object
MyObject *object = [MyObject createOrFetchWithData:objectData
inContext:[NSManagedObjectContext defaultContext]];
if(!object) return; // just to emphasise that I am sure the object is not nil.
[[NSManagedObjectContext defaultContext] saveOnlySelfWithCompletion:^(BOOL saveSuccessful, NSError *error) {
if(saveSuccessful) {
NSLog(#"yay");
} else {
NSLog(#"nay");
}
}];
The return value of [NSManagedObjectContext defaultContext] is not nil and I have verified that the code is executed on the main thread.
I have a relationship that isn't set in the MyObject *object, but it is marked as optional in the data model.
Any idea what might cause this simple operation to fail? I have other entities that save just fine, but this particular case fails.
Note: I am running OS X Mavericks DP 8.
In the end, it was my misunderstanding of MagicalRecord's (and possibly CoreData's) implementation of the save: methods: in case there is no change to the context ([context hasChanges] == NO) AND the parent context (!!), the save will be aborted, and the completion block is called with NO for successful and no error object.

Unable to use reference from a Make New command in Core-data app with AppleScript support

I am able to support the Make New command of AppleScript for my app, however the returned 'specified object' (an NSUniqueIDSpecifier) for the core data managed object is useless. The following AppleScript returns the error message:
error "SpellAnalysis got an error: Invalid key form." number -10002 from level id "x-coredata:///Levels/tC5A49E01-1CE1-4ED6-8F6B-BC0AE90E279A2"
tell application "SpellAnalysis"
set thisLevel to make new «class Slev» with properties {«class Saln»:3}
get properties of thisLevel
end tell
So the newly created Levels object can not be acted upon in AppleScript. I've combed the Web for a solution to this and the closest thing I have found is Bill Cheeseman's example app, "WareroomDemo" which specifically deals with Cocoa Scriptability for Core Data apps (the Sketch example does not use Core Data). Unfortunately, it is a dated example, running only on pre-64-bit XCode and I can't actually run it--I can only look at the code. His app's Make Command may have the same limitations for all I know.
The returned 'objectSpecifier' is unable to refer to the created object either as a safe-guard against corrupting Core Data's organizing scheme, or perhaps because the returned object is an un-cashed 'fault'. I think the latter possibility is unlikely because I can force the fault to cash (by getting a property value on the managed object) , yet I get the same error message with the AppleScript.
Here is the method that creates my class:
- (id)newScriptingObjectOfClass:(Class)class forValueForKey:(NSString *)key withContentsValue:(id)contentsValue properties:(NSDictionary *)properties { // Creates a new Lesson object in response to the AppleScript 'make' command.
// Documentation for 'newScriptingObject…' states that to create a new class object when using Core Data, you intercede using the following method (or you can subclass the NSCreateCommand's 'performDefaultImplementation' method and put your NSManagedObject init code there):
if (class == [Levels class]) {
//NSLog(#"class: %#",class);
NSEntityDescription *levelsEntity = [NSEntityDescription
entityForName:#"Levels"
inManagedObjectContext:levelsDBase];
NSManagedObject *levelObject = [[NSManagedObject alloc] initWithEntity:levelsEntity insertIntoManagedObjectContext:levelsDBase];
SLOG(#"lessonObject: %#", lessonObject);
NSString *levelNumberString = [[properties objectForKey:#"levelNumber"] stringValue];
SLOG(#"levelNumberString: %#", levelNumberString);
[levelObject setValue:levelNumberString forKey:#"levelNumber"];
return levelObject; // When using Core Data, it seems that you return the newly created object directly
}
return [super newScriptingObjectOfClass:(Class)class forValueForKey:(NSString *)key withContentsValue:(id)contentsValue properties:(NSDictionary *)properties];
}
Here is my object specifier method:
- (NSScriptObjectSpecifier *)objectSpecifier {
// This NSScriptObjectSpecifiers informal protocol returns a unique ID specifier specifying the absolute string of the URI representation of this managed object. // AppleScript return value: 'level id <id>'.
// The primary container is the application.
NSScriptObjectSpecifier *containerRef = nil; // I understand that if the application is the container, this is value you use for the container reference
NSString *uniqueID = [[[self objectID] URIRepresentation] absoluteString];
return [[[NSUniqueIDSpecifier alloc] initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[NSApp class]] containerSpecifier:containerRef key:#"levelsArray" uniqueID:uniqueID] autorelease];
}
The problem lies with the specifier method. The Sketch example actually uses the technique that I needed. I overlooked it many times because I didn't see how it would apply to Core Data managed objects. Instead of returning the objects uniqueID, you make it return the managedObject index using the 'indexOfObjectIdenticalTo:' method as follows:
- (NSScriptObjectSpecifier *)objectSpecifier {
NSArray *levelsArray = [[NSApp delegate] levelsArray]; // Access your exposed to-many relationship--a mutable array
unsigned index = [levelsArray indexOfObjectIdenticalTo:self]; // Determin the current objects index
if (index != (unsigned)NSNotFound) {
// The primary container is the document containing this object's managed object context.
NSScriptObjectSpecifier *containerRef = nil; // the appliation
return [[[NSIndexSpecifier allocWithZone:[self zone]] initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[NSApp class]] containerSpecifier:containerRef key:#"levelsArray" index:index] autorelease];
} else {
return nil;
}
}
Note that this method resides within a subclass of your Core Data managedObject--in this case, the 'Levels' class. The 'self' within the 'indexOfObjectIndenticalToSelf:' method refers to the current managedObject ('Levels') being handled. Also, be sure to provide the specifier (accessor) type to your 'sdef' file, like this:
<element type="level">
<cocoa key="levelsArray"/>
<accessor style="index"/>
</element>

LinqToSql update problem when switching parents

I am trying to perform a straighforward update using LinqToSQL, and just cannot get it to work.
Here's the data model: there is a timesheet_entry and customer. One customer has many timesheet_entries. It's a simple foreign key relationship.
If I'm editing an existing timesheet_entry, I get an exception if I change the customer_id.
Here's my attempt at the code. Can someone help me out here?
internal void CommitChangesToEntry(Timesheet_Entry entry)
{
Timesheet_Entry latest = (from e
in m_dataContext.Timesheet_Entries
where e.Timesheet_Entry_ID == entry.Timesheet_Entry_ID
select e).First();
latest.Entry_Start_DateTime = entry.Entry_Start_DateTime;
latest.Entry_End_DateTime = entry.Entry_End_DateTime;
latest.Task_Description = entry.Task_Description;
// Comment out this line of code and it
// doesn't throw an exception
latest.Customer_ID = entry.Customer_ID;
m_dataContext.SubmitChanges(); // This throws a NotSupportedException
}
The error is: "An attempt has been made to attach or add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported".
Do you have any reason for not using the Attach method? Like the following:
m_dataContext.Timesheet_Entries.Attach(entry);
m_dataContext.SubmitChanges();

CRM: Get children throws exception on no children

We're using MSCRM Dynamics, and we're trying to get all the children of a particular user. (A user has a manager, the manager has 'children'.) The following works, but throws an exception if the user has no children. This seems logical at first, maybe, but why not just return an empty set? And of all things, it throws a SoapException with a cryptic message of "Invalid Argument" (which is wrong) and the .Detail.InnerText says "0x80040203 The value passed for ConditionOperator.In is empty Platform". If you look at the corresponding Response class, it has a collection -- why not just leave it empty?
// Create the request object.
RetrieveAllChildUsersSystemUserRequest retrieve =
new RetrieveAllChildUsersSystemUserRequest();
// Create the column set object that indicates the fields to be retrieved.
ColumnSet cols = new ColumnSet();
cols.EntityName = "systemuserid";
// Set the column set.
retrieve.ColumnSet = cols;
// Set the ID of the parent user.
retrieve.EntityId = context.UserId;
RetrieveAllChildUsersSystemUserResponse retrieved =
new RetrieveAllChildUsersSystemUserResponse();
/// Execute the request.
/// Catches if user does not have children
/// (Check to see if user is manager)
try
{
retrieved =
(RetrieveAllChildUsersSystemUserResponse)crmService.Execute(retrieve);
}
catch (System.Web.Services.Protocols.SoapException e)
{
throw new Exception(string.Format("{0}", e.Detail.InnerText));
}
I agree, it should probably just return an empty result. My guess would be under the hood, some step of executing the request or preparing the response is translated into a QueryExpression. QueryExpressions blow up if you use a ConditionExpression that uses ConditionOperator.In and you pass it an empty list. So it may be something like it gets a list of child systemuser guids, which in some cases is an empty list, then tries to retrieve all attributes of the system users in that list using another QueryExpression and that is what throws the exception.
You can probably design a QueryExpression or FetchXML of your own that will net you the same results without the side effect of it throwing an exception when the list is empty, or just catch the exception and check for that particular error code and swallow it.

Resources