Merge branch 'duplicate-import-fix' into import-unique-objects-order

This commit is contained in:
Andrii Chernenko
2016-10-23 14:52:34 +02:00
2 changed files with 95 additions and 37 deletions

View File

@@ -414,6 +414,65 @@ class ImportTests: BaseTestDataTestCase {
}
}
@objc
dynamic func test_ThatImportUniqueObjects_ImportsLastOfImportSourcesWithSameIDs() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
stack.beginSynchronous { (transaction) in
do {
let sourceArray: [TestEntity1.ImportSource] = [
[
#keyPath(TestEntity1.testEntityID): NSNumber(value: 106),
#keyPath(TestEntity1.testBoolean): NSNumber(value: true),
#keyPath(TestEntity1.testNumber): NSNumber(value: 6),
#keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"),
#keyPath(TestEntity1.testString): "nil:TestEntity1:6",
#keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!,
#keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!
],
[
#keyPath(TestEntity1.testEntityID): NSNumber(value: 106),
#keyPath(TestEntity1.testBoolean): NSNumber(value: false),
#keyPath(TestEntity1.testNumber): NSNumber(value: 7),
#keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"),
#keyPath(TestEntity1.testString): "nil:TestEntity1:7",
#keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!,
#keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")!
]
]
let objects = try transaction.importUniqueObjects(
Into<TestEntity1>(),
sourceArray: sourceArray
)
XCTAssertEqual(objects.count, 1)
XCTAssertEqual(transaction.fetchCount(From<TestEntity1>()), 6)
let object = objects[0]
let dictionary = sourceArray[1]
XCTAssertEqual(object.testEntityID, dictionary[(#keyPath(TestEntity1.testEntityID))] as? NSNumber)
XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber)
XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber)
XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber)
XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String)
XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data)
XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date)
}
catch {
XCTFail()
}
transaction.context.reset()
}
}
}
@objc
dynamic func test_ThatImportUniqueObject_CanThrowError() {

View File

@@ -184,7 +184,9 @@ public extension BaseDataTransaction {
/**
Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources.
- Warning: While the array returned from `importUniqueObjects(...)` correctly maps to the order of `sourceArray`, the order of objects called with `ImportableUniqueObject` methods is arbitrary. Do not make assumptions that any particular object will be imported ahead or after another object.
Objects are called with `ImportableUniqueObject` methods in the same order as in import sources array.
The array returned from `importUniqueObjects(...)` correctly maps to the order of `sourceArray`.
If `sourceArray` contains multiple import sources with same ID, the last one will be imported.
- parameter into: an `Into` clause specifying the entity type
- parameter sourceArray: the array of objects to import values from
@@ -206,7 +208,7 @@ public extension BaseDataTransaction {
let entityType = into.entityClass as! T.Type
var mapping = Dictionary<T.UniqueIDType, T.ImportSource>()
var importSourceByID = Dictionary<T.UniqueIDType, T.ImportSource>()
let sortedIDs = try autoreleasepool {
return try sourceArray.flatMap { (source) -> T.UniqueIDType? in
@@ -215,50 +217,47 @@ public extension BaseDataTransaction {
return nil
}
mapping[uniqueIDValue] = source
// each subsequent import source with the same ID will replace the existing one
importSourceByID[uniqueIDValue] = source
return uniqueIDValue
}
}
mapping = try autoreleasepool { try preProcess(mapping) }
var objects = Dictionary<T.UniqueIDType, T>()
for object in self.fetchAll(From(entityType), Where(entityType.uniqueIDKeyPath, isMemberOf: sortedIDs)) ?? [] {
importSourceByID = try autoreleasepool { try preProcess(importSourceByID) }
var existingObjectsByID = Dictionary<T.UniqueIDType, T>()
self.fetchAll(From(entityType), Where(entityType.uniqueIDKeyPath, isMemberOf: sortedIDs))?
.forEach { existingObjectsByID[$0.uniqueIDValue] = $0 }
var processedObjectIDs = Set<T.UniqueIDType>()
var result = [T]()
for objectID in sortedIDs {
try autoreleasepool {
let uniqueIDValue = object.uniqueIDValue
guard let source = mapping.removeValue(forKey: uniqueIDValue),
entityType.shouldUpdate(from: source, in: self) else {
return
guard let source = importSourceByID[objectID], !processedObjectIDs.contains(objectID) else { return }
if let object = existingObjectsByID[objectID] {
guard entityType.shouldUpdate(from: source, in: self) else { return }
try object.update(from: source, in: self)
result.append(object)
}
try object.update(from: source, in: self)
objects[uniqueIDValue] = object
else if entityType.shouldInsert(from: source, in: self) {
let object = self.create(into)
object.uniqueIDValue = objectID
try object.didInsert(from: source, in: self)
result.append(object)
}
processedObjectIDs.insert(objectID)
}
}
for (uniqueIDValue, source) in mapping {
try autoreleasepool {
guard entityType.shouldInsert(from: source, in: self) else {
return
}
let object = self.create(into)
object.uniqueIDValue = uniqueIDValue
try object.didInsert(from: source, in: self)
objects[uniqueIDValue] = object
}
}
return sortedIDs.flatMap { objects[$0] }
return result
}
}
}