Skip to content
This repository was archived by the owner on May 20, 2021. It is now read-only.

Commit 68a1eea

Browse files
Check for existence of destination objects and avoid using nullify delete rule (fixes #23)
1 parent a30b2c2 commit 68a1eea

File tree

4 files changed

+115
-21
lines changed

4 files changed

+115
-21
lines changed

Examples/Todos/Todos/Todos.xcdatamodeld/Todos.xcdatamodel/contents

+7-7
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
<attribute name="name" attributeType="String">
66
<userInfo/>
77
</attribute>
8-
<relationship name="todos" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Todo" inverseName="list" inverseEntity="Todo" syncable="YES">
8+
<relationship name="todos" optional="YES" toMany="YES" deletionRule="No Action" destinationEntity="Todo" inverseName="list" inverseEntity="Todo" syncable="YES">
99
<userInfo>
1010
<entry key="storage" value="false"/>
1111
</userInfo>
1212
</relationship>
13-
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="privateLists" inverseEntity="User" syncable="YES"/>
13+
<relationship name="user" optional="YES" maxCount="1" deletionRule="No Action" destinationEntity="User" inverseName="privateLists" inverseEntity="User" syncable="YES"/>
1414
<userInfo/>
1515
</entity>
1616
<entity name="Todo" representedClassName="Todos.Todo" syncable="YES">
@@ -21,7 +21,7 @@
2121
</userInfo>
2222
</attribute>
2323
<attribute name="text" attributeType="String" syncable="YES"/>
24-
<relationship name="list" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="List" inverseName="todos" inverseEntity="List" syncable="YES"/>
24+
<relationship name="list" optional="YES" maxCount="1" deletionRule="No Action" destinationEntity="List" inverseName="todos" inverseEntity="List" syncable="YES"/>
2525
</entity>
2626
<entity name="User" representedClassName="Todos.User" syncable="YES">
2727
<attribute name="creationDate" optional="YES" attributeType="Date" syncable="YES">
@@ -37,15 +37,15 @@
3737
<attribute name="profile" optional="YES" attributeType="Transformable" syncable="YES"/>
3838
<attribute name="services" optional="YES" attributeType="Transformable" syncable="YES"/>
3939
<attribute name="username" optional="YES" attributeType="String" syncable="YES"/>
40-
<relationship name="privateLists" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="List" inverseName="user" inverseEntity="List" syncable="YES">
40+
<relationship name="privateLists" optional="YES" toMany="YES" deletionRule="No Action" destinationEntity="List" inverseName="user" inverseEntity="List" syncable="YES">
4141
<userInfo>
4242
<entry key="storage" value="false"/>
4343
</userInfo>
4444
</relationship>
4545
</entity>
4646
<elements>
47-
<element name="List" positionX="-45" positionY="194" width="128" height="103"/>
48-
<element name="Todo" positionX="-63" positionY="392" width="128" height="103"/>
49-
<element name="User" positionX="178" positionY="198" width="128" height="133"/>
47+
<element name="List" positionX="-45" positionY="194" width="128" height="105"/>
48+
<element name="Todo" positionX="-63" positionY="392" width="128" height="105"/>
49+
<element name="User" positionX="178" positionY="198" width="128" height="135"/>
5050
</elements>
5151
</model>

Meteor/METIncrementalStore.m

+10-4
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,9 @@ - (id)objectIDForToOneRelationship:(NSRelationshipDescription *)relationship inD
270270
NSString *fieldName = [self fieldNameForRelationship:relationship];
271271
id destinationDocumentID = document[fieldName];
272272

273-
if (destinationDocumentID) {
274-
return [self objectIDForDocumentKey:[METDocumentKey keyWithCollectionName:[self collectionNameForEntity:relationship.destinationEntity] documentID:destinationDocumentID]];
273+
METDocument *destinationDocument = [_client.database documentWithKey:[METDocumentKey keyWithCollectionName:[self collectionNameForEntity:relationship.destinationEntity] documentID:destinationDocumentID]];
274+
if (destinationDocument) {
275+
return [self objectIDForDocument:destinationDocument];
275276
}
276277

277278
return [NSNull null];
@@ -302,7 +303,12 @@ - (NSArray *)objectIDsForToManyRelationship:(NSRelationshipDescription *)relatio
302303
if (!destinationDocumentIDs) return nil;
303304
NSEntityDescription *destinationEntity = relationship.destinationEntity;
304305
return [destinationDocumentIDs mappedArrayUsingBlock:^id(id destinationDocumentID) {
305-
return [self objectIDForDocumentKey:[METDocumentKey keyWithCollectionName:[self collectionNameForEntity:destinationEntity] documentID:destinationDocumentID]];
306+
METDocument *destinationDocument = [_client.database documentWithKey:[METDocumentKey keyWithCollectionName:[self collectionNameForEntity:destinationEntity] documentID:destinationDocumentID]];
307+
if (destinationDocument) {
308+
return [self objectIDForDocument:destinationDocument];
309+
} else {
310+
return nil;
311+
}
306312
}];
307313
}
308314

@@ -588,5 +594,5 @@ - (NSError *)errorWithCode:(NSInteger)code localizedDescription:(NSString *)loca
588594
userInfo[NSLocalizedDescriptionKey] = localizedDescription;
589595
return [NSError errorWithDomain:METIncrementalStoreErrorDomain code:code userInfo:userInfo];
590596
}
591-
597+
592598
@end
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
22
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14D136" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">
3-
<entity name="Avatar" syncable="YES">
4-
<relationship name="player" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Player" inverseName="avatar" inverseEntity="Player" syncable="YES"/>
3+
<entity name="Avatar" representedClassName="METManagedObject" syncable="YES">
4+
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
5+
<relationship name="player" optional="YES" maxCount="1" deletionRule="No Action" destinationEntity="Player" inverseName="avatar" inverseEntity="Player" syncable="YES"/>
56
</entity>
67
<entity name="Meal" syncable="YES">
78
<userInfo>
@@ -15,20 +16,20 @@
1516
<entry key="fieldName" value="createdAt"/>
1617
</userInfo>
1718
</attribute>
18-
<relationship name="receivers" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Player" inverseName="receivedMessages" inverseEntity="Player" syncable="YES"/>
19-
<relationship name="sender" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Player" inverseName="sentMessages" inverseEntity="Player" syncable="YES"/>
19+
<relationship name="receivers" optional="YES" toMany="YES" deletionRule="No Action" destinationEntity="Player" inverseName="receivedMessages" inverseEntity="Player" syncable="YES"/>
20+
<relationship name="sender" optional="YES" maxCount="1" deletionRule="No Action" destinationEntity="Player" inverseName="sentMessages" inverseEntity="Player" syncable="YES"/>
2021
</entity>
21-
<entity name="Player" syncable="YES">
22+
<entity name="Player" representedClassName="METManagedObject" syncable="YES">
2223
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
2324
<attribute name="score" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
24-
<relationship name="avatar" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Avatar" inverseName="player" inverseEntity="Avatar" syncable="YES"/>
25-
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Message" inverseName="receivers" inverseEntity="Message" syncable="YES"/>
26-
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Message" inverseName="sender" inverseEntity="Message" syncable="YES"/>
25+
<relationship name="avatar" optional="YES" maxCount="1" deletionRule="No Action" destinationEntity="Avatar" inverseName="player" inverseEntity="Avatar" syncable="YES"/>
26+
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="No Action" destinationEntity="Message" inverseName="receivers" inverseEntity="Message" syncable="YES"/>
27+
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="No Action" destinationEntity="Message" inverseName="sender" inverseEntity="Message" syncable="YES"/>
2728
</entity>
2829
<elements>
29-
<element name="Avatar" positionX="-63" positionY="36" width="128" height="58"/>
30+
<element name="Avatar" positionX="-63" positionY="36" width="128" height="30"/>
3031
<element name="Meal" positionX="-54" positionY="36" width="128" height="45"/>
31-
<element name="Message" positionX="-54" positionY="9" width="128" height="103"/>
32+
<element name="Message" positionX="-54" positionY="9" width="128" height="105"/>
3233
<element name="Player" positionX="-63" positionY="-18" width="128" height="120"/>
3334
</elements>
3435
</model>

Tests/UnitTests/METCoreDataDDPClientTests.m

+87
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,88 @@ - (void)testConvertsNSManagedObjectIDParametersToDocumentIDs {
5757
XCTAssertEqualObjects(@[@"lovelace"], [_client convertParameters:@[lovelace.objectID]]);
5858
}
5959

60+
- (void)testMergesChangesForOneToOneRelationshipWithOneWayReferencingWhenReferencedDocumentIsRemovedAndAddedAgainLater {
61+
[self setNoStorageForRelationshipWithName:@"player" inEntityWithName:@"Avatar"];
62+
63+
[_client.database performUpdatesInLocalCacheWithoutTrackingChanges:^(METDocumentCache *localCache) {
64+
[localCache addDocumentWithKey:[METDocumentKey keyWithCollectionName:@"avatars" documentID:@"avatar1"] fields:@{}];
65+
[localCache updateDocumentWithKey:[METDocumentKey keyWithCollectionName:@"players" documentID:@"lovelace"] changedFields:@{@"avatarId": @"avatar1"}];
66+
}];
67+
68+
NSManagedObject *lovelace = [self existingObjectForDocumentWithKey:[METDocumentKey keyWithCollectionName:@"players" documentID:@"lovelace"]];
69+
NSManagedObject *avatar1 = [self existingObjectForDocumentWithKey:[METDocumentKey keyWithCollectionName:@"avatars" documentID:@"avatar1"]];
70+
71+
XCTAssertEqualObjects(avatar1, [lovelace valueForKey:@"avatar"]);
72+
XCTAssertEqualObjects(lovelace, [avatar1 valueForKey:@"player"]);
73+
74+
[self expectationForChangeToObject:avatar1 userInfoKey:NSDeletedObjectsKey];
75+
76+
[_client.database performUpdatesInLocalCache:^(METDocumentCache *localCache) {
77+
[localCache removeDocumentWithKey:[METDocumentKey keyWithCollectionName:@"avatars" documentID:@"avatar1"]];
78+
}];
79+
80+
[self waitForExpectationsWithTimeout:1.0 handler:nil];
81+
82+
XCTAssertNil([lovelace valueForKey:@"avatar"]);
83+
84+
[self expectationForChangeToObject:avatar1 userInfoKey:NSInsertedObjectsKey];
85+
86+
[_client.database performUpdatesInLocalCache:^(METDocumentCache *localCache) {
87+
[localCache addDocumentWithKey:[METDocumentKey keyWithCollectionName:@"avatars" documentID:@"avatar1"] fields:@{@"name": @"test"}];
88+
}];
89+
90+
[self waitForExpectationsWithTimeout:1.0 handler:nil];
91+
92+
XCTAssertEqualObjects(avatar1, [lovelace valueForKey:@"avatar"]);
93+
XCTAssertEqualObjects(lovelace, [avatar1 valueForKey:@"player"]);
94+
}
95+
96+
- (void)testMergesChangesForOneToManyRelationshipWithReferenceFieldOnTheOneSideWhenReferencedDocumentIsRemovedAndAddedAgainLater {
97+
[self setNoStorageForRelationshipWithName:@"sentMessages" inEntityWithName:@"Player"];
98+
99+
[_client.database performUpdatesInLocalCacheWithoutTrackingChanges:^(METDocumentCache *localCache) {
100+
[localCache addDocumentWithKey:[METDocumentKey keyWithCollectionName:@"players" documentID:@"lovelace"] fields:@{@"name": @"Ada Lovelace"}];
101+
[localCache addDocumentWithKey:[METDocumentKey keyWithCollectionName:@"messages" documentID:@"message1"] fields:@{@"senderId": @"lovelace"}];
102+
}];
103+
104+
NSManagedObject *lovelace = [self existingObjectForDocumentWithKey:[METDocumentKey keyWithCollectionName:@"players" documentID:@"lovelace"]];
105+
NSManagedObject *message1 = [self existingObjectForDocumentWithKey:[METDocumentKey keyWithCollectionName:@"messages" documentID:@"message1"]];
106+
107+
XCTAssertEqualObjects(lovelace, [message1 valueForKey:@"sender"]);
108+
XCTAssertEqualObjects([NSSet setWithObject:message1], [lovelace valueForKey:@"sentMessages"]);
109+
110+
[self expectationForChangeToObject:lovelace userInfoKey:NSDeletedObjectsKey];
111+
112+
[_client.database performUpdatesInLocalCache:^(METDocumentCache *localCache) {
113+
[localCache removeDocumentWithKey:[METDocumentKey keyWithCollectionName:@"players" documentID:@"lovelace"]];
114+
}];
115+
116+
[self waitForExpectationsWithTimeout:1.0 handler:nil];
117+
118+
XCTAssertNil([message1 valueForKey:@"sender"]);
119+
120+
[self expectationForChangeToObject:lovelace userInfoKey:NSInsertedObjectsKey];
121+
122+
[_client.database performUpdatesInLocalCache:^(METDocumentCache *localCache) {
123+
[localCache addDocumentWithKey:[METDocumentKey keyWithCollectionName:@"players" documentID:@"lovelace"] fields:@{@"name": @"Ada Lovelace"}];
124+
}];
125+
126+
[self waitForExpectationsWithTimeout:1.0 handler:nil];
127+
128+
NSLog(@"lovelace.sentMessages: %@", [lovelace valueForKey:@"sentMessages"]);
129+
130+
XCTAssertEqualObjects(lovelace, [message1 valueForKey:@"sender"]);
131+
XCTAssertEqualObjects([NSSet setWithObject:message1], [lovelace valueForKey:@"sentMessages"]);
132+
}
133+
60134
#pragma mark - Helper Methods
61135

