Skip to content
This repository has been archived by the owner on Sep 12, 2018. It is now read-only.

Dictionaries nested more than 1 level deep break findOrCreate: #60

Closed
kattrali opened this issue Dec 27, 2013 · 11 comments
Closed

Dictionaries nested more than 1 level deep break findOrCreate: #60

kattrali opened this issue Dec 27, 2013 · 11 comments

Comments

@kattrali
Copy link
Contributor

Right now OR handles creating nested entity objects during update: using the classes specified in an entity's mappings block. However, when a dictionary contains multiple levels of objects, the nested dictionaries aren't processed recursively, and are treated like values for entity property keys, so -[NSManagedObject predicateForDictionary:] returns a format like: id == 4 AND type == Cajun AND owner == { id = 6 }, resulting in a crash, since the owner value is not in a valid format.

Example

Given I have a Restaurant class, an Owner class, and a Car class, where a Restaurant has an Owner and an Owner has a Car

@implementation Restaurant
+ (NSDictionary *)mappings {
        return @{ @"id" : @"restaurantID",
                          @"owner" : @{ @"class" : [Owner class]}};
}
@end

@implementation Owner
+ (NSDictionary *)mappings {
        return @{ @"id" : @"ownerID",
                          @"car" : @{ @"class" : [Car class]}};
}
@end

@implementation Car
+ (NSDictionary *)mappings {
        return @{ @"id" : @"carID" };
}
@end

When I want to update my Restaurant instance with a dictionary that looks like so:

[restaurant update:@{
        @"id" : @4, 
        @"type" : @"Cajun",
        @"owner" : @{ 
                @"id" : @6, 
                @"full_name" : @"Rory Hill"
                @"car" : @{ @"id" : @43, @"model" : @"3"}}}];

Then I should have one Restaurant, one Owner, and one Car when the update is complete.

@stephencelis
Copy link
Collaborator

Fixed via #87, thanks!

@Tylerc230
Copy link

This still seems to be an issue on 1.5 using models without primary keys. Is that expected behavior? I receive:
2014-04-24 14:34:53.669 ZuliApp[17833:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
*** First throw call stack:
(
0 CoreFoundation 0x032e11e4 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x0303d8e5 objc_exception_throw + 44
2 CoreData 0x0117062c -[NSSQLGenerator newSQLStatementForFetchRequest:ignoreInheritance:countOnly:nestingLevel:] + 1308
3 CoreData 0x0116ffb8 -[NSSQLAdapter _newSelectStatementWithFetchRequest:ignoreInheritance:] + 472
4 CoreData 0x0116fdd0 -[NSSQLAdapter newSelectStatementWithFetchRequest:] + 48
5 CoreData 0x0116fb91 -[NSSQLCore newRowsForFetchPlan:] + 129
6 CoreData 0x0116f31d -[NSSQLCore objectsForFetchRequest:inContext:] + 701
7 CoreData 0x0116edcf -[NSSQLCore executeRequest:withContext:error:] + 383
8 CoreData 0x0116e7f2 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 4466
9 CoreData 0x0116bf56 -[NSManagedObjectContext executeFetchRequest:error:] + 566
10 ZuliApp 0x0023a368 +[NSManagedObject(ActiveRecord) fetchWithCondition:inContext:withOrder:fetchLimit:] + 520
11 ZuliApp 0x00237ad4 +[NSManagedObject(ActiveRecord) where:inContext:order:limit:] + 212
12 ZuliApp 0x00237744 +[NSManagedObject(ActiveRecord) where:inContext:] + 148
13 ZuliApp 0x00236e22 +[NSManagedObject(ActiveRecord) findOrCreate:inContext:] + 194
14 ZuliApp 0x0023b554 +[NSManagedObject(Mappings) objectOrSetOfObjectsFromValue:ofClass:inContext:] + 292
15 ZuliApp 0x0023b87a __77+[NSManagedObject(Mappings) objectOrSetOfObjectsFromValue:ofClass:inContext:]_block_invoke + 106
16 ZuliApp 0x0023d1db -[NSArray(ObjectiveSugar) map:] + 443
17 ZuliApp 0x0023b643 +[NSManagedObject(Mappings) objectOrSetOfObjectsFromValue:ofClass:inContext:] + 531
18 ZuliApp 0x0023b393 +[NSManagedObject(Mappings) transformValue:forRemoteKey:inContext:] + 323
19 ZuliApp 0x00239071 +[NSManagedObject(ActiveRecord) transformProperties:withContext:] + 1089
20 ZuliApp 0x00238346 -[NSManagedObject(ActiveRecord) update:] + 246
21 ZuliApp 0x00238102 +[NSManagedObject(ActiveRecord) create:inContext:] + 210
22 Zuli

@stephencelis
Copy link
Collaborator

Can you provide an example of the code that's calling update:?

@Tylerc230
Copy link

My graph: Network has many Rooms, Room has many Devices.

   NSArray *bedroomDevices = @[@{@"name": @"TV"}];
    NSArray *roomData = @[@{@"devices":bedroomDevices}];
    NSDictionary *networkData = @{@"rooms": roomData};
    self.network = [Network create:networkData inContext:self.fakeDataContext];

My Room mappings:

+ (NSDictionary *)mappings
{
    return @{@"devices": @{@"class": [Device class]}};
}

My Network mappings:

+ (NSDictionary *)mappings
{
    return @{@"rooms": @{@"class": [Room class]}};
}

@Tylerc230
Copy link

It seems to be the findOrCreate: call calling where: that things go south. It attempts to do a property by property query and when it reaches the devices property it breaks since (I don't believe) you can compare to many relationships in a Predicate.

@dzenbot
Copy link

dzenbot commented Aug 5, 2014

Were you able to fix this @Tylerc230 ?
I tried several things, with no success...

dzenbot pushed a commit to tinyspeck/ObjectiveRecord that referenced this issue Aug 6, 2014
…of deepness. Fixes supermarin#60 with error message 'to-many key not allowed here'
@dzenbot
Copy link

dzenbot commented Aug 6, 2014

We manage to fix this, with:

+ (NSPredicate *)predicateFromDictionary:(NSDictionary *)dict
{
    NSArray *subpredicates = [dict map:^(NSString *key, id value) {
        if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSSet class]]) {
            return (id)[NSPredicate predicateWithFormat:@"%@ IN %K", value, key];
        }
        else {
            return (id)[NSPredicate predicateWithFormat:@"%K = %@", key, value];
        }
    }];

    return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
}

