Skip to content

Commit edd0dd7

Browse files
committed
Merge branch 'feature/summitJSON'
2 parents 66efd65 + 710ea0e commit edd0dd7

File tree

18 files changed

+216542
-50
lines changed

18 files changed

+216542
-50
lines changed

CoreDataCodable.xcodeproj/project.pbxproj

Lines changed: 79 additions & 13 deletions
Large diffs are not rendered by default.

Sources/CoreDataCodable.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,13 @@ import CoreData
1414
/// Specifies how a type can be encoded to be stored with Core Data.
1515
public protocol CoreDataCodable: Codable {
1616

17-
static var identifierKey: String { get }
17+
static var identifierKey: CodingKey { get }
1818

1919
var coreDataIdentifier: CoreDataIdentifier { get }
2020
}
2121

2222
extension CoreDataCodable {
2323

24-
@inline(__always)
25-
func findOrCreate(in managedObjectContext: NSManagedObjectContext) throws -> NSManagedObject {
26-
27-
return try self.coreDataIdentifier.findOrCreate(in: managedObjectContext)
28-
}
29-
3024
func encode(to managedObjectContext: NSManagedObjectContext) throws -> NSManagedObject {
3125

3226
let encoder = CoreDataEncoder(managedObjectContext: managedObjectContext)

Sources/Decoder.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,15 @@ fileprivate extension CoreDataDecoder.Decoder {
271271
// override for CoreData supported native types that also are Decodable
272272
// and don't use Decodable implementation
273273

274-
if type is Data.Type {
274+
if let string = value as? String, type is URL.Type {
275+
276+
return URL(string: string) as! T
277+
278+
} else if let string = value as? String, type is UUID.Type {
279+
280+
return UUID(uuidString: string) as! T
281+
282+
} else if type is Data.Type {
275283

276284
return try unbox(value, as: type)
277285

Sources/Encoder.swift

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public struct CoreDataEncoder {
3939
fileprivate func encode<Encodable : CoreDataCodable>(_ encodable: Encodable, codingPath: [CodingKey]) throws -> NSManagedObject {
4040

4141
// get managed object
42-
let managedObject = try encodable.findOrCreate(in: managedObjectContext)
42+
let managedObject = try encodable.coreDataIdentifier.findOrCreate(in: managedObjectContext)
4343

4444
// create encoder for managed object
4545
let encoder = Encoder(managedObjectContext: managedObjectContext,
@@ -274,10 +274,58 @@ fileprivate extension CoreDataEncoder {
274274
// Custom
275275
private mutating func encode(_ value: Data, forKey key: Key) throws { try write(box(value), forKey: key) }
276276
private mutating func encode(_ value: Date, forKey key: Key) throws { try write(box(value), forKey: key) }
277-
private mutating func encode(_ value: UUID, forKey key: Key) throws { try write(box(value), forKey: key) }
278-
private mutating func encode(_ value: URL, forKey key: Key) throws { try write(box(value), forKey: key) }
279277
private mutating func encode(_ value: Decimal, forKey key: Key) throws { try write(box(value), forKey: key) }
280278

279+
private mutating func encode(_ value: UUID, forKey key: Key) throws {
280+
281+
// check if attribute is string
282+
let attribute = container.entity.attributesByName[key.stringValue]?.attributeType ?? .undefinedAttributeType
283+
284+
switch attribute {
285+
286+
case .UUIDAttributeType:
287+
288+
try write(box(value), forKey: key)
289+
290+
case .stringAttributeType:
291+
292+
try write(box(value.uuidString), forKey: key)
293+
294+
default:
295+
296+
// set coding key context
297+
codingPath.append(key)
298+
defer { codingPath.removeLast() }
299+
300+
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Invalid value type"))
301+
}
302+
}
303+
304+
private mutating func encode(_ value: URL, forKey key: Key) throws {
305+
306+
// check if attribute is string
307+
let attribute = container.entity.attributesByName[key.stringValue]?.attributeType ?? .undefinedAttributeType
308+
309+
switch attribute {
310+
311+
case .URIAttributeType:
312+
313+
try write(box(value), forKey: key)
314+
315+
case .stringAttributeType:
316+
317+
try write(box(value.absoluteString), forKey: key)
318+
319+
default:
320+
321+
// set coding key context
322+
codingPath.append(key)
323+
defer { codingPath.removeLast() }
324+
325+
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Invalid value type"))
326+
}
327+
}
328+
281329
// Encodable
282330
public mutating func encode<T: Swift.Encodable>(_ value: T, forKey key: Key) throws {
283331

@@ -287,9 +335,7 @@ fileprivate extension CoreDataEncoder {
287335
// identifier or to-one relationship
288336
if let identifier = value as? CoreDataIdentifier {
289337

290-
let encodable = encoder.encodable
291-
292-
let identifierKey = type(of: encodable).identifierKey
338+
let identifierKey = type(of: encoder.encodable).identifierKey.stringValue
293339

294340
// identifier
295341
if key.stringValue == identifierKey {
@@ -402,25 +448,33 @@ fileprivate extension CoreDataEncoder {
402448

403449
private mutating func setRelationship(_ encodables: [CoreDataCodable], forKey key: Key) throws {
404450

405-
let managedObjects = try encodables.map { (try $0.findOrCreate(in: encoder.managedObjectContext), $0) }
451+
let managedObjectContext = encoder.managedObjectContext
452+
453+
var managedObjects = [NSManagedObject]()
454+
managedObjects.reserveCapacity(encodables.count)
406455

407-
try managedObjects.forEach {
456+
for (index, encodable) in encodables.enumerated() {
457+
458+
let codingPath: [CodingKey] = self.codingPath + [key, Index(intValue: index)]
459+
460+
let managedObject = try encodable.coreDataIdentifier.findOrCreate(in: managedObjectContext)
461+
managedObjects.append(managedObject)
408462

409463
// create encoder for managed object
410464
let encoder = Encoder(managedObjectContext: self.encoder.managedObjectContext,
411-
managedObject: $0,
412-
encodable: $1,
413-
codingPath: self.encoder.codingPath,
465+
managedObject: managedObject,
466+
encodable: encodable,
467+
codingPath: codingPath,
414468
userInfo: self.encoder.userInfo,
415469
log: self.encoder.log)
416470

417471
// encoder into container
418-
try $1.encode(to: encoder)
472+
try encodable.encode(to: encoder)
419473
}
420474

421475
let isOrdered = self.encoder.managedObject.entity.relationshipsByName[key.stringValue]?.isOrdered ?? false
422476

423-
let set: NSObject = isOrdered ? NSOrderedSet(array: managedObjects.map({ $0.0 })) : NSSet(array: managedObjects.map({ $0.0 }))
477+
let set: NSObject = isOrdered ? NSOrderedSet(array: managedObjects) : NSSet(array: managedObjects)
424478

425479
// set value
426480
try write(set, forKey: key)
@@ -437,13 +491,15 @@ fileprivate extension CoreDataEncoder {
437491

438492
private mutating func setRelationship(_ encodable: CoreDataCodable, forKey key: Key) throws {
439493

440-
let managedObject = try encodable.findOrCreate(in: self.encoder.managedObjectContext)
494+
let managedObject = try encodable.coreDataIdentifier.findOrCreate(in: self.encoder.managedObjectContext)
495+
496+
let codingPath: [CodingKey] = self.codingPath + [key]
441497

442498
// create encoder for managed object
443499
let newEncoder = Encoder(managedObjectContext: self.encoder.managedObjectContext,
444500
managedObject: managedObject,
445501
encodable: encodable,
446-
codingPath: self.encoder.codingPath,
502+
codingPath: codingPath,
447503
userInfo: self.encoder.userInfo,
448504
log: self.encoder.log)
449505

@@ -759,3 +815,30 @@ fileprivate extension CoreDataEncoder.Encoder {
759815
}
760816
}
761817
}
818+
819+
fileprivate extension CoreDataEncoder {
820+
821+
struct Index: CodingKey {
822+
823+
public let index: Int
824+
825+
public init(intValue: Int) {
826+
827+
self.index = intValue
828+
}
829+
830+
init?(stringValue: String) {
831+
832+
return nil
833+
}
834+
835+
public var intValue: Int? {
836+
return index
837+
}
838+
839+
public var stringValue: String {
840+
return "\(index)"
841+
}
842+
}
843+
}
844+

Tests/CoreDataCodableTests/CoreDataCodableTests.swift

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import XCTest
1111
import CoreData
1212
import CoreDataCodable
1313

14+
let SummitJSONIdentifiers = [6, 7, 22]
15+
1416
final class CoreDataCodableTests: XCTestCase {
1517

1618
func testAttributes() {
@@ -28,6 +30,8 @@ final class CoreDataCodableTests: XCTestCase {
2830
string: "test",
2931
uri: URL(string: "https://swift.org")!,
3032
uuid: UUID(),
33+
//urlValue: URL(string: "https://apple.com")!,
34+
//uuidValue: UUID(),
3135
enumValue: .three,
3236
optional: nil)
3337

@@ -57,6 +61,8 @@ final class CoreDataCodableTests: XCTestCase {
5761
XCTAssert(managedObject.string == value.string)
5862
XCTAssert(managedObject.uri == value.uri)
5963
XCTAssert(managedObject.uuid == value.uuid)
64+
//XCTAssert(managedObject.urlValue == value.urlValue.absoluteString)
65+
//XCTAssert(managedObject.uuidValue == value.uuidValue.uuidString)
6066
XCTAssert(managedObject.enumValue == value.enumValue.rawValue)
6167
XCTAssertNil(managedObject.optional)
6268

@@ -223,6 +229,55 @@ final class CoreDataCodableTests: XCTestCase {
223229
try $0.save()
224230
})
225231
}
232+
233+
func testSummit() {
234+
235+
let jsonDecoder = JSONDecoder()
236+
237+
for jsonIdentifier in SummitJSONIdentifiers {
238+
239+
let filename = "Summit\(jsonIdentifier)"
240+
241+
let testBundle = Bundle(for: type(of: self))
242+
243+
let resourcePath = testBundle.path(forResource: filename, ofType: "json", inDirectory: nil, forLocalization: nil)!
244+
245+
let jsonData = try! Data(contentsOf: URL(fileURLWithPath: resourcePath))
246+
247+
XCTAssertNoThrow(try context {
248+
249+
let summitJSON = try jsonDecoder.decode(SummitResponse.Summit.self, from: jsonData)
250+
251+
let summit = Model.Summit(jsonDecodable: summitJSON)
252+
253+
var encoder = CoreDataEncoder(managedObjectContext: $0)
254+
//encoder.log = { print($0) }
255+
256+
print("Will encode")
257+
258+
let managedObject = try encoder.encode(summit) as! SummitManagedObject
259+
260+
print("Did encode")
261+
262+
print(managedObject)
263+
264+
XCTAssert(managedObject.identifier == summit.identifier.rawValue)
265+
266+
var decoder = CoreDataDecoder(managedObjectContext: $0)
267+
//decoder.log = { print($0) }
268+
269+
print("Will decode")
270+
271+
let decoded = try decoder.decode(Model.Summit.self, with: summit.identifier)
272+
273+
print("Did decode")
274+
275+
XCTAssert(decoded.identifier == summit.identifier)
276+
277+
try $0.save()
278+
})
279+
}
280+
}
226281
}
227282

228283
extension CoreDataCodableTests {
@@ -289,8 +344,8 @@ extension NSManagedObjectContext {
289344
let fetchRequest = NSFetchRequest<T>(entityName: entityName)
290345
fetchRequest.predicate = NSPredicate(format: "%K == %@", property, identifier)
291346
fetchRequest.fetchLimit = 1
292-
fetchRequest.includesSubentities = false
293-
fetchRequest.returnsObjectsAsFaults = true
347+
fetchRequest.includesSubentities = true
348+
fetchRequest.returnsObjectsAsFaults = false
294349

295350
if let existing = try self.fetch(fetchRequest).first {
296351

@@ -309,14 +364,14 @@ extension NSManagedObjectContext {
309364
}
310365
}
311366

312-
protocol Unique {
367+
protocol TestUnique {
313368

314369
associatedtype Identifier: Codable, RawRepresentable
315370

316371
var identifier: Identifier { get }
317372
}
318373

319-
extension Unique where Self: CoreDataCodable, Self.Identifier: CoreDataIdentifier {
374+
extension TestUnique where Self: CoreDataCodable, Self.Identifier: CoreDataIdentifier {
320375

321376
static var identifierKey: String { return "identifier" }
322377

0 commit comments

Comments
 (0)