136+
- (void)setNoStorageForRelationshipWithName:(NSString *)relationshipName inEntityWithName:(NSString *)entityName {
137+
NSEntityDescription *entity = _client.managedObjectModel.entitiesByName[entityName];
138+
NSRelationshipDescription *relationship = entity.relationshipsByName[relationshipName];
139+
relationship.userInfo = @{@"storage": @NO};
140+
}
141+
62142
- (NSManagedObject *)existingObjectWithID:(NSManagedObjectID *)objectID {
63143
NSError *error;
64144
NSManagedObject *object = [_client.mainQueueManagedObjectContext existingObjectWithID:objectID error:&error];
@@ -73,4 +153,11 @@ - (NSManagedObject *)existingObjectForDocumentWithKey:(METDocumentKey *)document
73153
return [self existingObjectWithID:objectID];
74154
}
75155

156+
- (XCTestExpectation *)expectationForChangeToObject:(NSManagedObject *)object userInfoKey:(NSString *)userInfoKey {
157+
return [self expectationForNotification:NSManagedObjectContextObjectsDidChangeNotification object:object.managedObjectContext handler:^BOOL(NSNotification *notification) {
158+
NSSet *changedObjects = notification.userInfo[userInfoKey];
159+
return [changedObjects containsObject:object];
160+
}];
161+
}
162+
76163
@end

0 commit comments

Comments
 (0)