This will preserve one-to-many relationships fetching without crashing when deeper level.

@supermarin
Copy link
Owner

@dzenbot hey Ignacio, sorry I was on a long vacation. will take a look on this today

@donly
Copy link

donly commented Aug 15, 2015

I still have the 'to-many key not allowed here' error there

// Car+Mappings.m
@implementation Car (Mappings)

+ (NSDictionary *)mappings {
    return @{
             @"seats": @{@"class": [Seat class]},
             @"owner": @{@"class": [Person class]
        },
    };
}

@end

// Seat+Mappings.m
@implementation Seat (Mappings)

+ (NSDictionary *)mappings {
    return @{
             @"car": @{@"class": [Car class]},
             };
}

@end

// using the below JSON data
NSDictionary *JSON = @{
                           @"first_name": @"Marin",
                           @"last_name": @"Usalj",
                           @"age": @25,
                           @"is_member": @"true",
                           @"cars": @[
                                   @{ @"hp": @220, @"make": @"Trabant",
                                      @"seats": @[
                                              @{@"orderId": @1},
                                              @{@"orderId": @2},
                                              ]},
                                   ],
                           };

    Person *person = [Person create:JSON];

Am I something wrong here

@Alexandeer
Copy link

For those who are faced with this problem. Find a different framework. This crude.

@Alexandeer
Copy link

Alexandeer commented Apr 20, 2016

  • (id)objectOrSetOfObjectsFromValue:(id)value ofClass:class inContext:(NSManagedObjectContext *)context {
    if ([value isKindOfClass:class])
    return value;

    if ([value isKindOfClass:[NSDictionary class]])
    return [class findOrCreate:value inContext:context];

    if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSSet class]])
    return [NSSet setWithArray:[value map:^id(id object) {
    return [self objectOrSetOfObjectsFromValue:object ofClass:class inContext:context];
    }]];

    if ([class class] == [NSDate class])
    return [NSDate dateWithTimeIntervalSince1970:[value floatValue]];

    return [class findOrCreate:@{ [class primaryKey]: value } inContext:context];
    }

This code helped me.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants