Skip to content

Commit

Permalink
Fix bug 225 (#266)
Browse files Browse the repository at this point in the history
* Add unit tests

* Add initial unit test

* Simplify test case

* Add support for clearing removed references

* Remove unused parameter

* Fix tests

* Added more tests

* Update comments

* Check for null children

* Improve consistency between empty and null

* Add comment
  • Loading branch information
3lvis authored Aug 26, 2016
1 parent 595bc65 commit 9213169
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 15 deletions.
36 changes: 33 additions & 3 deletions Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@
1469B7FB1C68D7D50080FD71 /* notes_with_user_id_custom.json in Resources */ = {isa = PBXBuildFile; fileRef = 1469B7FA1C68D7D50080FD71 /* notes_with_user_id_custom.json */; };
146F2EB31CE1F27200543F83 /* id.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 146F2EB11CE1F27200543F83 /* id.xcdatamodeld */; };
146F2EB51CE1F2C500543F83 /* id.json in Resources */ = {isa = PBXBuildFile; fileRef = 146F2EB41CE1F2C500543F83 /* id.json */; };
147A3A0F1D704D860049C22A /* 225-a-empty.json in Resources */ = {isa = PBXBuildFile; fileRef = 147A3A0D1D704D860049C22A /* 225-a-empty.json */; };
147A3A101D704D860049C22A /* 225-a-null.json in Resources */ = {isa = PBXBuildFile; fileRef = 147A3A0E1D704D860049C22A /* 225-a-null.json */; };
1491123C1D70272D00167D26 /* 225-a.json in Resources */ = {isa = PBXBuildFile; fileRef = 149112351D70272D00167D26 /* 225-a.json */; };
1491123D1D70272D00167D26 /* 225-a-replaced.json in Resources */ = {isa = PBXBuildFile; fileRef = 149112361D70272D00167D26 /* 225-a-replaced.json */; };
149112451D70279200167D26 /* 225.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 149112431D70279200167D26 /* 225.xcdatamodeld */; };
14959D141C065350004EF790 /* notes_with_user_id.json in Resources */ = {isa = PBXBuildFile; fileRef = 14959D131C065350004EF790 /* notes_with_user_id.json */; };
14A644111CD77A4C00F51E23 /* Bug202.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 14A6440F1CD77A4C00F51E23 /* Bug202.xcdatamodeld */; };
14A644151CD77C7A00F51E23 /* bug-202-a.json in Resources */ = {isa = PBXBuildFile; fileRef = 14A644121CD77C7A00F51E23 /* bug-202-a.json */; };
Expand Down Expand Up @@ -166,6 +171,11 @@
146D72B11AB782920058798C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
146F2EB21CE1F27200543F83 /* Demo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Demo.xcdatamodel; sourceTree = "<group>"; };
146F2EB41CE1F2C500543F83 /* id.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = id.json; sourceTree = "<group>"; };
147A3A0D1D704D860049C22A /* 225-a-empty.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "225-a-empty.json"; sourceTree = "<group>"; };
147A3A0E1D704D860049C22A /* 225-a-null.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "225-a-null.json"; sourceTree = "<group>"; };
149112351D70272D00167D26 /* 225-a.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "225-a.json"; sourceTree = "<group>"; };
149112361D70272D00167D26 /* 225-a-replaced.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "225-a-replaced.json"; sourceTree = "<group>"; };
149112441D70279200167D26 /* 151-many-to-many.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "151-many-to-many.xcdatamodel"; sourceTree = "<group>"; };
14959D131C065350004EF790 /* notes_with_user_id.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = notes_with_user_id.json; sourceTree = "<group>"; };
14A644101CD77A4C00F51E23 /* Demo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Demo.xcdatamodel; sourceTree = "<group>"; };
14A644121CD77C7A00F51E23 /* bug-202-a.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "bug-202-a.json"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -228,6 +238,10 @@
14D7321E1CE9A6C400C28D40 /* 157-cities.json */,
140B0DEE1CEAFAFC00D0EE3E /* 157-locations-update.json */,
141D9E711CE9A4830041A7FC /* 157-locations.json */,
149112351D70272D00167D26 /* 225-a.json */,
149112361D70272D00167D26 /* 225-a-replaced.json */,
147A3A0D1D704D860049C22A /* 225-a-empty.json */,
147A3A0E1D704D860049C22A /* 225-a-null.json */,
1418940B1BDD62F600EE52CE /* bug-113-comments-no-id.json */,
1418940C1BDD62F600EE52CE /* bug-113-custom_relationship_key_to_one.json */,
1418940D1BDD62F600EE52CE /* bug-113-stories-comments-no-ids.json */,
Expand Down Expand Up @@ -279,6 +293,7 @@
14096A3E1CEAF483002690D6 /* 151-ordered-many-to-many.xcdatamodeld */,
14096A401CEAF483002690D6 /* 151-ordered-to-many.xcdatamodeld */,
14096A3A1CEAF40E002690D6 /* 151-to-many.xcdatamodeld */,
149112431D70279200167D26 /* 225.xcdatamodeld */,
1418942B1BDD62F600EE52CE /* Bug84.xcdatamodeld */,
141894251BDD62F600EE52CE /* Bug113.xcdatamodeld */,
141894291BDD62F600EE52CE /* Bug125.xcdatamodeld */,
Expand Down Expand Up @@ -308,11 +323,11 @@
1425D59A1CC65BEB00EC49D4 /* Source */ = {
isa = PBXGroup;
children = (
1425D59B1CC65BEB00EC49D4 /* NSArray+Sync.swift */,
1425D59C1CC65BEB00EC49D4 /* NSEntityDescription+Sync.swift */,
1425D59F1CC65BEB00EC49D4 /* Sync.swift */,
1425D59D1CC65BEB00EC49D4 /* NSManagedObject+Sync.swift */,
1425D59C1CC65BEB00EC49D4 /* NSEntityDescription+Sync.swift */,
1425D59E1CC65BEB00EC49D4 /* NSManagedObjectContext+Sync.swift */,
1425D59F1CC65BEB00EC49D4 /* Sync.swift */,
1425D59B1CC65BEB00EC49D4 /* NSArray+Sync.swift */,
);
path = Source;
sourceTree = "<group>";
Expand Down Expand Up @@ -457,6 +472,7 @@
files = (
B0B637171C6E517F00229B03 /* bug-179-places.json in Resources */,
14A644151CD77C7A00F51E23 /* bug-202-a.json in Resources */,
1491123D1D70272D00167D26 /* 225-a-replaced.json in Resources */,
1418944C1BDD62F600EE52CE /* numbers.json in Resources */,
14959D141C065350004EF790 /* notes_with_user_id.json in Resources */,
14A644161CD77C7A00F51E23 /* bug-202-b.json in Resources */,
Expand All @@ -474,10 +490,12 @@
141894581BDD62F600EE52CE /* users_notes.json in Resources */,
14BF14DA1D6ED307008F5915 /* to-one-camelcase.json in Resources */,
141894531BDD62F600EE52CE /* unique.json in Resources */,
1491123C1D70272D00167D26 /* 225-a.json in Resources */,
B0B637181C6E517F00229B03 /* bug-179-routes.json in Resources */,
141894401BDD62F600EE52CE /* bug-113-comments-no-id.json in Resources */,
141894431BDD62F600EE52CE /* bug-125-light.json in Resources */,
1418944A1BDD62F600EE52CE /* markets_items.json in Resources */,
147A3A101D704D860049C22A /* 225-a-null.json in Resources */,
144EFF4C1D6CF3EC003EA935 /* bug-254.json in Resources */,
146F2EB51CE1F2C500543F83 /* id.json in Resources */,
141894561BDD62F600EE52CE /* users_c.json in Resources */,
Expand All @@ -496,6 +514,7 @@
14096A371CEAF329002690D6 /* 151-to-many-users.json in Resources */,
140FBF1E1CB91C2D0026484E /* camelcase.json in Resources */,
141894501BDD62F600EE52CE /* stories-comments-no-ids.json in Resources */,
147A3A0F1D704D860049C22A /* 225-a-empty.json in Resources */,
1418944F1BDD62F600EE52CE /* patients.json in Resources */,
141894471BDD62F600EE52CE /* custom_relationship_key_to_many.json in Resources */,
14BF14DB1D6ED307008F5915 /* to-one-snakecase.json in Resources */,
Expand Down Expand Up @@ -579,6 +598,7 @@
141D9E751CE9A4970041A7FC /* Bug157.xcdatamodeld in Sources */,
141894621BDD62F600EE52CE /* Recursive.xcdatamodeld in Sources */,
14E298431C563EE500B68B72 /* OrderedSocial.xcdatamodeld in Sources */,
149112451D70279200167D26 /* 225.xcdatamodeld in Sources */,
14A644111CD77A4C00F51E23 /* Bug202.xcdatamodeld in Sources */,
1425D5A31CC65BEB00EC49D4 /* NSManagedObjectContext+Sync.swift in Sources */,
141894591BDD62F600EE52CE /* Bug113.xcdatamodeld in Sources */,
Expand Down Expand Up @@ -957,6 +977,16 @@
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
149112431D70279200167D26 /* 225.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
149112441D70279200167D26 /* 151-many-to-many.xcdatamodel */,
);
currentVersion = 149112441D70279200167D26 /* 151-many-to-many.xcdatamodel */;
path = 225.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
14A6440F1CD77A4C00F51E23 /* Bug202.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
Expand Down
93 changes: 81 additions & 12 deletions Source/NSManagedObject+Sync.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ public extension NSManagedObject {
setValue(nil, forKey: relationship.name)
}
} else {
guard let localPrimaryKey = localPrimaryKey as? NSArray else { return }
let remoteItems = localPrimaryKey
guard let remoteItems = localPrimaryKey as? NSArray else { return }
let localRelationship: NSSet
if relationship.ordered {
let value = self.valueForKey(relationship.name) as? NSOrderedSet ?? NSOrderedSet()
Expand Down Expand Up @@ -141,23 +140,93 @@ public extension NSManagedObject {
- parameter dataStack: The DATAStack instance.
*/
func sync_toManyRelationship(relationship: NSRelationshipDescription, dictionary: [String : AnyObject], parent: NSManagedObject?, parentRelationship: NSRelationshipDescription?, dataStack: DATAStack, operations: DATAFilter.Operation) {
guard let managedObjectContext = managedObjectContext, destinationEntity = relationship.destinationEntity, childEntityName = destinationEntity.name else { abort() }

let inverseIsToMany = relationship.inverseRelationship?.toMany ?? false
var children: [[String : AnyObject]]?
var isNull = false

if let customRelationshipName = relationship.userInfo?[SYNCCustomRemoteKey] as? String {
children = dictionary[customRelationshipName] as? [[String : AnyObject]]
} else if let result = dictionary[relationship.name.hyp_remoteString()] as? [[String : AnyObject]] {
children = result
} else if let result = dictionary[relationship.name] as? [[String : AnyObject]] {
children = result
if relationship.userInfo?[SYNCCustomRemoteKey] is NSNull {
isNull = true
} else if dictionary[relationship.name.hyp_remoteString()] is NSNull {
isNull = true
} else if dictionary[relationship.name] is NSNull {
isNull = true
}

if isNull {
children = [[String : AnyObject]]()

if valueForKey(relationship.name) != nil {
setValue(nil, forKey: relationship.name)
}
} else {
if let customRelationshipName = relationship.userInfo?[SYNCCustomRemoteKey] as? String {
children = dictionary[customRelationshipName] as? [[String : AnyObject]]
} else if let result = dictionary[relationship.name.hyp_remoteString()] as? [[String : AnyObject]] {
children = result
} else if let result = dictionary[relationship.name] as? [[String : AnyObject]] {
children = result
}
}

let inverseIsToMany = relationship.inverseRelationship?.toMany ?? false
guard let managedObjectContext = managedObjectContext else { abort() }
guard let destinationEntity = relationship.destinationEntity else { abort() }
guard let childEntityName = destinationEntity.name else { abort() }

if let children = children {
var childPredicate: NSPredicate?
let childIDs = (children as NSArray).valueForKey(entity.sync_remotePrimaryKey())

if childIDs is NSNull {
if let _ = valueForKey(relationship.name) {
setValue(nil, forKey: relationship.name)
}
} else {
guard let destinationEntityName = destinationEntity.name else { fatalError("entityName not found in entity: \(destinationEntity)") }
if let remoteItems = childIDs as? NSArray {
let localRelationship: NSSet
if relationship.ordered {
let value = self.valueForKey(relationship.name) as? NSOrderedSet ?? NSOrderedSet()
localRelationship = value.set
} else {
localRelationship = self.valueForKey(relationship.name) as? NSSet ?? NSSet()
}
let localItems = localRelationship.valueForKey(entity.sync_localPrimaryKey()) as? NSSet ?? NSSet()

let deletedItems = NSMutableArray(array: localItems.allObjects)
deletedItems.removeObjectsInArray(remoteItems as [AnyObject])

if deletedItems.count > 0 {
let request = NSFetchRequest(entityName: destinationEntityName)

do {
let safeLocalObjects = try managedObjectContext.executeFetchRequest(request) as? [NSManagedObject] ?? [NSManagedObject]()
for safeObject in safeLocalObjects {
let currentID = safeObject.valueForKey(entity.sync_localPrimaryKey())!
for deleted in deletedItems {
if currentID.isEqual(deleted) {
if relationship.ordered {
let relatedObjects = mutableOrderedSetValueForKey(relationship.name)
if relatedObjects.containsObject(safeObject) {
relatedObjects.removeObject(safeObject)
setValue(relatedObjects, forKey: relationship.name)
}
} else {
let relatedObjects = mutableSetValueForKey(relationship.name)
if relatedObjects.containsObject(safeObject) {
relatedObjects.removeObject(safeObject)
setValue(relatedObjects, forKey: relationship.name)
}
}
}
}
}
} catch {
fatalError()
}
}
}
}

var childPredicate: NSPredicate?
let manyToMany = inverseIsToMany && relationship.toMany
if manyToMany {
if childIDs.count > 0 {
Expand Down
6 changes: 6 additions & 0 deletions Tests/JSONs/225-a-empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"id": 1,
"tags": []
}
]
6 changes: 6 additions & 0 deletions Tests/JSONs/225-a-null.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"id": 1,
"tags": null
}
]
10 changes: 10 additions & 0 deletions Tests/JSONs/225-a-replaced.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"id": 1,
"tags": [
{
"id": 20
}
]
}
]
10 changes: 10 additions & 0 deletions Tests/JSONs/225-a.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"id": 1,
"tags": [
{
"id": 10
}
]
}
]
8 changes: 8 additions & 0 deletions Tests/Models/225.xcdatamodeld/.xccurrentversion
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>151-many-to-many.xcdatamodel</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="10174" systemVersion="15G31" minimumToolsVersion="Automatic">
<entity name="Tag" syncable="YES">
<attribute name="remoteID" optional="YES" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<relationship name="users" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="User" inverseName="tags" inverseEntity="User" syncable="YES"/>
</entity>
<entity name="User" syncable="YES">
<attribute name="remoteID" optional="YES" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<relationship name="tags" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Tag" inverseName="users" inverseEntity="Tag" syncable="YES"/>
</entity>
<elements>
<element name="Tag" positionX="-63" positionY="-18" width="128" height="75"/>
<element name="User" positionX="124" positionY="-27" width="128" height="75"/>
</elements>
</model>
Loading

0 comments on commit 9213169

Please sign in to comment.