Core Data & RestKit: Multiple relation to same object - ios

I have a problem with the object mapping from a JSON string to a NSManagedObject. I use RestKit via getObject request and the Core Data integration. And I want to automatically map the JSON response.
1.)
I get from a webservice the following response ("A" object):
{
"Bs":[
{ "id" : "abc", "name" : "name 1"},
{ "id" : "def", "name" : "name 2"}
{ "id" : "abc", "name" : "name 1"},
],
"id": "1"
}
2.)
This response has ordered values (B Objects), with sometimes the same object ("id" : "abc") more than one time. Further more, the order of the items is important.
3.)
Core-Data has not the support to save multiple relations to the same object, because it used a NSSet (NSOrderedSet). And it ignores all double objects.
Has anyone an idea how I could solve the problem?
My try, which fails:
1.)
I insert a new core data table (AB), which:
has a reference to A
has a position field
has a reference to one B, from the A
2.)
I try to map the object with a RKValueTransformer instance.
This iterates over the JSON response for the B instances and create AB objects with the current position. These objects are saved in an NSSet, which return from the custom value transformer
RKValueTransformer *aabbTransformer = [RKBlockValueTransformer valueTransformerWithValidationBlock:^BOOL(__unsafe_unretained Class sourceClass, __unsafe_unretained Class destinationClass) {
return ([sourceClass isSubclassOfClass:[NSArray class]] && [destinationClass isSubclassOfClass:[NSOrderedSet class]]);
} transformationBlock:^BOOL(id inputValue, __autoreleasing id *outputValue, Class outputValueClass, NSError *__autoreleasing *error) {
// Validate the input and output
RKValueTransformerTestInputValueIsKindOfClass(inputValue, [NSArray class], error);
RKValueTransformerTestOutputValueClassIsSubclassOfClass(outputValueClass, [NSOrderedSet class], error);
NSMutableOrderedSet *outputSet = [[NSMutableOrderedSet alloc] init];
NSInteger pos = 1;
for (id b in inputValue) {
// see JSON output at the top
// B instance already exists in core data persistent store
NSString *bid = [b valueForKeyPath:#"id"];
B *b = [B bById:bid];
// create AB instance
AB *ab = [NSEntityDescription ...]
ab.b = b;
ab.position = [NSNumber numberWithInteger:pos];
[outputSet addObject:ab];
pos++;
}
// return for A.abs
*outputValue = [[NSOrderedSet alloc] initWithOrderedSet:outputSet];
return YES;
}];
RKAttributeMapping *aabbMapping = [RKAttributeMapping attributeMappingFromKeyPath:#"bs" toKeyPath:#"abs"];
aabbMapping.valueTransformer = aabbMappingTransformer;
3.) But I get an error:
illegal attempt to establish a relationship 'abs' between objects in different contexts
But I use always the same context.
If you don't have an better idea, do you have a solution for this problem?

Your proposed model structure is a sensible solution for your duplicate data requirement. The only change you should make there is to ensure that all relationships are double ended (have an inverse) and have appropriate multiplicity. Be sure that the relationship from B to AB is to-many. The relationship from A to AB should also be to-many.
The general approach to this is to use multiple response descriptors:
One to create the A object and AB objects, key path is nil
One to create the B objects without any duplication, key path is Bs
The response descriptor to create the A object has a nested relationship mapping to create the AB objects with duplicates and order - for this you can not use any identification attributes for AB and you need to use the mapping metadata provided by RestKit.
Once the objects are created they then need to be connected and that would be done with a foreign key mapping. That means storing a transient attribute on the AB instances which contains the id for the appropriate B object and using that to make the connection.
Note that after updates you may have some orphaned objects (because you can't use identification attributes for AB) in the data store as a result of this manipulation and you should consider creating a fetch request block to purge them out (or do this yourself periodically).

Related

How to get data from unrelated entities using Core Data?

I have these three entities which are not related. How can i get the salary of an employee. I want something like this :
Select C.salary FROM Employee A, Department B, Salaries C Where A.id=B.empid AND B.id=C.deptid AND A.id=12
i need to do the same above operation in core data.
As Martin suggested, the easy way is to set up the needed relationships and traverse them.
But if you don't have the permission to alter the model, you need to work and filter managed in memory object you own once retrieved. In other words, you need to set up NSFetchRequests with the correct NSPredicates.
For example, to retrieve the department for a given employee.
NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:#"Department"];
request.predicate = [NSPredicate predicateWithFormat:#"empid", #(12)];
NSError *error = nil;
NSArray *departments = [moc executeFetchRequest:request error:&error];
if(departments) {
// do a NSLog for departments here to see what you have
// here you can access deptid for each department you retrieve
// and with that value run another request against Salaries
// e.g. NSManagedObject* department = [departments lastObject];
// NSNumber* deptid = [department valueForKey:#"deptid"]
} else {
// handle error here
}
Other part is left as excersise ;)

Fetching entities with different number of parameters

I have several entities with more or less the same parameters but in different numbers.
For example:
EntityA : model, code, color, name
EntityB : model, code, color, name, country
EntityC : model, code, color, name, country, style, date
Because of this, I did not create classes for the entity, so it is convenient to add stuff to "entityA" by doing this:
NSArray *keys = #[#"model", #"code", #"color", #"name"];
NSManagedObject *newObjectOnEntity = [NSManagedObject newObjectOnEntity:#"entityA"
inManagedObjectContext:self.managedObjectContext];
NSDictionary *dict = [NSDictionary dictionaryWithObjects:values forKeys:keys];
[newObjectOnEntity setValuesForKeysWithDictionary:dict];
I simply add more entries to keys and values and I can use the same code to add stuff to all entities.
But now comes the fetch part.
At this point I need to do a fetch like this on lets say entityA:
fetch entry for (model = "abc") && (code = "zoltrix") && (color = "blue") && (name = "roger")
if the entity is B, the fetch will also have && (country = "usa") and if entity is C will also have style and date.
So what I am asking is if I can have a fetch where I pass an array of parameters, an array of values and it searches for the entry on that entity that matches all parameters and values (parameter1 == value1) && (parameter2 == value2) ...
So what I am asking is if I can have a fetch where I pass an array of parameters, an array of values and it searches for the entry on that entity that matches all parameters and values
Sure you can. In the NSPredicate syntax you can specify the field name with %K and the value with %# (or other format specifiers).
(Check out "Dynamic Property Names" in the "Predicate Programming Guide".)

RestKit: Ignoring some dynamic nesting attributes

I receive JSON objects like this:
{
"rent": {
"id": "someId"
},
"upcoming": {
"id": "someId"
},
"watchnow": {
"id": "someId"
}
}
I then set forceCollectionMapping to YES on my mapping to get one object for each key, i.e. one object for "rent", one for "upcoming" and one for "watchnow". Specifically this is done with this code:
[searchResultsMapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"searchSection"];
So this succesfully gives me three objects for which I can then do some relationship mapping to get the id keys and what ever else is on the object.
Now, my problem is that if an error occurs, I get this JSON code:
{
"error": {
"errorcode": "someId"
}
}
So (searchSection) becomes "error" and my relationship mapping looks for "id" but it's not there so the mapping fails. The problem is that setting addAttributeMappingFromKeyOfRepresentationToAttribute makes RestKit try to make an object from every single key, and I can't expect every key to be relevant and useful for my mappings. Can I do anything about this?
You have a couple of options:
Use an RKDynamicMapping as the root mapping for your response descriptor
Use multiple response descriptors to specify exactly which keypaths to process
Use KVC validation to reject the error mapping (not ideal as the error isn't really captured)
For the dynamic mapping option, the dynamic mapping has the forceCollectionMapping option set and it checks the top level key available and returns the appropriate mapping (which wouldn't have forceCollectionMapping set and which uses addAttributeMappingFromKeyOfRepresentationToAttribute:).
I got it to work using Wain's first suggestion. Here's the solution if anyone has the same issue:
I created a dynamic mapping like this:
RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
dynamicMapping.forceCollectionMapping = YES;
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping * (id representation) {
if ([representation valueForKey:#"watchnow"] || [representation valueForKey:#"upcoming"] || [representation valueForKey:#"rent"]) {
return searchResultsMapping;
}
return nil;
}];
As you can see in my example at the top, I'm only interested in keys named "watchnow", "upcoming" or "rent".
The searchResultsMapping which is returned is configured like this:
RKObjectMapping *searchResultsMapping = [RKObjectMapping mappingForClass:[TDXSearchResults class]];
[searchResultsMapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"searchSection"];
So I now end up with three SearchResult objects with either "watchnow", "upcoming" or "rent" in their searchSection NSString property.

How to check to see if an object exists in NSMutableArray without knowing the index, and replace it if it exists, in iOS?

I have an NSMutableArray that contains objects of type Person. The Person object contains parameters of NSString *name, NSString *dateStamp, and NSString *testScore. What I would like to do using fast enumeration, is to check to see in the NSMutableArray *testResults, is see if an object with the same name parameter exists.
If it does, then I want to replace the existing object in the NSMutableArray, with the object that I am about to insert which will have the most current dateStamp, and testScore values. If an object with no matching name parameter is found, then simply insert the object that I have.
My code so far looks like this:
This is the code that creates my object that I am about to insert:
Person *newPerson = [[Person alloc] init];
[newPerson setPersonName:newName]; //variables newName, pass, and newDate have already been
[newPerson setScore:pass]; //created and initialized
[newPerson setDateStamp:newDate];
and here is the code where I try to iterate through the NSMutableArray to see if an object with the same name parameter already exists:
for (Person *checkPerson in personList) { //personList is of type NSMutableArray
if (newPerson.newName == checkPerson.name) {
//here is where I need to insert the code that replaces checkPerson with newPerson after a match has been found
}
else {
personList.addObject(newPerson); //this is the code that adds the new object to the NSMutableArray when no match was found.
}
}
It's not a very complicated problem, but I am confused as to how to go about finding a match, and then replacing the actual object without knowing ahead of time what index the object resides in.
You want to use indexOfObjectPassingTest to look for a match:
NSInteger indx = [personList indexOfObjectPassingTest:^BOOL(Person *obj, NSUInteger idx, BOOL *stop) {
return [obj.name isEqualToString:newPerson.name];
}];
if (indx != NSNotFound) {
[personList replaceObjectAtIndex:indx withObject:newPerson];
}else{
[personList addObject:newPerson];
}
Notice that I used isEqualToString: to compare the two strings not ==. That mistake has been asked about and answered a million times on this forum.
You have some inconsistency in your naming. In the question, you say the Person objects have a name property, but when you create a new person you use setPersonName, which would imply that the property name is personName. I assumed, just name in my answer.

NSMutableArray or NSMutableDictionary : which is best for this scenario?

I need to scroll through several thousands of words to categorize them... to determine which words have the same pattern. (this part works)
For example, a four letter word that has two m's in 2nd & 4th position represent a pattern ("-m-m"). Once I have gone through all the words, I will know how many words there are for any given pattern. I am scrolling through now, but the problem I have is 'remembering' how many words I have in any given pattern.
I was thinking of using NSMutableDictionary and have the key be the pattern ('-m-m-') and the object represent the count of that pattern. This means every time I come across a pattern, I look up that pattern in the dictionary, get the key, increment the key, and put it back in the dictionary.
I need help with both the decision and syntax for performing this task.
Thank You
The answer to your question was this part of your (given) question "I will know how many words there are for any given pattern.". I would use an array of dictionary. You use the dictionary to store key value pair: a known pattern and the count. And you use the array to store those KVP records. So the next time you detect a pattern, search for the array for that record (dictionary), if found, increment the count. If not, create new record and set the count to 1.
Added sample code:
#define kPattern #"Pattern"
#define kPatternCount #"PatternCount"
-(NSMutableDictionary *)createANewDictionaryRecord:(NSString *) newPattern
{
int count = 1;
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
newPattern, kPattern,
[NSString stringWithFormat:#"%i",count], kPatternCount,
nil];
return myDictionary;
}
-(void)addANewPatternToArray:(NSMutableDictionary *)newDictionary
{
// NSMutableArray *myArrayOfDictionary = [[NSMutableArray alloc]init]; // you need to define it somewhere else and use property etc.
[self.myArrayOfDictionary addObject:newDictionary]; //or [self.myArrayOfDictionary addObject:newDictionary]; if you follow the recommendation above.
}
-(BOOL)existingPatternLookup:(NSString *)pattern
{
for (NSMutableDictionary *obj in self.myArrayOfDictionary)
{
if ([[obj objectForKey:kPattern] isEqual:pattern])
{
int count = [[obj objectForKey:kPatternCount] intValue] + 1;
[obj setValue:[NSString stringWithFormat:#"%i",count] forKey:kPatternCount];
return YES;
}
}
[self.myArrayOfDictionary addObject:[self createANewDictionaryRecord:pattern]];
return NO;
}
-(void)testData
{
NSMutableDictionary *newDict = [self createANewDictionaryRecord:#"mmm"];
[self addANewPatternToArray:newDict];
}
-(void) printArray
{
for (NSMutableDictionary * obj in self.myArrayOfDictionary)
{
NSLog(#"mydictionary: %#", obj);
}
}
- (IBAction)buttonPressed:(id)sender
{
if ([self existingPatternLookup:#"abc"])
{
[self printArray];
} else
{
[self printArray];
}
}
Not being an objective C expert but solving this problem in java before, I would say a dictionary(I used a map when doing it in java) is the best way. Check if the key(pattern) already exist if so increment that count else put a new one in the dictionary.
EDIT
If you want to not just get the count of a pattern, but in fact tell which words fall under that pattern, I would use a dictionary of strings to mutable arrays. In the arrays you store the words and the key to the array is the pattern(as a string), similar code as above but instead of just incrementing the count, you have to add the new word to the array.
The only difference in NSDictionary and NSMutableDictionary is that one can have objects added to it. I think your implementation is good, but English is a complex language. It would be more efficient to parse out the string with regex than to set a key for it.
Why don't you use NSCountedSet instead:
NSCountedSet Class Reference
..which is available in iOS 2.0 and later?
Each distinct object inserted into an NSCountedSet object has a counter associated with it. NSCountedSetkeeps track of the number of times objects are inserted [...] Thus, there is only one instance of an object in an NSSet object even if the object has been added to the set multiple times. The count method defined by the superclass NSSet has special significance; it returns the number of distinct objects, not the total number of times objects are represented in the set.
Then use:
- (NSUInteger)countForObject:(id)anObject
Use a dictionary of NSMutableArrays, and check for the existence of each search key as you recommended. If the key doesn't exist, add an NSMutableSet or NSMutableArray (depending on your needs) for the searched key type "-m-m" for example, and then add to the set or array for that key.

Resources